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