From 7379fa99c538dcbc8366178dd338b7b2b8ca81b1 Mon Sep 17 00:00:00 2001 From: Marc Audy Date: Tue, 1 Sep 2020 14:07:48 -0400 Subject: [PATCH] Merging //UE5/Release-Engine-Staging to Main (//UE5/Main) @ 14229157 [CL 14233282 by Marc Audy in ue5-main branch] --- .../Binaries/DotNET/CsvTools/ReportGraphs.xml | 16 +- .../Binaries/DotNET/CsvTools/ReportTypes.xml | 5 + .../epicgames/ue4/GameActivity.java.template | 190 +- Engine/Build/BatchFiles/Mac/Build.sh | 27 +- Engine/Build/BatchFiles/Mac/XcodeBuild.sh | 170 +- Engine/Build/Commit.gitdeps.xml | 2311 +++++------ Engine/Config/Android/AndroidEngine.ini | 22 +- .../Config/Android/DataDrivenPlatformInfo.ini | 27 + Engine/Config/BaseEngine.ini | 16 +- Engine/Config/BaseScalability.ini | 32 +- Engine/Config/IOS/DataDrivenPlatformInfo.ini | 17 + Engine/Config/IOS/IOSEngine.ini | 8 +- .../BuildConfigProperties.INT.udn | 3 +- .../framework/src/framework.ts | 6 +- .../functional_tests/framework/src/tests.ts | 4 +- .../framework/src/tests/test-terminal.ts | 37 + .../v3/functional_tests/framework/src/ztag.ts | 2 +- Engine/Extras/RoboMerge/v3/public/index.html | 7 + Engine/Extras/RoboMerge/v3/public/login.html | 8 + .../RoboMerge/v3/src/common/perforce.ts | 84 +- Engine/Extras/RoboMerge/v3/src/common/ztag.ts | 2 +- .../RoboMerge/v3/src/robo/branchdefs.ts | 1 + .../Extras/RoboMerge/v3/src/robo/edgebot.ts | 7 + Engine/Extras/Windows/cl-filter/cl-filter.cpp | 41 +- .../Paper2D/Private/PaperTileMapComponent.cpp | 12 +- .../Private/AnimNodesTrack.cpp | 2 +- .../Private/AnimationTickRecordsTrack.cpp | 2 +- .../Private/GameplayInsightsModule.cpp | 3 + .../LiveLink/Private/LiveLinkPreset.cpp | 96 +- .../Source/LiveLink/Public/LiveLinkPreset.h | 14 +- .../MovieSceneLiveLinkTrackRecorder.cpp | 4 + .../Private/OpenColorIOShaderMap.cpp | 39 +- .../OpenColorIO/Public/OpenColorIOShared.h | 10 +- .../Private/AnimationSharingModule.cpp | 4 + .../Public/AnimationSharingModule.h | 8 + .../Source/Concert/Private/ConcertClient.cpp | 10 +- .../Concert/Private/ConcertClientSession.cpp | 5 +- .../Concert/Private/ConcertServerSession.cpp | 5 +- .../Private/ConcertLocalEndpoint.cpp | 3 + .../Private/DisasterRecoveryTasks.cpp | 3 + .../PropertyAccessNode.uplugin | 33 + .../Private/K2Node_PropertyAccess.cpp | 251 ++ .../Private/K2Node_PropertyAccess.h | 88 + .../Private/PropertyAccessNodeFactory.cpp | 15 + .../Private/PropertyAccessNodeFactory.h | 12 + .../Private/PropertyAccessNodeModule.cpp | 27 + .../Private/SPropertyAccessNode.cpp | 199 + .../Private/SPropertyAccessNode.h | 25 + .../PropertyAccessNode.Build.cs | 30 + .../Private/SourceFilterSetup.cpp | 1 + .../Source/Private/AssetSearchManager.cpp | 16 +- .../Private/Indexers/BlueprintIndexer.cpp | 6 +- ...etIndexer.cpp => GenericObjectIndexer.cpp} | 11 +- ...aAssetIndexer.h => GenericObjectIndexer.h} | 13 +- .../Indexers/MaterialExpressionIndexer.cpp | 72 + .../Indexers/MaterialExpressionIndexer.h | 25 + .../Source/Private/Widgets/SSearchTreeRow.cpp | 34 +- .../Private/ContentBrowserAssetDataCore.cpp | 23 +- .../ContentBrowserAssetDataPayload.cpp | 2 +- .../Public/ContentBrowserAssetDataCore.h | 2 +- .../Private/ContentBrowserClassDataSource.cpp | 22 +- .../Private/NativeClassHierarchy.cpp | 12 +- .../Public/ContentBrowserClassDataSource.h | 2 + .../Private/EditorLevelLibrary.cpp | 24 + .../Public/EditorLevelLibrary.h | 3 + .../Private/SGameplayTagGraphPin.cpp | 2 +- .../CADLibrary/Private/CoreTechHelper.cpp | 2 +- .../Private/DatasmithCADTranslator.cpp | 8 +- .../Private/DatasmithOpenNurbsTranslator.cpp | 4 +- .../Private/DatasmithWireTranslator.cpp | 440 +-- .../ActorPalette/ActorPalette.uplugin | 25 + .../Source/ActorPalette/ActorPalette.Build.cs | 51 + .../ActorPalette/Private/ActorPalette.cpp | 22 + .../ActorPalette/Private/ActorPalette.h | 26 + .../Private/ActorPaletteCommands.cpp | 13 + .../Private/ActorPaletteModule.cpp | 130 + .../Private/ActorPaletteSettings.cpp | 111 + .../Private/ActorPaletteSettings.h | 86 + .../Private/ActorPaletteStyle.cpp | 84 + .../Private/ActorPaletteViewport.cpp | 502 +++ .../Private/ActorPaletteViewport.h | 50 + .../Private/ActorPaletteViewportClient.cpp | 204 + .../Private/ActorPaletteViewportClient.h | 51 + .../Public/ActorPaletteCommands.h | 24 + .../ActorPalette/Public/ActorPaletteModule.h | 38 + .../ActorPalette/Public/ActorPaletteStyle.h | 32 + .../AutomationUtilsBlueprintLibrary.cpp | 5 +- .../ChaosCaching/ChaosCaching.uplugin | 30 + .../Source/ChaosCaching/ChaosCaching.Build.cs | 41 + .../Private/Chaos/Adapters/CacheAdapter.cpp | 118 + ...eometryCollectionComponentCacheAdapter.cpp | 348 ++ .../StaticMeshComponentCacheAdapter.cpp | 193 + .../Private/Chaos/CacheCollection.cpp | 61 + .../Private/Chaos/CacheEvents.cpp | 182 + .../Private/Chaos/CacheManagerActor.cpp | 585 +++ .../ChaosCaching/Private/Chaos/ChaosCache.cpp | 511 +++ .../Private/Chaos/ChaosCachingPlugin.cpp | 13 + .../Public/Chaos/Adapters/CacheAdapter.h | 194 + .../GeometryCollectionComponentCacheAdapter.h | 65 + .../StaticMeshComponentCacheAdapter.h | 31 + .../Public/Chaos/CacheCollection.h | 24 + .../ChaosCaching/Public/Chaos/CacheEvents.h | 167 + .../Public/Chaos/CacheManagerActor.h | 241 ++ .../ChaosCaching/Public/Chaos/ChaosCache.h | 361 ++ .../Public/Chaos/ChaosCachingPlugin.h | 40 + .../ChaosCachingEditor.Build.cs | 34 + .../Chaos/ActorFactoryCacheManager.cpp | 80 + .../AssetTypeActions_ChaosCacheCollection.cpp | 46 + .../Chaos/CacheCollectionCustomization.cpp | 186 + .../Private/Chaos/CacheCollectionFactory.cpp | 36 + .../Private/Chaos/CacheEditorCommands.cpp | 14 + .../Chaos/CacheManagerCustomization.cpp | 225 ++ .../Chaos/ChaosCachingEditorPlugin.cpp | 284 ++ .../Public/Chaos/ActorFactoryCacheManager.h | 26 + .../AssetTypeActions_ChaosCacheCollection.h | 17 + .../Chaos/CacheCollectionCustomization.h | 39 + .../Public/Chaos/CacheCollectionFactory.h | 32 + .../Public/Chaos/CacheEditorCommands.h | 25 + .../Public/Chaos/CacheManagerCustomization.h} | 32 +- .../Public/Chaos/ChaosCachingEditorPlugin.h | 55 + .../NiagaraDataInterfaceFieldSystem.ush | 122 +- .../Source/ChaosNiagara/ChaosNiagara.Build.cs | 16 +- .../NiagaraDataInterfaceFieldSystem.h | 129 +- .../ChaosNiagara/Private/ChaosNiagara.cpp | 6 + .../NiagaraDataInterfaceChaosDestruction.cpp | 17 +- .../NiagaraDataInterfaceFieldSystem.cpp | 2061 ++++++++++ .../ChaosVehicles/Private/ChaosTireConfig.cpp | 130 - .../Private/ChaosVehicleManager.cpp | 18 +- .../Private/ChaosVehicleMovementComponent.cpp | 310 +- .../Private/ChaosVehicleWheel.cpp | 1 - .../ChaosWheeledVehicleMovementComponent.cpp | 389 +- .../ChaosVehicles/Private/SimpleCarActor.cpp | 168 - .../Private/VehicleContactModification.cpp | 240 -- .../ChaosVehicles/Public/ChaosTireConfig.h | 111 - .../Public/ChaosVehicleMovementComponent.h | 66 +- .../ChaosVehicles/Public/ChaosVehicleWheel.h | 7 - .../ChaosWheeledVehicleMovementComponent.h | 20 +- .../ChaosVehicles/Public/SimpleCarActor.h | 229 -- .../Public/VehicleContactModification.h | 16 - .../Private/ChaosVehiclesEditorCommands.cpp | 1 - .../MovieSceneControlRigParameterTemplate.cpp | 17 +- .../Private/ControlRigBlueprint.cpp | 2 + .../Private/Editor/ControlRigEditor.cpp | 31 + .../Private/Editor/ControlRigEditor.h | 4 + .../ForwardingChannels.uplugin | 20 - .../ForwardingChannels.Build.cs | 19 - .../Private/ForwardingChannel.cpp | 230 -- .../Private/ForwardingChannelsModule.cpp | 13 - .../Private/ForwardingChannelsModule.h | 34 - .../Private/ForwardingChannelsSubsystem.cpp | 150 - .../Private/ForwardingGroup.cpp | 198 - .../Public/ForwardingChannel.h | 232 -- .../Public/ForwardingChannelFactory.h | 48 - .../Public/ForwardingChannelsFwd.h | 16 - .../Public/ForwardingChannelsSubsystem.h | 91 - .../Public/ForwardingChannelsUtils.h | 142 - .../Public/ForwardingGroup.h | 124 - .../Public/ForwardingPacket.h | 16 - .../MovieSceneGeometryCacheSection.cpp | 11 +- .../Source/DynamicMesh/DynamicMesh.Build.cs | 4 +- .../DynamicMesh/Private/GroupTopology.cpp | 10 + .../DynamicMesh/Private/MeshCurvature.cpp | 64 +- .../Private/Operations/GroupEdgeInserter.cpp | 1289 ++++++ .../Operations/GroupTopologyDeformer.cpp | 11 +- .../Private/Operations/InsetMeshRegion.cpp | 156 +- .../Private/Operations/MeshConvexHull.cpp | 22 +- .../Private/Operations/MeshProjectionHull.cpp | 72 + .../Parameterization/DynamicMeshUVEditor.cpp | 45 + .../Parameterization/MeshUVPacking.cpp | 981 +++++ .../Sampling/MeshCurvatureMapBaker.cpp | 202 + .../Private/Sampling/MeshImageBakingCache.cpp | 261 ++ .../Private/Sampling/MeshNormalMapBaker.cpp | 62 + .../Sampling/MeshOcclusionMapBaker.cpp | 131 + .../Private/Sampling/MeshPropertyMapBaker.cpp | 130 + .../Sampling/MeshResampleImageBaker.cpp | 46 + .../MeshSimpleShapeApproximation.cpp | 415 ++ .../ShapeApproximation/ShapeDetection3.cpp | 260 ++ .../ShapeApproximation/SimpleShapeSet3.cpp | 346 ++ .../Solvers/ConstrainedMeshDeformer.cpp | 6 + .../ConstrainedMeshDeformationSolver.cpp | 176 +- .../ConstrainedMeshDeformationSolver.h | 92 + .../Internal/ConstrainedMeshDeformers.cpp | 97 + .../Internal/ConstrainedMeshDeformers.h | 23 + .../Internal/ConstrainedPoissonSolver.h | 36 + .../Private/Solvers/Internal/FSparseMatrixD.h | 41 + .../Solvers/Internal/LaplacianOperators.cpp | 36 - .../Private/Solvers/Internal/MeshUVSolver.cpp | 3 +- .../Source/DynamicMesh/Public/GroupTopology.h | 6 + .../Source/DynamicMesh/Public/MeshCurvature.h | 32 +- .../Public/Operations/GroupEdgeInserter.h | 115 + .../Public/Operations/InsetMeshRegion.h | 18 +- .../Public/Operations/MeshConvexHull.h | 3 - .../Public/Operations/MeshProjectionHull.h | 56 + .../Parameterization/DynamicMeshUVEditor.h | 11 + .../Public/Parameterization/MeshUVPacking.h | 46 + .../Public/Sampling/MeshCurvatureMapBaker.h | 86 + .../Public/Sampling/MeshImageBaker.h | 31 + .../Public/Sampling/MeshImageBakingCache.h | 76 + .../Public/Sampling/MeshNormalMapBaker.h | 41 + .../Public/Sampling/MeshOcclusionMapBaker.h | 44 + .../Public/Sampling/MeshPropertyMapBaker.h | 47 + .../Public/Sampling/MeshResampleImageBaker.h | 39 + .../MeshSimpleShapeApproximation.h | 165 + .../ShapeApproximation/ShapeDetection3.h | 45 + .../ShapeApproximation/SimpleShapeSet3.h | 129 + .../Public/Solvers/ConstrainedMeshDeformer.h | 13 + .../Public/Solvers/ConstrainedMeshSolver.h | 11 + .../Intersection/ContainmentQueries3.cpp | 50 +- .../Intersection/IntersectionQueries3.cpp | 52 + .../Private/Spatial/GeometrySet3.cpp | 37 +- .../Source/GeometricObjects/Public/BoxTypes.h | 17 + .../GeometricObjects/Public/CapsuleTypes.h | 17 + .../Public/Generators/CapsuleGenerator.h | 240 ++ .../Public/Generators/SphereGenerator.h | 4 + .../GeometricObjects/Public/HalfspaceTypes.h | 45 + .../Public/Image/ImageBuilder.h | 140 + .../Public/Image/ImageOccupancyMap.h | 2 +- .../Public/Intersection/ContainmentQueries3.h | 108 +- .../Intersection/IntersectionQueries3.h | 34 + .../Source/GeometricObjects/Public/MathUtil.h | 28 + .../Source/GeometricObjects/Public/Polygon2.h | 12 + .../Public/Sampling/VectorSetAnalysis.h | 103 + .../Public/Solvers/MatrixInterfaces.h | 14 +- .../Public/Spatial/DenseGrid2.h | 189 + .../Public/Spatial/FastWinding.h | 2 +- .../Public/Spatial/GeometrySet3.h | 5 + .../GeometricObjects/Public/SphereTypes.h | 17 + .../Public/Util/ElementLinearization.h | 12 + .../GeometricObjects/Public/VectorTypes.h | 327 ++ .../Private/ConvexHull2.cpp | 290 ++ .../Private/ConvexHull3.cpp | 327 +- .../Private/ExactPredicates.cpp | 14 +- .../ThirdParty/ShewchukPredicatesInterface.h | 16 +- .../GeometryAlgorithms/Private/GteUtil.h | 14 + .../Private/MinVolumeBox3.cpp | 10 + .../GeometryAlgorithms/Public/ConvexHull2.h | 117 + .../GeometryAlgorithms/Public/ConvexHull3.h | 116 +- .../Public/ExactPredicates.h | 16 + .../Private/DynamicMeshToMeshDescription.cpp | 66 +- .../Shaders/Private/NiagaraDirectSolver.ush | 1031 +++++ .../NiagaraDataInterfaceFieldSystem.cpp | 914 ----- .../NiagaraDataInterfaceHairStrands.cpp | 70 +- .../NiagaraDataInterfacePhysicsAsset.cpp | 86 +- .../NiagaraDataInterfacePressureGrid.cpp | 4 +- .../NiagaraDataInterfaceVelocityGrid.cpp | 16 +- .../Public/NiagaraDataInterfaceHairStrands.h | 8 +- .../Public/NiagaraDataInterfacePhysicsAsset.h | 9 - .../Public/NiagaraDataInterfacePressureGrid.h | 2 +- .../Public/NiagaraDataInterfaceVelocityGrid.h | 6 +- .../Private/ImagePlateComponent.cpp | 2 +- .../LiveStreamAnimation.uplugin | 38 - .../Source/LSAEditor/LSAEditor.Build.cs | 28 - .../LSAEditor/Private/LSAEditorModule.cpp | 41 - .../LSAEditor/Private/LSAEditorModule.h | 36 - .../Private/LSAHandleDetailCustomization.cpp | 283 -- .../LSALiveLinkFrameTranslatorAssetActions.h | 22 - .../LSALiveLinkFrameTranslatorFactory.cpp | 30 - .../LSALiveLinkFrameTranslatorFactory.h | 23 - .../LiveStreamAnimation.Build.cs | 28 - .../Private/ControlPacket.cpp | 60 - .../Private/ControlPacket.h | 79 - .../Private/LiveLink/LiveLinkPacket.cpp | 404 -- .../Private/LiveLink/LiveLinkPacket.h | 205 - .../LiveLink/LiveLinkStreamingHelper.cpp | 493 --- .../LiveLink/LiveLinkStreamingHelper.h | 108 - .../LiveStreamAnimationLiveLinkFrameData.cpp | 27 - ...StreamAnimationLiveLinkFrameTranslator.cpp | 264 -- .../LiveStreamAnimationLiveLinkSource.cpp | 203 - .../LiveStreamAnimationLiveLinkSource.h | 50 - .../Test/SkelMeshToLiveLinkSource.cpp | 273 -- .../Private/LiveStreamAnimationChannel.cpp | 78 - .../Private/LiveStreamAnimationHandle.cpp | 45 - .../Private/LiveStreamAnimationLog.cpp | 5 - .../Private/LiveStreamAnimationModule.cpp | 13 - .../Private/LiveStreamAnimationModule.h | 35 - .../Private/LiveStreamAnimationPacket.cpp | 54 - .../Private/LiveStreamAnimationPacket.h | 91 - .../Private/LiveStreamAnimationSettings.cpp | 109 - .../Private/LiveStreamAnimationSubsystem.cpp | 252 -- .../LiveStreamAnimationLiveLinkFrameData.h | 38 - ...veStreamAnimationLiveLinkFrameTranslator.h | 136 - .../LiveStreamAnimationLiveLinkRole.h | 31 - ...LiveStreamAnimationLiveLinkSourceOptions.h | 80 - .../LiveLink/Test/SkelMeshToLiveLinkSource.h | 150 - .../Public/LiveStreamAnimationChannel.h | 51 - .../Public/LiveStreamAnimationFwd.h | 18 - .../Public/LiveStreamAnimationHandle.h | 147 - .../Public/LiveStreamAnimationSettings.h | 88 - .../Public/LiveStreamAnimationSubsystem.h | 241 -- .../MattRadford_RnD/MattRadford_RnD.uplugin | 15 - .../MeshModelingTools.Build.cs | 2 +- .../Private/BakeMeshAttributeMapsTool.cpp | 1032 +++-- .../Private/DeformMeshPolygonsTool.cpp | 24 +- .../Private/DrawAndRevolveTool.cpp | 226 +- .../Private/DrawPolygonTool.cpp | 3 +- .../Private/EdgeLoopInsertionTool.cpp | 522 +++ .../Private/EditMeshPolygonsTool.cpp | 56 +- .../Private/MeshInspectorTool.cpp | 12 +- .../Private/MeshVertexSculptTool.cpp | 17 +- .../CollisionGeometryVisualization.cpp | 118 + .../Private/Physics/CollisionPropertySets.cpp | 71 + .../Physics/ExtractCollisionGeometryTool.cpp | 321 ++ .../Private/Physics/PhysicsInspectorTool.cpp | 155 + .../Physics/SetCollisionGeometryTool.cpp | 461 +++ .../Private/Sculpting/KelvinletBrushOp.h | 453 ++- .../Public/AddPrimitiveTool.h | 34 +- .../Public/BakeMeshAttributeMapsTool.h | 241 +- .../Public/DeformMeshPolygonsTool.h | 1 + .../Public/DrawAndRevolveTool.h | 47 +- .../Public/DrawPolygonTool.h | 3 +- .../Public/EdgeLoopInsertionTool.h | 243 ++ .../Public/EditMeshPolygonsTool.h | 69 +- .../Public/MeshVertexSculptTool.h | 12 + .../Physics/CollisionGeometryVisualization.h | 21 + .../Public/Physics/CollisionPropertySets.h | 144 + .../Physics/ExtractCollisionGeometryTool.h | 77 + .../Public/Physics/PhysicsInspectorTool.h | 63 + .../Public/Physics/SetCollisionGeometryTool.h | 221 ++ .../Private/UVLayoutTool.cpp | 108 +- .../Public/UVLayoutTool.h | 53 +- .../Private/Drawing/LineSetComponent.cpp | 18 +- .../Private/Drawing/PointSetComponent.cpp | 39 +- .../Private/Drawing/PolyEditPreviewMesh.cpp | 6 +- .../Private/Drawing/TriangleSetComponent.cpp | 35 +- .../Private/Drawing/UVLayoutPreview.cpp | 222 ++ .../Mechanics/CurveControlPointsMechanic.cpp | 1352 +++++++ .../Private/MeshOpPreviewHelpers.cpp | 11 + .../Private/Physics/PhysicsDataCollection.cpp | 84 + .../Private/PreviewMesh.cpp | 15 +- .../Selection/GroupTopologySelector.cpp | 177 +- .../Selection/PolygonSelectionMechanic.cpp | 87 +- .../Snapping/BasePositionSnapSolver3.cpp | 18 +- .../Snapping/PointPlanarSnapSolver.cpp | 233 +- .../Private/ToolSceneQueriesUtil.cpp | 22 + .../Private/ToolSetupUtil.cpp | 1 - .../Public/AssetUtils/Texture2DBuilder.h | 97 +- .../Public/Drawing/LineSetComponent.h | 6 + .../Public/Drawing/PointSetComponent.h | 15 +- .../Public/Drawing/PolyEditPreviewMesh.h | 2 +- .../Public/Drawing/TriangleSetComponent.h | 14 + .../Public/Drawing/UVLayoutPreview.h | 162 + .../Mechanics/CurveControlPointsMechanic.h | 492 +++ .../Public/MeshOpPreviewHelpers.h | 8 + .../Physics/CollisionGeometryConversion.h | 75 + .../Public/Physics/PhysicsDataCollection.h | 78 + .../ModelingComponents/Public/PreviewMesh.h | 11 +- .../Public/Selection/GroupTopologySelector.h | 75 +- .../Selection/PolygonSelectionMechanic.h | 20 +- .../Public/Snapping/BasePositionSnapSolver3.h | 2 + .../Public/Snapping/PointPlanarSnapSolver.h | 72 +- .../Public/ToolSceneQueriesUtil.h | 8 + .../CuttingOps/EdgeLoopInsertionOp.cpp | 56 + .../Public/CuttingOps/EdgeLoopInsertionOp.h | 42 + .../CompositionOps/VoxelBooleanMeshesOp.cpp | 6 +- .../CompositionOps/VoxelMergeMeshesOp.cpp | 6 +- .../ParameterizeMeshOp.cpp | 27 +- .../ParameterizationOps/UVLayoutOp.cpp | 119 +- .../Public/ParameterizationOps/UVLayoutOp.h | 17 +- .../Private/ModelingToolsActions.cpp | 8 + .../Private/ModelingToolsEditorMode.cpp | 37 +- .../ModelingToolsEditorModeToolkit.cpp | 14 + .../Private/ModelingToolsManagerActions.cpp | 8 + .../Public/ModelingToolsActions.h | 1 + .../Public/ModelingToolsManagerActions.h | 10 +- .../MotionTrailEditorMode.uplugin | 30 + .../MotionTrailEditorMode.Build.cs | 48 + .../Private/MotionTrailEditorMode.cpp | 201 + .../Private/MotionTrailEditorModeCommands.cpp | 27 + .../Private/MotionTrailEditorModeModule.cpp | 48 + .../Private/MotionTrailEditorModeToolkit.cpp | 41 + .../Private/MotionTrailEditorToolset.cpp | 148 + .../Private/MovieSceneTransformTrail.cpp | 588 +++ .../Private/MovieSceneTransformTrail.h | 228 ++ .../Private/SequencerTrailHierarchy.cpp | 303 ++ .../Private/SequencerTrailHierarchy.h | 60 + .../MotionTrailEditorMode/Private/Trail.cpp | 72 + .../Private/TrailHierarchy.cpp | 185 + .../Private/TrajectoryCache.cpp | 101 + .../Private/TrajectoryDrawInfo.cpp | 64 + .../Public/MotionTrailEditorMode.h | 115 + .../Public/MotionTrailEditorModeCommands.h | 38 + .../Public/MotionTrailEditorModeModule.h | 20 + .../Public/MotionTrailEditorModeToolkit.h | 24 + .../Public/MotionTrailEditorToolset.h | 120 + .../MotionTrailEditorMode/Public/Trail.h | 96 + .../Public/TrailHierarchy.h | 84 + .../Public/TrajectoryCache.h | 108 + .../Public/TrajectoryDrawInfo.h | 88 + .../PlatformCryptoAesEncryptorsOpenSSL.cpp | 12 +- .../ProxyLOD/Private/ProxyLODVolume.cpp | 8 +- .../Private/PythonScriptPlugin.cpp | 4 + .../Private/SkeletalMeshReductionPlugin.cpp | 7 + .../Source/Private/SkeletalSimplifier.h | 5 + .../Private/SkeletalSimplifierMeshManager.cpp | 253 +- .../Private/SkeletalSimplifierMeshManager.h | 31 + .../Classes/WebSocketConnection.h | 2 +- .../Private/WebsocketConnection.cpp | 4 +- ...agaraStackGraphUtilitiesAdapterLibrary.cpp | 663 ++-- ...NiagaraStackGraphUtilitiesAdapterLibrary.h | 188 +- .../Private/NiagaraEmitterInstanceShader.usf | 44 +- .../Private/NiagaraMeshVertexFactory.ush | 2 +- .../Private/NiagaraRibbonVertexFactory.ush | 88 +- .../Shaders/Private/NiagaraSortKeyGen.usf | 26 +- .../Private/NiagaraSpriteVertexFactory.ush | 68 +- .../Private/NiagaraVFParticleAccess.usf | 83 +- .../Niagara/Classes/NDISkeletalMeshCommon.h | 10 +- .../Source/Niagara/Classes/NiagaraConstants.h | 8 +- .../Niagara/Classes/NiagaraDataInterface.h | 58 +- .../Classes/NiagaraDataInterfaceArray.h | 4 + ...NiagaraDataInterfaceArrayFunctionLibrary.h | 6 + .../Classes/NiagaraDataInterfaceArrayImpl.h | 308 +- .../Classes/NiagaraDataInterfaceAudioPlayer.h | 39 +- .../Classes/NiagaraDataInterfaceCamera.h | 14 +- .../NiagaraDataInterfaceGrid2DCollection.h | 31 +- .../NiagaraDataInterfaceGrid3DCollection.h | 24 +- .../NiagaraDataInterfaceNeighborGrid3D.h | 2 +- .../Niagara/Classes/NiagaraDataInterfaceRW.h | 3 + .../NiagaraDataInterfaceRenderTarget2D.h | 111 + .../NiagaraDataInterfaceSkeletalMesh.h | 32 +- .../Classes/NiagaraDataInterfaceStaticMesh.h | 36 +- .../Classes/NiagaraDataInterfaceTexture.h | 4 +- .../Source/Niagara/Classes/NiagaraDataSet.h | 1 - .../Niagara/Classes/NiagaraDataSetAccessor.h | 4 +- .../Source/Niagara/Classes/NiagaraEmitter.h | 6 +- .../Niagara/Classes/NiagaraEmitterInstance.h | 7 + .../Classes/NiagaraEmitterInstanceBatcher.h | 25 +- .../Source/Niagara/Classes/NiagaraScript.h | 110 +- .../Classes/NiagaraScriptExecutionContext.h | 129 +- .../Source/Niagara/Classes/NiagaraSystem.h | 8 +- .../NDISkeletalMesh_TriangleSampling.cpp | 52 +- .../NDISkeletalMesh_VertexSampling.cpp | 23 +- .../Source/Niagara/Private/NiagaraCommon.cpp | 335 +- .../Niagara/Private/NiagaraComponent.cpp | 292 +- .../Niagara/Private/NiagaraComponentPool.cpp | 83 +- .../NiagaraComponentRendererProperties.cpp | 56 +- .../Private/NiagaraComponentSettings.cpp | 4 +- .../Niagara/Private/NiagaraConstants.cpp | 118 +- .../Private/NiagaraDataInterfaceArray.cpp | 13 +- .../NiagaraDataInterfaceArrayFloat.cpp | 18 +- ...agaraDataInterfaceArrayFunctionLibrary.cpp | 20 + .../Private/NiagaraDataInterfaceArrayImpl.cpp | 6 + .../Private/NiagaraDataInterfaceArrayInt.cpp | 6 +- .../NiagaraDataInterfaceAudioPlayer.cpp | 566 ++- .../Private/NiagaraDataInterfaceCamera.cpp | 109 +- .../NiagaraDataInterfaceCollisionQuery.cpp | 9 +- .../NiagaraDataInterfaceColorCurve.cpp | 2 +- .../NiagaraDataInterfaceGrid2DCollection.cpp | 159 +- ...araDataInterfaceGrid2DCollectionReader.cpp | 4 +- .../NiagaraDataInterfaceGrid3DCollection.cpp | 138 +- .../Private/NiagaraDataInterfaceLandscape.cpp | 4 +- .../NiagaraDataInterfaceNeighborGrid3D.cpp | 22 +- .../NiagaraDataInterfaceParticleRead.cpp | 224 +- .../Private/NiagaraDataInterfaceRW.cpp | 109 +- .../NiagaraDataInterfaceRenderTarget2D.cpp | 498 +++ .../NiagaraDataInterfaceSkeletalMesh.cpp | 410 +- .../Private/NiagaraDataInterfaceSpline.cpp | 9 +- .../NiagaraDataInterfaceStaticMesh.cpp | 316 +- .../Private/NiagaraDataInterfaceTexture.cpp | 20 +- .../NiagaraDataInterfaceVector2DCurve.cpp | 2 +- .../NiagaraDataInterfaceVector4Curve.cpp | 2 +- .../NiagaraDataInterfaceVectorCurve.cpp | 2 +- .../Source/Niagara/Private/NiagaraDataSet.cpp | 39 +- .../Niagara/Private/NiagaraEffectType.cpp | 2 +- .../Source/Niagara/Private/NiagaraEmitter.cpp | 79 +- .../Niagara/Private/NiagaraEmitterHandle.cpp | 5 +- .../Private/NiagaraEmitterInstance.cpp | 161 +- .../Private/NiagaraEmitterInstanceBatcher.cpp | 968 +++-- .../Private/NiagaraFunctionLibrary.cpp | 14 +- .../NiagaraGPUInstanceCountManager.cpp | 2 +- .../NiagaraLightRendererProperties.cpp | 28 +- .../Private/NiagaraMeshRendererProperties.cpp | 91 +- .../Source/Niagara/Private/NiagaraModule.cpp | 137 +- .../Niagara/Private/NiagaraParameterStore.cpp | 28 +- .../Niagara/Private/NiagaraRenderer.cpp | 110 +- .../Private/NiagaraRendererComponents.cpp | 16 +- .../Niagara/Private/NiagaraRendererLights.cpp | 19 +- .../Niagara/Private/NiagaraRendererMeshes.cpp | 6 +- .../Private/NiagaraRendererProperties.cpp | 73 +- .../Private/NiagaraRendererRibbons.cpp | 214 +- .../Private/NiagaraRendererSprites.cpp | 324 +- .../NiagaraRibbonRendererProperties.cpp | 139 +- .../Private/NiagaraScalabilityManager.cpp | 2 +- .../Source/Niagara/Private/NiagaraScript.cpp | 90 +- .../Private/NiagaraScriptExecutionContext.cpp | 536 ++- .../Niagara/Private/NiagaraSettings.cpp | 2 +- .../Private/NiagaraSimulationStageBase.cpp | 82 +- .../NiagaraSpriteRendererProperties.cpp | 170 +- .../Source/Niagara/Private/NiagaraSystem.cpp | 64 +- .../Niagara/Private/NiagaraSystemInstance.cpp | 451 +-- .../Private/NiagaraSystemSimulation.cpp | 171 +- .../NiagaraUserRedirectionParameterStore.cpp | 14 +- .../Niagara/Private/NiagaraWorldManager.cpp | 92 +- .../Source/Niagara/Public/NiagaraCommon.h | 156 +- .../Source/Niagara/Public/NiagaraComponent.h | 34 + .../Niagara/Public/NiagaraComponentPool.h | 10 +- .../NiagaraComponentRendererProperties.h | 11 +- .../Niagara/Public/NiagaraComponentSettings.h | 46 +- .../Public/NiagaraLightRendererProperties.h | 3 +- .../Public/NiagaraMeshRendererProperties.h | 5 +- .../Source/Niagara/Public/NiagaraModule.h | 10 + .../Niagara/Public/NiagaraParameterStore.h | 36 +- .../Source/Niagara/Public/NiagaraRenderer.h | 10 +- .../Public/NiagaraRendererComponents.h | 2 +- .../Niagara/Public/NiagaraRendererMeshes.h | 2 +- .../Public/NiagaraRendererProperties.h | 22 +- .../Niagara/Public/NiagaraRendererRibbons.h | 12 +- .../Niagara/Public/NiagaraRendererSprites.h | 9 +- .../Public/NiagaraRibbonRendererProperties.h | 134 +- .../Source/Niagara/Public/NiagaraSettings.h | 2 +- .../Public/NiagaraSimulationStageBase.h | 17 +- .../Public/NiagaraSpriteRendererProperties.h | 30 +- .../Niagara/Public/NiagaraSystemInstance.h | 91 +- .../Niagara/Public/NiagaraSystemSimulation.h | 24 +- .../Source/Niagara/Public/NiagaraTypes.h | 227 +- .../NiagaraUserRedirectionParameterStore.h | 10 +- .../Niagara/Public/NiagaraWorldManager.h | 31 +- .../Private/NiagaraCustomVersion.cpp | 2 +- .../NiagaraCore/Public/NiagaraCustomVersion.h | 6 + .../Public/NiagaraDataInterfaceBase.h | 57 +- ...garaComponentRendererPropertiesDetails.cpp | 7 +- .../Customizations/NiagaraScriptDetails.cpp | 42 +- .../NiagaraTypeCustomizations.cpp | 877 ++++- .../NiagaraTypeCustomizations.h | 64 +- .../Private/EdGraphSchema_Niagara.cpp | 12 +- .../Private/NiagaraClipboard.cpp | 13 +- .../NiagaraEditor/Private/NiagaraCompiler.cpp | 105 +- .../Private/NiagaraComponentBroker.h | 37 + .../Private/NiagaraEditorModule.cpp | 46 +- .../Private/NiagaraEditorUtilities.cpp | 97 +- .../NiagaraEditor/Private/NiagaraGraph.cpp | 14 +- .../Private/NiagaraHlslTranslator.cpp | 209 +- .../Private/NiagaraHlslTranslator.h | 29 +- .../NiagaraEditor/Private/NiagaraNode.cpp | 10 +- .../Private/NiagaraNodeAssignment.cpp | 4 +- .../Private/NiagaraNodeDataSetBase.cpp | 1 + .../Private/NiagaraNodeFunctionCall.cpp | 13 +- .../NiagaraEditor/Private/NiagaraNodeOp.cpp | 1 + .../Private/NiagaraNodeParameterMapBase.cpp | 51 +- .../Private/NiagaraNodeParameterMapBase.h | 6 +- .../Private/NiagaraNodeWithDynamicPins.cpp | 5 +- .../Private/NiagaraParameterMapHistory.cpp | 7 + .../Private/NiagaraScriptMergeManager.cpp | 353 +- .../Private/NiagaraScriptMergeManager.h | 48 +- .../NiagaraColorTypeEditorUtilities.cpp | 6 + .../NiagaraColorTypeEditorUtilities.h | 1 + .../NiagaraEnumTypeEditorUtilities.cpp | 5 + .../NiagaraEnumTypeEditorUtilities.h | 1 + .../NiagaraFloatTypeEditorUtilities.h | 2 + .../NiagaraVectorTypeEditorUtilities.cpp | 63 + .../NiagaraVectorTypeEditorUtilities.h | 14 + .../NiagaraParameterCollectionViewModel.cpp | 11 + .../NiagaraParameterCollectionViewModel.h | 3 + .../ViewModels/NiagaraScratchPadUtilities.cpp | 119 +- .../ViewModels/NiagaraSystemViewModel.cpp | 1 + .../Stack/NiagaraStackFunctionInput.cpp | 74 +- .../NiagaraStackFunctionInputCollection.cpp | 10 + .../Stack/NiagaraStackGraphUtilities.cpp | 10 +- .../Stack/NiagaraStackModuleItem.cpp | 36 +- .../ViewModels/Stack/NiagaraStackObject.cpp | 15 + .../Stack/NiagaraStackScriptItemGroup.cpp | 7 +- .../NiagaraStackSimulationStageGroup.cpp | 23 + .../Stack/NiagaraStackSystemSettingsGroup.cpp | 9 +- .../SNiagaraGraphParameterMapGetNode.cpp | 14 +- .../SNiagaraGraphParameterMapSetNode.cpp | 14 +- .../Widgets/SNiagaraParameterCollection.cpp | 46 +- .../Widgets/SNiagaraParameterMapView.cpp | 70 +- .../Widgets/SNiagaraParameterPanel.cpp | 18 +- .../Widgets/SNiagaraSpreadsheetView.cpp | 14 +- .../Widgets/SNiagaraSystemViewport.cpp | 46 +- .../Public/INiagaraEditorTypeUtilities.h | 7 + .../NiagaraEditor/Public/NiagaraClipboard.h | 3 + .../Public/NiagaraEditorModule.h | 5 +- .../Public/NiagaraEditorUtilities.h | 8 +- .../NiagaraEditor/Public/NiagaraGraph.h | 6 +- .../Stack/NiagaraStackFunctionInput.h | 8 + .../NiagaraStackFunctionInputCollection.h | 2 + .../Stack/NiagaraStackGraphUtilities.h | 4 +- .../ViewModels/Stack/NiagaraStackItemGroup.h | 2 + .../Stack/NiagaraStackSimulationStageGroup.h | 4 + ...iagaraDataInterfaceSkeletalMeshDetails.cpp | 327 +- .../Stack/SNiagaraStackFunctionInputValue.cpp | 19 +- .../Private/Stack/SNiagaraStackItemGroup.cpp | 112 +- .../Private/Stack/SNiagaraStackItemGroup.h | 4 + .../Private/Stack/SNiagaraStackModuleItem.cpp | 26 +- .../SNiagaraStackParameterStoreEntryValue.cpp | 24 +- .../SNiagaraStackParameterStoreEntryValue.h | 2 +- .../Private/NiagaraScriptBase.cpp | 8 + .../NiagaraShader/Private/NiagaraShader.cpp | 196 +- .../NiagaraShaderCompilationManager.cpp | 13 - .../Private/NiagaraShaderDerivedDataVersion.h | 2 +- .../NiagaraShader/Private/NiagaraShared.cpp | 233 +- .../NiagaraShader/Public/NiagaraScriptBase.h | 53 + .../NiagaraShader/Public/NiagaraShader.h | 6 +- .../NiagaraShader/Public/NiagaraShaderType.h | 1 + .../NiagaraShader/Public/NiagaraShared.h | 121 +- .../Public/NiagaraRibbonVertexFactory.h | 8 +- .../Public/NiagaraSpriteVertexFactory.h | 15 + .../Private/MagicLeapVREyeTracker.cpp | 3 + .../Private/MagicLeapHandMeshingModule.cpp | 2 + .../Private/MagicLeapImageTrackerModule.cpp | 3 + .../Source/Private/MagicLeapPlanesModule.cpp | 3 + .../Private/MagicLeapPrivilegesModule.cpp | 3 + .../Private/MagicLeapCVCameraModule.cpp | 3 + .../Private/MagicLeapCameraPlugin.cpp | 3 + .../Private/MagicLeapConnectionsPlugin.cpp | 3 + .../Private/MagicLeapContactsPlugin.cpp | 3 + .../Private/MagicLeapNetworkingPlugin.cpp | 3 + .../Private/MagicLeapScreensPlugin.cpp | 3 + .../Private/MagicLeapSharedFilePlugin.cpp | 3 + .../Source/Private/MagicLeapTabletPlugin.cpp | 3 + .../AppleProResMedia/AppleProResMedia.uplugin | 4 + .../AppleProResMedia.Build.cs | 3 +- .../MoviePipelineAppleProResOutput.cpp | 2 + .../Private/MoviePipelineAppleProResOutput.h | 2 + .../Media/AvidDNxMedia/AvidDNxMedia.uplugin | 7 + .../Source/Source/AvidDNxMedia.Build.cs | 1 + .../Private/MoviePipelineAvidDNxOutput.cpp | 0 .../Private/MoviePipelineAvidDNxOutput.h | 2 + .../Private/Codecs/VideoEncoder.cpp | 2 +- .../ThirdParty/vpx/build/EpicChanges.txt | 16 + .../vpx/build/Mac/BuildForMac.command | 197 + .../vpx/build/Mac/build-libvpx-mac.sh | 72 - .../webm/build/Mac/BuildForMac.command | 161 + .../webm/build/Mac/build-libwebm-mac.sh | 78 - .../Private/MeshPaintMode.cpp | 6 +- .../Private/UdpMessagingModule.cpp | 2 + .../Private/SequencerSectionBP.cpp | 3 +- .../Private/LevelSequenceEditorToolkit.cpp | 2 +- .../Misc/LevelSequenceEditorSpawnRegister.cpp | 7 + .../MovieRenderPipeline.uplugin | 27 +- .../MovieRenderPipelineCore.Build.cs | 6 + .../Private/MoviePipeline.cpp | 108 +- .../Private/MoviePipelineBlueprintLibrary.cpp | 84 +- .../MoviePipelineFCPXMLExporterSetting.cpp | 123 + .../MoviePipelineInProcessExecutor.cpp | 196 +- .../Private/MoviePipelineMasterConfig.cpp | 31 +- .../Private/MoviePipelineOutputBuilder.cpp | 7 + .../Private/MoviePipelineOutputSetting.cpp | 27 +- .../Private/MoviePipelineQueue.cpp | 2 + .../MoviePipelineQueueEngineSubsystem.cpp | 36 + .../Private/MoviePipelineRendering.cpp | 47 + .../Private/MoviePipelineSurfaceReader.cpp | 45 +- .../Private/MoviePipelineTiming.cpp | 4 +- .../Private/MoviePipelineVideoOutputBase.cpp | 24 +- .../MovieRenderPipelineCommandLine.cpp | 2 +- .../Public/MoviePipeline.h | 31 +- .../Public/MoviePipelineAntiAliasingSetting.h | 25 +- .../Public/MoviePipelineBlueprintLibrary.h | 7 +- .../Public/MoviePipelineCameraSetting.h | 20 +- .../Public/MoviePipelineExecutor.h | 5 + .../MoviePipelineFCPXMLExporterSetting.h | 49 + .../Public/MoviePipelineHighResSetting.h | 25 +- .../Public/MoviePipelineInProcessExecutor.h | 29 + .../Public/MoviePipelineMasterConfig.h | 2 +- .../Public/MoviePipelineOutputSetting.h | 13 +- .../Public/MoviePipelineQueue.h | 2 + .../MoviePipelineQueueEngineSubsystem.h | 78 + .../Public/MoviePipelineSetting.h | 4 +- .../Public/MoviePipelineVideoOutputBase.h | 4 + .../Public/MovieRenderPipelineDataTypes.cpp | 29 +- .../Public/MovieRenderPipelineDataTypes.h | 23 +- .../Private/MoviePipelinePIEExecutor.cpp | 1 + .../Widgets/SMoviePipelineConfigEditor.cpp | 6 + .../Widgets/SMoviePipelineQueuePanel.cpp | 4 +- .../MovieRenderPipelineRenderPasses.Build.cs | 10 +- .../Private/MoviePipelineDeferredPasses.cpp | 160 +- .../Private/MoviePipelineEXROutput.cpp | 429 ++ .../Private/MoviePipelineEXROutput.h | 137 + .../MoviePipelineImageSequenceOutput.cpp | 25 +- .../Private/MoviePipelineWaveOutput.cpp | 4 +- .../Public/MoviePipelineDeferredPasses.h | 89 +- .../Public/MoviePipelineImageSequenceOutput.h | 29 +- .../Public/MoviePipelineWaveOutput.h | 2 + .../Private/MoviePipelineBurnInSetting.cpp | 1 + .../MoviePipelineWidgetRenderSetting.cpp | 4 +- .../Public/MoviePipelineBurnInSetting.h | 2 +- .../MoviePipelineConsoleVariableSetting.h | 13 +- .../Public/MoviePipelineWidgetRenderSetting.h | 2 +- .../openexrRTTI/Private/OpenExrRTTIModule.cpp | 52 + .../openexrRTTI/Public/IOpenExrRTTIModule.h | 18 + .../Source/openexrRTTI/UEOpenExrRTTI.Build.cs | 34 + .../MovieRenderPipelineEncoders.uplugin | 42 - .../MovieRenderPipelineEncoders.Build.cs | 32 - .../MovieSceneBindingExtensions.cpp | 40 +- .../MovieScenePropertyTrackExtensions.cpp | 1 + .../MovieSceneSectionExtensions.cpp | 11 + .../MovieSceneSequenceExtensions.cpp | 49 + .../MovieSceneTrackExtensions.cpp | 37 +- .../MovieSceneVectorTrackExtensions.cpp | 2 + .../MovieSceneBindingExtensions.h | 15 +- .../MovieSceneSectionExtensions.h | 20 +- .../MovieSceneSequenceExtensions.h | 28 +- .../MovieSceneTrackExtensions.h | 38 +- .../Private/SequencerTools.cpp | 4 +- .../TemplateSequenceInstanceData.cpp | 3 - .../TemplateSequenceSectionTemplate.cpp | 70 - .../Section/TemplateSequenceSection.cpp | 37 +- .../Systems/TemplateSequenceSystem.cpp | 120 + .../Private/Systems/TemplateSequenceSystem.h | 51 + .../Private/Tracks/TemplateSequenceTrack.cpp | 28 - .../Evaluation/TemplateSequenceInstanceData.h | 20 - .../TemplateSequenceSectionTemplate.h | 40 - .../Public/Sections/TemplateSequenceSection.h | 13 +- .../Public/Tracks/TemplateSequenceTrack.h | 4 - .../NUTUnrealEngine4/NUTUnrealEngine4.uplugin | 2 +- .../NetcodeUnitTest/NetcodeUnitTest.uplugin | 2 +- .../Private/OnlineSubsystemGameCircle.cpp | 3 + .../Private/OnlineSubsystemGooglePlay.cpp | 3 + .../Source/Private/OnlinePurchaseIOS.cpp | 42 +- .../Source/Private/OnlineStoreIOS.cpp | 9 - .../Private/OnlineStoreInterfaceIOS.cpp | 6 +- .../Source/Private/OnlineStoreKitHelper.cpp | 363 +- .../Source/Private/OnlineStoreKitHelper.h | 6 +- .../Source/Private/OnlineSubsystemIOS.cpp | 13 +- .../Source/Public/OnlinePurchaseIOS.h | 3 - .../Source/Hotfix/Private/UpdateManager.cpp | 2 + .../Party/PartyPlatformSessionMonitor.cpp | 12 +- .../Source/Party/Private/SocialManager.cpp | 9 + .../Source/Party/Private/SocialToolkit.cpp | 7 + .../Party/Private/User/SocialUserList.cpp | 2 + .../Party/Public/Party/PartyDataReplicator.h | 3 + .../Source/Party/Public/SocialQuery.h | 2 + .../Source/Party/Public/SocialToolkit.h | 5 +- .../Source/PatchCheck/Private/PatchCheck.cpp | 2 +- .../Source/Qos/Private/QosEvaluator.cpp | 259 +- .../Source/Qos/Private/QosEvaluator.h | 26 +- .../Source/Private/OnlineSessionInterface.cpp | 35 + .../Interfaces/OnlinePresenceInterface.h | 11 +- .../Interfaces/OnlineSessionInterface.h | 93 +- .../Source/Public/OnlineSessionSettings.h | 9 + .../Source/Public/OnlineSubsystem.h | 15 +- .../Source/Private/OnlineSubsystemAmazon.cpp | 3 + .../Private/OnlineSubsystemFacebookCommon.cpp | 3 + .../Private/OnlineSubsystemGoogleCommon.cpp | 3 + .../Private/OnlineMessageSanitizerNull.cpp | 3 + .../Source/Private/OnlineSubsystemNull.cpp | 3 + .../Source/Private/OnlineSubsystemOculus.cpp | 3 + .../Source/Private/OnlineAuthHandlerSteam.h | 3 - .../Source/Private/OnlineSubsystemSteam.cpp | 3 + .../Classes/IpConnection.h | 2 +- .../Classes/IpNetDriver.h | 4 +- .../Classes/OnlineSessionClient.h | 6 + .../Private/IpConnection.cpp | 4 +- .../Private/IpNetDriver.cpp | 64 +- .../Private/OnlineSessionClient.cpp | 15 + .../Source/Private/VivoxVoiceChat.cpp | 50 +- .../Source/Public/VivoxVoiceChat.h | 2 + .../VoiceChat/Source/Public/VoiceChat.h | 16 +- .../Public/SkeletalMeshComponentBudgeted.h | 6 +- .../AudioModulation/AudioModulation.uplugin | 6 +- .../Private/AudioModulation.cpp | 5 + .../Private/AudioModulationDebugger.cpp | 27 +- .../Private/AudioModulationDebugger.h | 7 +- .../AudioModulationProfileSerializer.h | 50 +- .../Private/AudioModulationStatics.cpp | 66 +- .../Private/AudioModulationSystem.cpp | 167 +- .../Private/AudioModulationSystem.h | 46 +- .../Private/SoundControlBus.cpp | 34 +- .../Private/SoundControlBusMix.cpp | 39 +- .../Private/SoundControlBusMixProxy.cpp | 106 +- .../Private/SoundControlBusMixProxy.h | 34 +- .../Private/SoundControlBusProxy.cpp | 18 +- .../Private/SoundControlBusProxy.h | 16 +- .../Private/SoundModulationGenerator.cpp | 26 + ...FO.cpp => SoundModulationGeneratorLFO.cpp} | 27 +- ...p => SoundModulationGeneratorLFOProxy.cpp} | 4 +- ...y.h => SoundModulationGeneratorLFOProxy.h} | 12 +- .../Private/SoundModulationParameter.cpp | 1 + .../Private/SoundModulationPatch.cpp | 27 +- .../Private/SoundModulationPatchProxy.cpp | 21 +- .../Private/SoundModulationPatchProxy.h | 54 +- .../Private/SoundModulationTransform.cpp | 95 +- .../Private/SoundModulationValue.cpp | 127 +- .../AudioModulation/Public/AudioModulation.h | 2 + .../Public/AudioModulationStatics.h | 85 +- .../Public/AudioModulationStyle.h | 2 +- .../AudioModulation/Public/SoundControlBus.h | 34 +- .../Public/SoundControlBusMix.h | 38 +- .../Public/SoundModulationGenerator.h | 26 + ...torLFO.h => SoundModulationGeneratorLFO.h} | 23 +- .../Public/SoundModulationPatch.h | 98 +- .../Public/SoundModulationTransform.h | 54 +- .../Public/SoundModulationValue.h | 30 +- ...ypeActions_SoundModulationGeneratorLFO.cpp | 23 + ...TypeActions_SoundModulationGeneratorLFO.h} | 6 +- .../AssetTypeActions_SoundModulatorLFO.cpp | 23 - .../Private/AudioModulationEditor.cpp | 22 +- .../ModulationPatchCurveEditorViewStacked.cpp | 179 +- .../ModulationPatchCurveEditorViewStacked.h | 22 +- .../Private/Editors/ModulationPatchEditor.cpp | 304 +- .../Private/Editors/ModulationPatchEditor.h | 34 +- .../SoundModulationGeneratorLFOFactory.cpp | 19 + ...h => SoundModulationGeneratorLFOFactory.h} | 9 +- .../Factories/SoundModulatorLFOFactory.cpp | 19 - ....cpp => SoundControlBusMixStageLayout.cpp} | 16 +- ...yout.h => SoundControlBusMixStageLayout.h} | 4 +- .../SoundControlModulationPatchLayout.cpp | 106 +- .../SoundControlModulationPatchLayout.h | 71 +- ...SoundModulationParameterSettingsLayout.cpp | 2 +- .../SoundModulationTransformLayout.cpp | 29 +- .../Layouts/SoundModulationTransformLayout.h | 8 +- .../CableComponent/Classes/CableComponent.h | 1 + .../CableComponent/Private/CableComponent.cpp | 12 + .../Private/Abilities/GameplayAbility.cpp | 11 +- .../AbilitySystemComponent_Abilities.cpp | 2 +- .../Private/GameplayCueManager.cpp | 8 +- .../GameplayDebuggerCategory_Abilities.cpp | 94 +- .../GameplayDebuggerCategory_Abilities.h | 2 + .../Private/GameplayEffect.cpp | 2 +- .../Source/Private/HTTPChunkInstaller.cpp | 3 + .../Private/NetworkPredictionCues.cpp | 5 +- .../Private/NetworkPredictionPhysics.cpp | 26 +- .../NetworkPredictionPhysicsComponent.cpp | 2 +- .../Private/NetworkPredictionWorldManager.cpp | 58 +- .../Public/NetworkPredictionCues.h | 257 +- .../Public/NetworkPredictionDriver.h | 196 +- .../Public/NetworkPredictionModelDef.h | 25 +- .../Public/NetworkPredictionPhysics.h | 64 +- .../Public/NetworkPredictionProxy.h | 2 +- .../Public/NetworkPredictionProxyInit.h | 4 +- .../Public/NetworkPredictionSerialization.h | 64 +- .../Public/NetworkPredictionTrace.h | 28 +- .../Public/NetworkPredictionWorldManager.h | 17 + .../Services/NetworkPredictionInstanceData.h | 3 - .../NetworkPredictionService_Finalize.inl | 8 +- .../NetworkPredictionService_Interpolate.inl | 27 +- .../NetworkPredictionService_Physics.inl | 22 +- .../NetworkPredictionService_Rollback.inl | 97 +- .../NetworkPredictionService_Ticking.inl | 2 +- .../Runtime/NetworkPrediction/readme.txt | 39 +- .../NetworkPredictionExtras.uplugin | 2 +- .../Private/FlyingMovementComponent.cpp | 14 +- .../Private/FlyingMovementSimulation.cpp | 15 - .../Private/MockAbilitySimulation.cpp | 25 +- .../Private/MockPhysicsComponent.cpp | 16 +- .../Private/MockPhysicsSimulation.cpp | 160 +- .../Private/MockRootMotionComponent.cpp | 191 + .../Private/MockRootMotionSimulation.cpp | 104 + .../Private/MockRootMotionSourceDataAsset.cpp | 161 + .../Public/BaseMovementSimulation.h | 2 +- .../Public/FlyingMovementComponent.h | 3 + .../Public/FlyingMovementSimulation.h | 5 - .../Public/MockPhysicsSimulation.h | 3 +- .../Public/MockRootMotionComponent.h | 74 + .../Public/MockRootMotionSimulation.h | 271 ++ .../Public/MockRootMotionSourceDataAsset.h | 102 + .../Public/ParametricMovement.cpp | 15 +- .../Public/ParametricMovement.h | 3 +- .../NetworkPredictionInsightsModule.cpp | 2 + .../Private/OSCModulationMixingStatics.cpp | 36 +- .../Public/OSCModulationMixingStatics.h | 10 +- .../Private/OculusAudioDllManager.cpp | 3 + .../OculusAudio/Private/OculusAudioMixer.cpp | 2 + .../Source/Public/AESGCMHandlerComponent.h | 2 - .../Source/Public/AESHandlerComponent.h | 2 - .../Private/OodleTrainerCommandlet.cpp | 222 +- .../Public/OodleHandlerComponent.h | 8 - .../Source/DTLSHandlerComponent.Build.cs | 2 +- .../Source/Private/DTLSCertStore.cpp | 8 +- .../Source/Private/DTLSCertificate.cpp | 4 +- .../Source/Private/DTLSContext.cpp | 8 +- .../Source/Private/DTLSHandlerComponent.cpp | 13 +- .../Source/Public/DTLSCertStore.h | 7 +- .../Source/Public/DTLSCertificate.h | 4 +- .../Source/Public/DTLSHandlerComponent.h | 2 - .../PropertyAccess/PropertyAccess.uplugin | 31 + ...BlueprintClassSubsystem_PropertyAccess.cpp | 42 + .../PropertyAccess/Private/PropertyAccess.cpp | 856 ++++ .../Private/PropertyAccessModule.cpp} | 3 +- .../PropertyAccess/PropertyAccess.Build.cs | 17 + ...imBlueprintClassSubsystem_PropertyAccess.h | 36 + .../PropertyAccess/Public/PropertyAccess.h | 418 ++ .../Public/PropertyEventInterfaces.h | 52 + ...eprintCompilerSubsystem_PropertyAccess.cpp | 59 + ...lueprintCompilerSubsystem_PropertyAccess.h | 46 + .../Private/PropertyAccessEditor.cpp | 635 +++ .../Private/PropertyAccessEditor.h | 68 + .../Private/PropertyAccessEditorModule.cpp | 54 + .../Private/SPropertyBinding.cpp | 698 ++++ .../Private/SPropertyBinding.h | 85 + .../PropertyAccessEditor.Build.cs | 35 + .../Source/Private/ReplicationGraph.cpp | 368 +- .../Source/Private/ReplicationGraphTypes.cpp | 1 + .../Source/Public/ReplicationGraph.h | 33 +- .../Source/Public/ReplicationGraphTypes.h | 42 +- .../Private/SteamSocketsSubsystem.cpp | 3 + .../Runtime/Steam/SteamVR/SteamVR.uplugin | 15 +- .../SourceEffects/SourceEffectBitCrusher.h | 8 + .../SubmixEffectMultiBandCompressor.h | 134 + .../SubmixEffectMultiBandCompressor.cpp | 205 + .../TimeSynth/Private/TimeSynthComponent.cpp | 2 +- .../WinDualShock/Private/WinDualShock.cpp | 24 +- .../AnimationBlueprintFastPathTest.cpp | 30 +- .../Private/VirtualHeightfieldMesh.usf | 725 ++-- .../Private/VirtualHeightfieldMesh.ush | 114 +- .../VirtualHeightfieldMeshVertexFactory.ush | 461 +-- .../VirtualHeightfieldMeshComponent.cpp | 24 +- .../Private/VirtualHeightfieldMeshEnable.cpp | 12 +- .../VirtualHeightfieldMeshSceneProxy.cpp | 578 +-- .../VirtualHeightfieldMeshVertexFactory.cpp | 49 +- .../Public/VirtualHeightfieldMeshComponent.h | 58 +- .../Public/VirtualHeightfieldMeshSceneProxy.h | 9 +- .../VirtualHeightfieldMeshVertexFactory.h | 10 +- ...ualHeightfieldMeshDetailsCustomization.cpp | 39 +- ...rtualHeightfieldMeshDetailsCustomization.h | 3 + .../VirtualHeightfieldMeshEditor.Build.cs | 1 + .../VirtualHeightfieldMesh.uplugin | 2 +- .../Private/RemoteControlModule.cpp | 7 +- .../Recorder/TakeRecorderParameters.cpp | 1 + .../Public/Recorder/TakeRecorderParameters.h | 4 + .../Private/TakeRecorderActorSource.cpp | 1 + .../TakeRecorderMicrophoneAudioSource.cpp | 3 + .../MovieScene3DAttachTrackRecorder.cpp | 14 +- .../MovieScene3DTransformTrackRecorder.cpp | 1 + .../MovieSceneAnimationTrackRecorder.cpp | 3 + .../MovieSceneTrackPropertyRecorder.cpp | 17 +- .../IMovieSceneTrackRecorderHost.h | 2 + .../MovieSceneTrackPropertyRecorder.h | 4 +- .../TakesCore/Public/TakeRecorderSources.h | 2 +- .../Private/TimedDataMonitorCalibration.cpp | 6 +- .../Shaders/Private/BasePassPixelShader.usf | 2 +- .../Shaders/Private/CapsuleShadowShaders.usf | 7 +- .../Private/ComposeSeparateTranslucency.usf | 2 + .../Private/DeferredLightPixelShaders.usf | 2 + .../Private/DeferredLightingCommon.ush | 74 +- .../Shaders/Private/DeferredShadingCommon.ush | 10 +- Engine/Shaders/Private/LightmapCommon.ush | 6 +- Engine/Shaders/Private/MaterialTemplate.ush | 6 +- .../Private/PathTracing/PathTracing.usf | 5 +- Engine/Shaders/Private/PostProcessMobile.usf | 10 +- Engine/Shaders/Private/PostProcessTonemap.usf | 20 +- .../RayTracing/GenerateCulledLightListCS.usf | 141 + .../RayTracingAmbientOcclusionRGS.usf | 1 + .../RayTracing/RayTracingBuiltInShaders.usf | 1 + .../Private/RayTracing/RayTracingCommon.ush | 98 +- .../RayTracingCreateGatherPointsRGS.usf | 2 + .../Private/RayTracing/RayTracingDebug.usf | 1 + .../RayTracingDeferredReflections.usf | 13 +- .../RayTracingDeferredReflections.ush | 16 +- .../RayTracing/RayTracingDirectionalLight.ush | 3 +- .../RayTracing/RayTracingFinalGatherRGS.usf | 14 +- .../RayTracingGlobalIlluminationRGS.usf | 2 + .../RayTracingLightCullingCommon.ush | 110 + .../RayTracing/RayTracingLightingCommon.ush | 279 +- .../RayTracingMaterialHitShaders.usf | 51 +- .../RayTracing/RayTracingOcclusionRGS.usf | 112 +- .../RayTracing/RayTracingRectLight.ush | 7 +- .../RayTracing/RayTracingRectLightRGS.usf | 1 + .../RayTracing/RayTracingReflections.usf | 9 +- .../RayTracingReflectionsGenerateRaysCS.usf | 5 +- .../RayTracingSkyLightEvaluation.ush | 43 +- .../RayTracing/RayTracingSkyLightRGS.usf | 11 +- .../Private/RayTracing/RayTracingTest.usf | 1 + .../ReflectionEnvironmentPixelShader.usf | 2 +- .../Private/ReflectionEnvironmentShaders.usf | 20 +- .../Private/ReflectionEnvironmentShared.ush | 26 +- .../ScreenSpaceDenoise/SSDDefinitions.ush | 2 +- .../Private/ScreenSpaceDenoise/SSDInjest.usf | 5 + .../SSDSignalBufferEncoding.ush | 2 +- .../SSDSpatialAccumulation.usf | 14 +- .../ScreenSpaceDenoise/SSDSpatialKernel.ush | 8 +- .../SSDTemporalAccumulation.usf | 7 +- Engine/Shaders/Private/ShadingModels.ush | 15 +- .../Private/ShadowProjectionPixelShader.usf | 3 +- .../Shaders/Private/TemporalAA/TAACommon.ush | 15 - .../Private/TemporalAA/TAAUpdateHistory.usf | 51 +- Engine/Shaders/Private/VolumetricCloud.usf | 66 +- Engine/Shaders/Private/VolumetricFog.usf | 1 + .../Private/VolumetricRenderTarget.usf | 42 +- Engine/Shaders/Public/Platform.ush | 4 + .../Public/Platform/Metal/MetalCommon.ush | 2 +- Engine/Shaders/Public/ShaderVersion.ush | 2 +- Engine/Shaders/Shared/RayTracingDefinitions.h | 2 +- .../Private/MetalShaderCompiler.cpp | 16 +- .../AssetTools/Private/AssetTools.cpp | 2 + .../AssetTypeActions_TextureRenderTarget.cpp | 7 + ...setTypeActions_TextureRenderTargetVolume.h | 16 + .../AssetTypeActions_World.cpp | 37 +- .../AssetTypeActions/AssetTypeActions_World.h | 1 + .../AssetTools/Private/AssetViewUtils.cpp | 6 +- .../AssetTools/Public/AssetTypeActions_Base.h | 5 + .../AssetTools/Public/IAssetTypeActions.h | 3 + .../BlueprintCompilerCppBackendAnim.cpp | 29 +- ...BlueprintCompilerCppBackendValueHelper.cpp | 15 +- .../Private/BlueprintNativeCodeGenModule.cpp | 6 +- .../DesktopPlatform/Private/PlatformInfo.cpp | 9 + .../DesktopPlatform/Public/PlatformInfo.h | 8 +- .../Public/GameplayDebuggerPlayerManager.h | 2 +- .../Private/IOSTargetPlatform.cpp | 4 +- .../IoStoreUtilities.Build.cs | 1 + .../Private/IoStoreUtilities.cpp | 217 +- .../Private/Launcher/LauncherWorker.cpp | 56 +- .../Private/LinuxTargetPlatform.h | 4 +- .../Private/MacTargetPlatformModule.cpp | 2 +- .../Private/ExportMaterialProxy.h | 2 +- .../Private/MaterialBakingModule.cpp | 6 +- .../Private/MaterialUtilities.cpp | 8 +- .../Private/ProxyGenerationProcessor.cpp | 3 + .../OutputLog/Private/SDeviceOutputLog.cpp | 1 + .../OutputLog/Private/SDeviceOutputLog.h | 6 + .../Private/PakFileUtilities.cpp | 5 +- .../Private/RigVMModel/RigVMController.cpp | 24 +- .../Private/RigVMModel/RigVMGraph.cpp | 5 + .../Public/RigVMModel/RigVMController.h | 3 + .../Private/Widgets/SScreenComparisonRow.cpp | 13 + .../Private/Widgets/SScreenComparisonRow.h | 1 + .../Settings/Private/SettingsSection.cpp | 10 +- .../Private/VectorVMShaderCompiler.cpp | 4 +- .../Private/ir_vm_gen_bytecode_visitor.cpp | 7 + .../Private/ir_vm_scalarize_visitor.cpp | 10 +- .../Private/LODUtilities.cpp | 9 +- .../Private/TargetPlatformBase.cpp | 5 + .../Public/Common/TargetPlatformBase.h | 2 + .../Public/Interfaces/ITargetPlatform.h | 5 + .../Analyzers/LoadTimeTraceAnalysis.cpp | 2 +- .../Private/D3DShaderCompiler.inl | 11 +- .../Private/D3DShaderCompilerDXC.cpp | 13 +- .../Editor/AnimGraph/AnimGraph.Build.cs | 4 +- .../Classes/AnimGraphNode_AssetPlayerBase.h | 11 +- .../AnimGraph/Classes/AnimGraphNode_Base.h | 71 +- .../Classes/AnimGraphNode_CustomProperty.h | 8 +- .../AnimGraphNode_LinkedAnimGraphBase.h | 8 +- .../Classes/AnimGraphNode_LinkedInputPose.h | 8 +- .../AnimGraph/Classes/AnimGraphNode_Root.h | 2 + .../Classes/AnimGraphNode_SaveCachedPose.h | 1 + .../Classes/AnimGraphNode_StateMachineBase.h | 1 + .../Classes/AnimGraphNode_StateResult.h | 2 + .../Classes/AnimGraphNode_TransitionResult.h | 1 + .../Classes/AnimGraphNode_UseCachedPose.h | 1 + .../Private/AnimBlueprintCompiler.cpp | 1369 +++++++ .../AnimGraph/Private/AnimBlueprintCompiler.h | 181 + .../AnimBlueprintCompilerSubsystem.cpp | 116 + ...AnimBlueprintCompilerSubsystemCollection.h | 39 + .../AnimBlueprintCompilerSubsystem_Base.cpp | 1071 +++++ .../AnimBlueprintCompilerSubsystem_Base.h | 271 ++ ...mBlueprintCompilerSubsystem_CachedPose.cpp | 169 + ...nimBlueprintCompilerSubsystem_CachedPose.h | 38 + ...printCompilerSubsystem_LinkedAnimGraph.cpp | 15 + ...ueprintCompilerSubsystem_LinkedAnimGraph.h | 18 + ...lueprintCompilerSubsystem_StateMachine.cpp | 583 +++ ...mBlueprintCompilerSubsystem_StateMachine.h | 58 + .../AnimBlueprintNodeOptionalPinManager.cpp | 3 +- .../AnimGraph/Private/AnimGraphModule.cpp | 18 +- .../Private/AnimGraphNode_AssetPlayerBase.cpp | 50 + .../AnimGraph/Private/AnimGraphNode_Base.cpp | 35 +- .../Private/AnimGraphNode_CustomProperty.cpp | 63 + .../AnimGraphNode_LinkedAnimGraphBase.cpp | 23 + .../Private/AnimGraphNode_LinkedInputPose.cpp | 74 + .../AnimGraph/Private/AnimGraphNode_Root.cpp | 9 +- .../AnimGraphNode_StateMachineBase.cpp | 393 ++ .../Private/AnimGraphNode_StateResult.cpp | 8 + .../Private/AnimGraphNode_UseCachedPose.cpp | 37 + .../Private/AnimStateTransitionNode.cpp | 39 +- .../Public/AnimBlueprintCompilerSubsystem.h | 167 + .../AnimGraph/Public/IClassVariableCreator.h | 29 + .../Public/IPropertyAccessCompilerSubsystem.h | 36 + .../AnimGraphConnectionDrawingPolicy.cpp | 6 +- .../AnimationNodes/SAnimationGraphNode.cpp | 74 +- .../AnimationNodes/SAnimationGraphNode.h | 1 + .../SGraphNodeBlendSpacePlayer.cpp | 2 +- .../SGraphNodeSequencePlayer.cpp | 2 +- .../SGraphNodeAnimState.cpp | 2 +- .../SGraphNodeAnimTransition.cpp | 2 +- .../BlueprintGraph/Classes/EdGraphSchema_K2.h | 15 +- .../Classes/K2Node_CallParentFunction.h | 6 + .../Classes/K2Node_EditablePinBase.h | 3 + .../Classes/K2Node_FunctionEntry.h | 2 + .../Classes/K2Node_FunctionResult.h | 2 - .../Classes/K2Node_FunctionTerminator.h | 1 - .../Classes/K2Node_GetSubsystem.h | 2 +- .../Classes/K2Node_MakeStruct.h | 5 +- .../Classes/K2Node_SetFieldsInStruct.h | 2 +- .../Private/BlueprintActionDatabase.cpp | 4 +- .../Private/BlueprintActionFilter.cpp | 14 +- .../Private/EdGraphSchema_K2.cpp | 7 +- .../Private/K2Node_AddComponentByClass.cpp | 15 +- .../Private/K2Node_CallFunction.cpp | 26 +- .../Private/K2Node_CallParentFunction.cpp | 5 + .../Private/K2Node_ComponentBoundEvent.cpp | 19 +- .../Private/K2Node_EaseFunction.cpp | 2 + .../Private/K2Node_EditablePinBase.cpp | 8 + .../Private/K2Node_FormatText.cpp | 5 +- .../Private/K2Node_FunctionEntry.cpp | 30 + .../Private/K2Node_GetSubsystem.cpp | 28 +- .../Private/K2Node_MakeStruct.cpp | 23 +- .../Private/K2Node_SetFieldsInStruct.cpp | 2 +- .../Private/SplineComponentVisualizer.cpp | 244 +- .../Public/SplineComponentVisualizer.h | 15 +- .../ContentBrowser/Private/AssetViewTypes.cpp | 2 +- .../Private/ContentBrowserUtils.cpp | 2 +- .../ContentBrowser/Private/SFilterList.cpp | 1 + .../Private/ContentBrowserDataSubsystem.cpp | 2 + .../Private/RichCurveEditorModel.cpp | 2 +- .../Private/Tree/CurveEditorTree.cpp | 7 + .../CurveEditor/Public/Tree/CurveEditorTree.h | 5 + .../Private/ActorDetails.cpp | 2 +- .../Private/DetailCustomizations.cpp | 21 +- .../HardwareTargetingSettingsDetails.cpp | 2 +- .../EditorStyle/Private/SlateEditorStyle.cpp | 35 +- .../Private/EdModeInteractiveToolsContext.cpp | 16 +- .../FoliageEdit/Private/FoliageEdMode.cpp | 2 +- .../FoliageEdit/Private/SFoliageEdit.cpp | 2 +- .../Private/GameProjectUtils.cpp | 55 +- .../Public/GameProjectUtils.h | 5 + .../Private/GraphEditorActions.cpp | 2 +- .../GraphEditor/Private/SCommentBubble.cpp | 11 +- .../GraphEditor/Private/SGraphEditorImpl.cpp | 282 +- .../GraphEditor/Private/SGraphEditorImpl.h | 1 + .../Private/BPFunctionClipboardData.cpp | 91 + .../Kismet/Private/BPFunctionClipboardData.h | 50 + .../Private/BlueprintCompilationManager.cpp | 1 - .../Private/BlueprintDetailsCustomization.cpp | 18 +- .../Editor/Kismet/Private/BlueprintEditor.cpp | 108 +- .../Kismet/Private/BlueprintEditorModes.cpp | 20 +- .../Kismet/Private/BlueprintEditorModule.cpp | 76 +- .../Kismet/Private/FindInBlueprintManager.cpp | 20 +- .../Kismet/Private/FindInBlueprints.cpp | 25 +- .../Private/ISCSEditorUICustomization.cpp | 3 + .../Private/ReplaceNodeReferencesHelper.cpp | 139 + .../Private/SCSEditorExtensionContext.cpp | 3 + .../Editor/Kismet/Private/SMyBlueprint.cpp | 535 ++- .../Editor/Kismet/Private/SMyBlueprint.h | 29 + .../Kismet/Private/SReplaceNodeReferences.cpp | 777 +++- .../Kismet/Private/SReplaceNodeReferences.h | 197 +- .../Editor/Kismet/Private/SSCSEditor.cpp | 146 +- .../Kismet/Private/WatchPointViewer.cpp | 2 +- .../Editor/Kismet/Public/BlueprintEditor.h | 13 + .../Kismet/Public/BlueprintEditorModule.h | 25 +- .../Editor/Kismet/Public/FindInBlueprints.h | 8 + .../Kismet/Public/ISCSEditorUICustomization.h | 34 + .../Public/ReplaceNodeReferencesHelper.h | 89 + .../Kismet/Public/SCSEditorExtensionContext.h | 22 + .../Source/Editor/Kismet/Public/SSCSEditor.h | 28 +- .../Private/AnimBlueprintCompiler.cpp | 3494 ----------------- .../Private/AnimBlueprintCompiler.h | 385 -- .../KismetCompiler/Private/KismetCompiler.cpp | 43 +- .../Private/KismetCompilerMisc.cpp | 113 +- .../Private/KismetCompilerModule.cpp | 2 - .../KismetCompiler/Public/KismetCompiler.h | 9 +- .../Public/KismetCompilerMisc.h | 2 + .../Private/SPinTypeSelector.cpp | 50 +- .../Private/LandscapeEdModeComponentTools.cpp | 9 +- .../Editor/LevelEditor/LevelEditor.Build.cs | 3 +- .../Private/ActorDetailsExtensionContext.cpp | 13 - .../LevelEditor/Private/LevelEditor.cpp | 31 +- .../Private/LevelEditorActions.cpp | 24 +- .../Private/LevelEditorToolBar.cpp | 94 +- .../LevelEditor/Private/SActorDetails.cpp | 38 +- .../LevelEditor/Private/SActorDetails.h | 7 +- .../LevelEditor/Private/SLevelEditor.cpp | 61 +- .../Editor/LevelEditor/Private/SLevelEditor.h | 16 +- .../Public/ActorDetailsExtensionContext.h | 21 - .../Editor/LevelEditor/Public/ILevelEditor.h | 3 + .../LevelEditor/Public/LevelEditorActions.h | 7 +- .../Private/Frame/MainFrameHandler.cpp | 4 +- .../MaterialEditor/Private/FindInMaterial.cpp | 14 +- .../MaterialEditor/Private/MaterialEditor.cpp | 195 +- .../MaterialEditor/Private/MaterialEditor.h | 14 +- .../Private/MaterialEditorActions.cpp | 2 +- .../MaterialEditor/Private/MaterialStats.cpp | 2 +- .../Private/MaterialStatsCommon.cpp | 14 +- .../Private/SMaterialLayersFunctionsTree.cpp | 108 +- .../SMaterialParametersOverviewWidget.cpp | 63 +- .../MaterialEditor/Public/FindInMaterial.h | 3 + .../MaterialEditor/Public/IMaterialEditor.h | 5 + .../Public/MaterialEditorActions.h | 2 +- .../MeshPaint/Private/IMeshPaintMode.cpp | 4 + .../Channels/BuiltInChannelEditors.cpp | 121 +- .../Private/FCPXML/FCPXMLExport.cpp | 34 +- .../Private/FCPXML/FCPXMLExport.h | 20 +- .../Private/FCPXML/FCPXMLMetadataExport.cpp | 331 ++ .../Private/FCPXML/FCPXMLMetadataExport.h | 32 + .../FCPXML/FCPXMLMovieSceneTranslator.cpp | 19 +- .../Private/MovieSceneEventUtils.cpp | 62 +- .../Private/MovieSceneToolHelpers.cpp | 87 +- .../Private/MovieSceneToolsModule.cpp | 121 +- .../Private/TrackEditors/AudioTrackEditor.cpp | 7 +- .../TrackEditors/MaterialTrackEditor.cpp | 47 +- .../EulerTransformPropertyTrackEditor.cpp | 8 +- .../FloatPropertyTrackEditor.cpp | 4 +- .../TransformPropertyTrackEditor.cpp | 2 +- .../TrackEditors/TransformTrackEditor.cpp | 35 +- .../FCPXML/FCPXMLMovieSceneTranslator.h | 4 +- .../Public/MovieSceneExportMetadata.h | 39 + .../Public/MovieSceneToolHelpers.h | 9 +- .../Public/MovieSceneToolsModule.h | 3 +- .../Public/MovieSceneTranslator.h | 4 +- .../Public/TrackEditors/MaterialTrackEditor.h | 3 +- Engine/Source/Editor/Persona/Persona.Build.cs | 6 +- .../Persona/Private/AnimGraphNodeDetails.cpp | 408 +- .../Persona/Private/AnimGraphNodeDetails.h | 2 +- .../Editor/Persona/Private/PersonaModule.cpp | 2 +- .../Private/PhysicsAssetEditor.cpp | 19 + .../Private/PhysicsAssetEditor.h | 6 + .../Private/PhysicsAssetEditorEditMode.cpp | 34 +- .../Private/PhysicsAssetEditorSharedData.cpp | 7 + .../Private/PhysicsAssetEditorSharedData.h | 9 +- ...hysicsAssetEditorSkeletalMeshComponent.cpp | 27 +- .../PhysicsAssetEditorSkeletalMeshComponent.h | 1 + .../Private/DetailPropertyRow.cpp | 10 + .../Private/DetailPropertyRow.h | 2 + .../Private/EditConditionContext.cpp | 26 +- .../PropertyEditor/Private/PropertyNode.cpp | 50 +- .../Public/IDetailPropertyRow.h | 18 +- .../Private/AnimationRecorder.cpp | 14 +- .../Private/SequenceRecorder.cpp | 4 +- .../Public/AnimationRecorder.h | 15 +- .../Private/DisplayNodes/SequencerRootNode.h | 4 +- .../LevelEditorSequencerIntegration.cpp | 15 + .../Editor/Sequencer/Private/SSequencer.cpp | 26 - .../Sequencer/Private/SSequencerSection.cpp | 25 +- .../Editor/Sequencer/Private/Sequencer.cpp | 102 +- .../Editor/Sequencer/Private/Sequencer.h | 6 + .../Private/SequencerAddKeyOperation.cpp | 26 +- .../Private/SequencerContextMenus.cpp | 260 +- .../Sequencer/Private/SequencerContextMenus.h | 7 + .../Sequencer/Private/SequencerModule.cpp | 5 + .../Sequencer/Private/SequencerNodeTree.cpp | 91 +- .../Sequencer/Private/SequencerNodeTree.h | 9 + .../Private/SequencerSelectionCurveFilter.h | 2 +- .../Sequencer/Private/SequencerSettings.cpp | 15 + .../Editor/Sequencer/Public/ISequencer.h | 8 +- .../Public/LevelEditorSequencerIntegration.h | 6 + .../Public/SequencerAddKeyOperation.h | 15 + .../Sequencer/Public/SequencerSettings.h | 9 + .../Private/SStaticMeshEditorViewport.cpp | 22 +- .../Private/SStaticMeshEditorViewport.h | 1 - .../Private/StaticMeshEditor.cpp | 94 +- .../Models/TextureEditorViewportClient.cpp | 80 +- .../Models/TextureEditorViewportClient.h | 4 + .../Private/TextureEditorToolkit.cpp | 38 +- .../Widgets/STextureEditorViewport.cpp | 3 +- .../Sequencer2DTransformTrackEditor.cpp | 2 +- .../UMGDetailCustomizations.cpp | 388 +- .../Customizations/UMGDetailCustomizations.h | 3 + .../Private/Designer/SDesignerView.cpp | 14 +- .../Details/DetailWidgetExtensionHandler.cpp | 5 +- .../Private/Details/SPropertyBinding.cpp | 795 ---- .../Private/Details/SPropertyBinding.h | 81 - .../Private/WidgetBlueprintEditor.cpp | 71 +- .../Editor/UMGEditor/UMGEditor.Build.cs | 2 +- .../ConvertLevelsToExternalActorsCommandlet.h | 6 + .../Commandlets/ResavePackagesCommandlet.h | 3 +- .../CookOnTheSide/CookOnTheFlyServer.h | 7 +- .../UnrealEd/Classes/Editor/EditorEngine.h | 65 +- .../UnrealEd/Classes/Factories/Factory.h | 2 +- .../ReimportFbxAnimSequenceFactory.h | 1 + .../ReimportFbxSkeletalMeshFactory.h | 1 + .../Factories/ReimportFbxStaticMeshFactory.h | 1 + .../Factories/ReimportTextureFactory.h | 4 + .../TextureRenderTargetVolumeFactoryNew.h | 39 + .../Settings/EditorExperimentalSettings.h | 4 - .../Settings/LevelEditorMiscSettings.h | 4 + .../Settings/LevelEditorViewportSettings.h | 16 + .../UnrealEd/Private/AssetDeleteModel.cpp | 14 +- .../Commandlets/AssetRegistryGenerator.cpp | 2 +- .../Commandlets/ContentCommandlets.cpp | 87 +- ...onvertLevelsToExternalActorsCommandlet.cpp | 305 +- .../Private/Commandlets/CookCommandlet.cpp | 2 +- .../ShaderPipelineCacheToolsCommandlet.cpp | 241 +- .../Private/ComponentTypeRegistry.cpp | 2 +- .../Private/ComponentVisualizerManager.cpp | 36 +- .../UnrealEd/Private/CookOnTheFlyServer.cpp | 140 +- .../Source/Editor/UnrealEd/Private/Editor.cpp | 329 +- .../Editor/UnrealEd/Private/EditorActor.cpp | 9 +- .../UnrealEd/Private/EditorBuildUtils.cpp | 2 +- .../Editor/UnrealEd/Private/EditorEngine.cpp | 52 +- .../Editor/UnrealEd/Private/EditorObject.cpp | 24 +- .../UnrealEd/Private/EditorViewportClient.cpp | 16 +- .../Private/Factories/EditorFactories.cpp | 60 +- .../UnrealEd/Private/Fbx/FbxFactory.cpp | 5 +- .../UnrealEd/Private/Fbx/FbxMainExport.cpp | 134 +- .../Editor/UnrealEd/Private/FbxExporter.h | 2 +- .../Editor/UnrealEd/Private/FileHelpers.cpp | 9 +- .../UnrealEd/Private/HierarchicalLOD.cpp | 6 +- .../Private/Kismet2/BlueprintEditorUtils.cpp | 133 +- .../ChildActorComponentEditorUtils.cpp | 16 +- .../Private/Kismet2/DebuggerCommands.cpp | 2 +- .../Private/Kismet2/KismetDebugUtilities.cpp | 50 +- .../Private/Lightmass/LightmassRender.cpp | 10 +- .../Editor/UnrealEd/Private/MaterialGraph.cpp | 2 +- .../Editor/UnrealEd/Private/ObjectTools.cpp | 199 +- .../Editor/UnrealEd/Private/PackageTools.cpp | 4 +- .../Editor/UnrealEd/Private/PlayLevel.cpp | 115 +- .../Private/Settings/SettingsClasses.cpp | 5 + .../UnrealEd/Private/ThumbnailManager.cpp | 9 +- .../Editor/UnrealEd/Private/UnrealEdMisc.cpp | 17 +- .../UnrealEd/Public/EditorReimportHandler.h | 24 +- .../UnrealEd/Public/IPropertyAccessCompiler.h | 43 + .../UnrealEd/Public/IPropertyAccessEditor.h | 207 + .../Public/Kismet2/BlueprintEditorUtils.h | 41 +- .../Kismet2/ChildActorComponentEditorUtils.h | 21 +- .../Public/Kismet2/KismetDebugUtilities.h | 4 +- .../UnrealEd/Public/MouseDeltaTracker.h | 16 +- .../Public/Settings/EditorProjectSettings.h | 12 - .../Source/Editor/UnrealEd/UnrealEd.Build.cs | 1 + ...untimeVirtualTextureDetailsCustomization.h | 2 + ...RuntimeVirtualTextureBuildMinMaxHeight.cpp | 22 +- ...untimeVirtualTextureBuildStreamingMips.cpp | 21 +- ...timeVirtualTextureDetailsCustomization.cpp | 16 +- .../RuntimeVirtualTextureSetBounds.cpp | 1 + .../WorldBrowser/Private/SWorldDetails.cpp | 38 +- .../WorldBrowser/Private/SWorldDetails.h | 2 + .../Tiles/WorldTileCollectionModel.cpp | 5 + .../AutomationUtils/BundleUtils.cs | 19 + .../AutomationUtils/DeviceReservation.cs | 13 +- .../AutomationUtils/MCPPublic.cs | 43 + .../AutomationUtils/ProjectParams.cs | 21 +- .../AutomationTool/AutomationUtils/Utils.cs | 6 +- .../Framework/Base/Gauntlet.BaseTestNode.cs | 5 + .../Framework/Base/Gauntlet.TestNode.cs | 5 + .../Framework/Devices/Gauntlet.DevicePool.cs | 21 +- .../Framework/Gauntlet.TestExecutor.cs | 17 +- .../Gauntlet.SelfTest.OrderOfOpsTest.cs | 11 + .../SelfTest/Gauntlet.SelfTest.BaseNode.cs | 15 +- .../Unreal/Base/Gauntlet.UnrealPGONode.cs | 57 +- .../Base/Gauntlet.UnrealTestConfiguration.cs | 16 + .../Unreal/Base/Gauntlet.UnrealTestNode.cs | 3 + .../Scripts/BuildPhysX.Automation.cs | 187 +- .../Scripts/CleanAutomationReports.cs | 87 +- .../CopyBuildToStagingDirectory.Automation.cs | 64 +- .../Scripts/RunProjectCommand.Automation.cs | 12 +- .../AutomationToolLauncher/Launcher.cs | 56 +- .../Programs/BuildAgent/BuildAgent.csproj | 1 + .../BuildAgent/Issues/DumpIssuesMode.cs | 83 + .../Programs/BuildAgent/Issues/Matcher.cs | 8 +- .../Issues/Matchers/ContentIssueMatcher.cs | 2 +- .../Programs/BuildAgent/Run/LineBuffer.cs | 2 +- .../Source/Programs/BuildAgent/Run/RunMode.cs | 3 +- .../BuildAgent/Workspace/Common/Repository.cs | 8 +- .../BuildAgent/Workspace/PopulateCacheMode.cs | 17 +- .../Programs/CSVTools/CsvStats/CsvStats.cs | 2 +- .../BuildUtilities/UEBuildPlatformSDK.cs | 2 +- .../DotNETUtilities/BinaryArchiveWriter.cs | 2 +- .../DotNETUtilities/DirectoryReference.cs | 8 + .../DotNETUtilities/FileReference.cs | 19 +- .../DatasmithCADWorker.Target.cs | 2 +- .../DatasmithFacadeCSharp.Target.cs | 1 + .../DatasmithMax2017.Target.cs | 1 + .../DatasmithRevit2018.Target.cs | 1 + .../DatasmithSketchUp2017.Target.cs | 1 + .../GeometryCollectionTestClustering.cpp | 102 +- ...metryCollectionTestCollisionResolution.cpp | 51 +- .../GeometryCollectionTestFramework.cpp | 4 +- .../GeometryCollectionTestSimulation.cpp | 8 +- .../GeometryCollectionTestSimulationField.cpp | 7 +- ...GeometryCollectionTestSimulationSolver.cpp | 98 +- .../Private/HeadlessChaosTestBroadphase.cpp | 97 - .../Private/HeadlessChaosTestCollisions.cpp | 2 - .../HeadlessChaosTestDataMarshalling.cpp | 73 + .../Private/HeadlessChaosTestEPA.cpp | 416 +- .../Private/HeadlessChaosTestGraph.cpp | 2 +- .../Private/HeadlessChaosTestImplicits.cpp | 8 +- .../Private/HeadlessChaosTestPhysicsScene.cpp | 505 +++ .../Private/HeadlessChaosTestRewind.cpp | 19 +- .../Private/HeadlessChaosTestSim.cpp | 62 +- .../Private/HeadlessChaosTestUtility.cpp | 10 + .../IncludeTool/CompileEnvironment.cs | 9 +- .../Programs/IncludeTool/IncludeTool/Rules.cs | 2 - .../LiveCodingConsole/Private/SLogWidget.cpp | 3 + .../Configuration/UEBuildPlatform.cs | 9 + .../Configuration/UEBuildTarget.cs | 64 +- .../UnrealBuildTool/Modes/BuildMode.cs | 23 +- .../UnrealBuildTool/Modes/CleanMode.cs | 1 - .../Modes/GenerateClangDatabase.cs | 10 + .../Platform/Android/AndroidPlatformSDK.cs | 2 +- .../Platform/IOS/UEBuildIOS.cs | 2 +- .../Platform/Linux/LinuxPlatformSDK.cs | 2 +- .../Platform/Lumin/LuminPlatformSDK.cs | 2 +- .../Platform/Mac/MacToolChain.cs | 2 +- .../Platform/Mac/UEBuildMac.cs | 20 +- .../Platform/Windows/UEBuildWindows.cs | 17 + .../Platform/Windows/VCEnvironment.cs | 2 +- .../Platform/Windows/VCToolChain.cs | 11 +- .../ProjectFiles/ProjectFileGenerator.cs | 41 +- .../VSCodeProjectFileGenerator.cs | 333 +- .../ProjectFiles/Xcode/XcodeProject.cs | 120 +- .../Xcode/XcodeProjectFileGenerator.cs | 73 +- .../UnrealBuildTool/System/ActionHistory.cs | 329 +- .../System/DynamicCompilation.cs | 13 +- .../UnrealBuildTool/System/HotReload.cs | 7 +- .../UnrealBuildTool/System/PlatformExports.cs | 10 + .../UnrealBuildTool/UnrealBuildTool.cs | 7 +- .../UnrealGameSync/Automation/P4Automation.cs | 80 + .../Automation/VisualStudioAutomation.cs | 305 ++ .../UnrealGameSync/AutomationServer.cs | 279 +- .../UnrealGameSync/BuildStep.cs | 16 +- .../UnrealGameSync/ConfigFile.cs | 35 +- .../Controls/WorkspaceControl.Designer.cs | 10 + .../Controls/WorkspaceControl.cs | 110 +- .../UnrealGameSync/DeploymentSettings.cs | 21 + .../DetectProjectSettingsTask.cs | 11 + .../UnrealGameSync/EventMonitor.cs | 318 +- .../UnrealGameSync/FindFoldersToCleanTask.cs | 40 +- .../ApplicationSettingsWindow.Designer.cs | 119 +- .../Forms/ApplicationSettingsWindow.cs | 30 + .../Forms/AutomatedBuildWindow.Designer.cs | 357 ++ .../Forms/AutomatedBuildWindow.cs | 206 + .../Forms/AutomatedBuildWindow.resx | 120 + .../Forms/AutomatedSyncWindow.cs | 44 +- .../Forms/IssueBrowserWindow.cs | 6 +- .../Forms/IssueDetailsWindow.cs | 21 +- .../Forms/IssueSettingsWindow.Designer.cs | 510 +-- .../Forms/IssueSettingsWindow.cs | 28 +- .../UnrealGameSync/Forms/MainWindow.cs | 334 +- .../UnrealGameSync/IssueMonitor.cs | 59 +- .../UnrealGameSync/UnrealGameSync/Perforce.cs | 41 +- .../UnrealGameSync/PerforceMonitor.cs | 24 + .../UnrealGameSync/UnrealGameSync/Program.cs | 30 +- .../ProgramApplicationContext.cs | 13 +- .../UnrealGameSync/Properties/AssemblyInfo.cs | 4 +- .../UnrealGameSync/UnrealGameSync/RESTApi.cs | 44 +- .../UnrealGameSync/UnrealGameSync.csproj | 23 +- .../UnrealGameSync/UriHandlers/P4Handler.cs | 24 + .../UnrealGameSync/UriHandlers/UGSHandler.cs | 75 + .../UnrealGameSync/UriHandlers/UriHandler.cs | 407 ++ .../UriHandlers/VisualStudioHandler.cs | 35 + .../UnrealGameSync/UserSettings.cs | 13 +- .../UnrealGameSync/Workspace.cs | 5 +- .../UnrealGameSyncLauncher.csproj | 3 +- .../Private/ClassDeclarationMetaData.cpp | 6 + .../UnrealHeaderTool/Private/ClassMaps.h | 1 + .../Private/CodeGenerator.cpp | 4 + .../Specifiers/ClassMetadataSpecifiers.def | 1 + .../CompileTimeAnalyzer/MainWindow.xaml | 6 +- .../CompileTimeAnalyzer/MainWindow.xaml.cs | 76 +- .../AIModule/Classes/BehaviorTree/BTNode.h | 4 +- .../Classes/BehaviorTree/BehaviorTreeTypes.h | 77 +- .../Blueprint/AIBlueprintHelperLibrary.h | 12 + .../Navigation/PathFollowingComponent.h | 2 +- .../BehaviorTree/BehaviorTreeComponent.cpp | 222 +- .../BehaviorTree/BehaviorTreeTypes.cpp | 148 + .../Blueprint/AIBlueprintHelperLibrary.cpp | 46 + .../GameplayDebuggerCategory_Navmesh.cpp | 53 +- .../Microsoft/AVEncoderMicrosoftCommon.h | 1 - .../Analytics/Public/AnalyticsConversion.h | 28 +- .../Private/AnalyticsProviderETEventCache.cpp | 24 +- .../Private/IAnalyticsProviderET.cpp | 37 +- .../AnalyticsET/Public/AnalyticsET.h | 8 +- .../AnimNodes/AnimNode_PoseHandler.cpp | 2 +- .../BoneControllers/AnimNode_RigidBody.cpp | 77 +- .../Public/AnimCustomInstanceHelper.h | 8 + .../BoneControllers/AnimNode_RigidBody.h | 91 +- .../Apple/MetalRHI/Private/MetalBuffer.cpp | 67 +- .../MetalRHI/Private/MetalCommandQueue.cpp | 2 +- .../Apple/MetalRHI/Private/MetalCommands.cpp | 11 +- .../Apple/MetalRHI/Private/MetalContext.cpp | 54 +- .../Apple/MetalRHI/Private/MetalContext.h | 14 +- .../MetalRHI/Private/MetalIndexBuffer.cpp | 15 - .../Apple/MetalRHI/Private/MetalRHIPrivate.h | 15 +- .../MetalRHI/Private/MetalRenderPass.cpp | 47 +- .../MetalRHI/Private/MetalStateCache.cpp | 24 +- .../Private/MetalStructuredBuffer.cpp | 31 +- .../Apple/MetalRHI/Private/MetalTexture.cpp | 3 +- .../Apple/MetalRHI/Private/MetalUAV.cpp | 2 +- .../Private/MetalUniformBufferCommands.cpp | 4 - .../MetalRHI/Private/MetalVertexBuffer.cpp | 692 ++-- .../Apple/MetalRHI/Private/MetalViewport.cpp | 10 +- .../Apple/MetalRHI/Public/MetalResources.h | 91 +- .../ApplicationCore/ApplicationCore.Build.cs | 6 +- .../Android/AndroidPlatformFramePacer.cpp | 9 +- .../Private/IOS/IOSAppDelegate.cpp | 3 + .../IOS/IOSBackgroundURLSessionHandler.cpp | 48 +- .../IOS/IOSPaymentTransactionObserver.cpp | 115 + .../Private/IOS/IOSPlatformFramePacer.cpp | 14 +- .../Private/Windows/WindowsApplication.cpp | 9 +- .../Android/AndroidPlatformFramePacer.h | 6 +- .../IOS/IOSBackgroundURLSessionHandler.h | 18 +- .../IOS/IOSPaymentTransactionObserver.h | 60 + .../AssetRegistry/Public/AssetRegistryState.h | 10 + .../AudioExtensions/Public/IAudioModulation.h | 2 + .../AudioMixer/Private/AudioMixerBus.cpp | 32 +- .../AudioMixer/Private/AudioMixerSource.cpp | 42 +- .../AudioMixer/Private/AudioMixerSource.h | 3 + .../Private/AudioMixerSourceManager.cpp | 147 +- .../Private/AudioMixerSourceManager.h | 5 + .../Private/AudioMixerSourceVoice.cpp | 19 + .../Private/AudioMixerSourceVoice.h | 3 + .../AudioMixer/Private/SoundWaveDecoder.cpp | 8 + .../AudioMixer/Public/SoundWaveDecoder.h | 2 +- Engine/Source/Runtime/Core/Core.Build.cs | 3 +- .../Android/AndroidErrorOutputDevice.cpp | 19 + .../Private/Android/AndroidPlatformMisc.cpp | 212 +- .../Core/Private/Android/AndroidSignals.h | 2 +- .../Core/Private/Android/AndroidStats.cpp | 91 +- .../Runtime/Core/Private/Async/TaskGraph.cpp | 45 +- .../Containers/BackgroundableTicker.cpp | 3 + .../Private/FramePro/FrameProProfiler.cpp | 2 +- .../GenericPlatformIoDispatcher.cpp | 172 +- .../GenericPlatformIoDispatcher.h | 51 +- .../GenericPlatform/GenericPlatformMemory.cpp | 5 + .../GenericPlatform/GenericPlatformMisc.cpp | 24 +- .../Core/Private/HAL/ConsoleManager.cpp | 8 +- .../IPlatformFileManagedStorageWrapper.cpp | 67 + .../Core/Private/HAL/LowLevelMemTracker.cpp | 34 +- .../Core/Private/HAL/PlatformMemory.cpp | 41 + .../Runtime/Core/Private/IO/IoDispatcher.cpp | 228 +- .../Private/IO/IoDispatcherFileBackend.cpp | 648 ++- .../Core/Private/IO/IoDispatcherFileBackend.h | 161 +- .../Private/IO/IoDispatcherFileBackendTypes.h | 226 ++ .../Core/Private/IO/IoDispatcherPrivate.h | 4 +- .../Runtime/Core/Private/IO/IoStore.cpp | 71 +- .../Core/Private/IOS/IOSPlatformMisc.cpp | 31 +- .../Private/Internationalization/Text.cpp | 61 +- .../Internationalization/TextFormatter.cpp | 34 + .../Internationalization/TextHistory.cpp | 186 +- .../Internationalization/TextHistory.h | 20 + .../Private/Internationalization/TextKey.cpp | 148 +- .../TextLocalizationManager.cpp | 19 +- .../TextLocalizationResource.cpp | 42 +- .../Math/BasicMathExpressionEvaluator.cpp | 6 +- .../Runtime/Core/Private/MemPro/MemPro.cpp | 2 + .../Core/Private/Misc/ConfigCacheIni.cpp | 29 +- .../Runtime/Core/Private/Misc/CoreMisc.cpp | 22 - .../Misc/DataDrivenPlatformInfoRegistry.cpp | 54 +- .../Private/Misc/EmbeddedCommunication.cpp | 3 + .../ProfilingDebugging/CsvProfiler.cpp | 32 +- .../Windows/AQtimeExternalProfiler.cpp | 8 +- .../Private/Serialization/MemoryImage.cpp | 236 +- .../Private/Tests/Async/TaskGraphTest.cpp | 22 +- .../Tests/Internationalization/TextTest.cpp | 56 + .../Core/Private/Tests/Misc/AsciiSetTest.cpp | 13 + .../Core/Private/UObject/UnrealNames.cpp | 21 + .../Core/Public/Android/AndroidPlatformMisc.h | 13 +- .../Core/Public/Android/AndroidStats.h | 5 +- .../Core/Public/Async/TaskGraphInterfaces.h | 2 + .../Runtime/Core/Public/Containers/BitArray.h | 4 +- .../Core/Public/Containers/StringConv.h | 27 +- .../GenericPlatform/GenericPlatformMemory.h | 13 + .../GenericPlatform/GenericPlatformMisc.h | 25 +- .../HAL/IPlatformFileManagedStorageWrapper.h | 875 +++++ .../Public/HAL/IPlatformFileOpenLogWrapper.h | 10 +- .../Core/Public/HAL/LowLevelMemTracker.h | 4 +- .../Source/Runtime/Core/Public/HAL/Platform.h | 8 + .../Runtime/Core/Public/HAL/PlatformMemory.h | 27 + .../Runtime/Core/Public/IO/IoDispatcher.h | 18 +- .../Runtime/Core/Public/IOS/IOSPlatformMisc.h | 4 +- .../Core/Public/Internationalization/Text.h | 43 +- .../Public/Internationalization/TextKey.h | 120 +- .../TextLocalizationManager.h | 5 + .../TextLocalizationResourceVersion.h | 6 +- .../Runtime/Core/Public/Mac/MacPlatformMath.h | 6 +- .../Runtime/Core/Public/Mac/MacPlatformMisc.h | 1 - .../Runtime/Core/Public/Math/GenericOctree.h | 8 + Engine/Source/Runtime/Core/Public/Math/Quat.h | 10 + .../Core/Public/Math/TransformNonVectorized.h | 7 +- .../Core/Public/Math/TransformVectorized.h | 7 +- .../Runtime/Core/Public/Misc/AsciiSet.h | 9 +- .../Runtime/Core/Public/Misc/Attribute.h | 37 + .../Source/Runtime/Core/Public/Misc/Build.h | 11 + .../Runtime/Core/Public/Misc/ConfigCacheIni.h | 1 + .../Runtime/Core/Public/Misc/CoreMisc.h | 45 +- .../Misc/DataDrivenPlatformInfoRegistry.h | 16 + .../Runtime/Core/Public/Misc/NetworkVersion.h | 1 + .../Public/ProfilingDebugging/CsvProfiler.h | 3 + .../Core/Public/Serialization/MemoryImage.h | 8 +- .../Public/Serialization/MemoryImageWriter.h | 4 + .../Core/Public/Serialization/MemoryLayout.h | 3 + .../ExternalPhysicsCustomObjectVersion.h | 3 + .../UObject/FortniteMainBranchObjectVersion.h | 6 + .../Runtime/Core/Public/UObject/NameTypes.h | 82 + .../Core/Public/UObject/PropertyPortFlags.h | 31 +- .../Core/Public/Unix/UnixPlatformMath.h | 2 + .../Core/Public/Unix/UnixPlatformMisc.h | 1 - .../Private/Blueprint/BlueprintSupport.cpp | 50 +- .../Private/Serialization/AsyncLoading.cpp | 9 - .../Private/Serialization/AsyncLoading2.cpp | 126 +- .../Private/Serialization/BulkData2.cpp | 13 +- .../Private/UObject/EnumProperty.cpp | 19 + .../Private/UObject/LinkerLoad.cpp | 17 +- .../CoreUObject/Private/UObject/Obj.cpp | 10 + .../CoreUObject/Private/UObject/Property.cpp | 38 +- .../Private/UObject/PropertyArray.cpp | 9 + .../Private/UObject/PropertyBaseObject.cpp | 39 +- .../Private/UObject/PropertyHelper.h | 8 +- .../Private/UObject/PropertyMap.cpp | 14 + .../Private/UObject/PropertySet.cpp | 9 + .../Private/UObject/SavePackage.cpp | 2 +- .../Private/UObject/ScriptCore.cpp | 161 +- .../Private/UObject/UObjectHash.cpp | 40 +- .../Public/Serialization/AsyncLoading2.h | 1 + .../Public/Serialization/AsyncPackageLoader.h | 11 + .../CoreUObject/Public/UObject/CoreNet.h | 2 + .../CoreUObject/Public/UObject/EnumProperty.h | 2 + .../CoreUObject/Public/UObject/Field.h | 6 + .../Public/UObject/FieldIterator.h | 136 +- .../CoreUObject/Public/UObject/FieldPath.h | 7 +- .../CoreUObject/Public/UObject/Object.h | 10 + .../CoreUObject/Public/UObject/ObjectKey.h | 48 + .../CoreUObject/Public/UObject/ObjectMacros.h | 7 +- .../CoreUObject/Public/UObject/Script.h | 93 +- .../CoreUObject/Public/UObject/Stack.h | 4 +- .../CoreUObject/Public/UObject/UnrealType.h | 5 +- .../Private/Android/AndroidErrorReport.cpp | 76 +- .../Private/CrashDescription.cpp | 3 +- .../Public/Android/AndroidErrorReport.h | 15 +- .../CrashReportCore/Public/CrashDescription.h | 5 + .../Runtime/D3D12RHI/Private/D3D12Adapter.cpp | 7 +- .../D3D12RHI/Private/D3D12CommandContext.cpp | 28 +- .../D3D12RHI/Private/D3D12CommandContext.h | 10 + .../D3D12RHI/Private/D3D12CommandList.cpp | 63 +- .../D3D12RHI/Private/D3D12CommandList.h | 14 +- .../D3D12RHI/Private/D3D12Commands.cpp | 2 + .../Runtime/D3D12RHI/Private/D3D12Device.cpp | 3 + .../Runtime/D3D12RHI/Private/D3D12Device.h | 7 + .../Private/D3D12DirectCommandListManager.cpp | 80 +- .../D3D12RHI/Private/D3D12RHIPrivate.h | 1 + .../D3D12RHI/Private/D3D12RayTracing.cpp | 5 +- .../D3D12RHI/Private/D3D12RenderTarget.cpp | 21 +- .../D3D12RHI/Private/D3D12Resources.cpp | 57 + .../Runtime/D3D12RHI/Private/D3D12Stats.h | 1 + .../Runtime/D3D12RHI/Private/D3D12Texture.h | 9 + .../Private/D3D12TimedIntervalQuery.cpp | 280 ++ .../Private/D3D12TimedIntervalQuery.h | 65 + .../Runtime/D3D12RHI/Private/D3D12Util.cpp | 56 +- .../Private/Windows/WindowsD3D12Device.cpp | 1 + .../Runtime/D3D12RHI/Public/D3D12RHIBridge.h | 12 + .../Runtime/D3D12RHI/Public/D3D12Resources.h | 95 +- .../Classes/AI/Navigation/NavigationTypes.h | 1 + .../Animation/AnimBlueprintClassSubsystem.h | 40 + .../Animation/AnimBlueprintGeneratedClass.h | 48 +- .../Engine/Classes/Animation/AnimClassData.h | 76 +- .../Classes/Animation/AnimClassInterface.h | 23 +- .../Engine/Classes/Animation/AnimInstance.h | 6 + .../Engine/Classes/Animation/AnimNodeBase.h | 120 +- .../Animation/AnimNode_TransitionResult.h | 2 +- .../Components/DirectionalLightComponent.h | 7 + .../Classes/Components/PrimitiveComponent.h | 11 + .../RuntimeVirtualTextureComponent.h | 2 + .../Components/SkeletalMeshComponent.h | 41 +- .../Classes/Components/SplineComponent.h | 20 +- .../Components/VolumetricCloudComponent.h | 4 +- .../Engine/Classes/Engine/ActorChannel.h | 9 +- .../Engine/Classes/Engine/AssetManager.h | 25 +- .../Classes/Engine/BlueprintGeneratedClass.h | 5 +- .../Runtime/Engine/Classes/Engine/Channel.h | 5 +- .../Engine/Classes/Engine/ChildConnection.h | 2 +- .../Engine/Classes/Engine/DemoNetConnection.h | 56 +- .../Engine/Classes/Engine/DemoNetDriver.h | 697 +--- .../Runtime/Engine/Classes/Engine/Engine.h | 36 +- .../Engine/Classes/Engine/EngineTypes.h | 4 + .../Engine/Classes/Engine/GameEngine.h | 6 + .../Engine/InheritableComponentHandler.h | 2 +- .../Runtime/Engine/Classes/Engine/Level.h | 3 + .../Engine/Classes/Engine/NetConnection.h | 110 +- .../Runtime/Engine/Classes/Engine/NetDriver.h | 10 +- .../Engine/Classes/Engine/PendingNetGame.h | 15 +- .../Engine/Classes/Engine/RendererSettings.h | 2 +- .../Runtime/Engine/Classes/Engine/Scene.h | 14 + .../Engine/Classes/Engine/StaticMesh.h | 5 + .../Engine/TextureRenderTargetVolume.h | 113 + .../Classes/Engine/ViewportStatsSubsystem.h | 145 + .../Runtime/Engine/Classes/Engine/World.h | 28 +- .../Engine/Classes/GameFramework/Actor.h | 9 +- .../Engine/Classes/GameFramework/Character.h | 12 +- .../Engine/Classes/GameFramework/Controller.h | 15 +- .../Classes/GameFramework/GameSession.h | 2 +- .../Classes/GameFramework/GameStateBase.h | 1 + .../Classes/GameFramework/PlayerController.h | 17 +- .../Engine/Classes/GameFramework/Volume.h | 2 +- .../Classes/GameFramework/WorldSettings.h | 9 +- .../Engine/Classes/Materials/Material.h | 17 +- ...pressionVolumetricAdvancedMaterialOutput.h | 4 +- .../Classes/Materials/MaterialInstance.h | 10 +- .../Classes/Materials/MaterialInterface.h | 10 +- .../Materials/MaterialLayersFunctions.h | 85 +- .../Classes/PhysicsEngine/BodyInstance.h | 12 + .../Engine/Classes/PhysicsEngine/BodySetup.h | 1 + .../PhysicsEngine/ConstraintInstance.h | 22 +- .../PhysicsEngine/PhysicsHandleComponent.h | 15 + .../Engine/Classes/Sound/AudioVolume.h | 42 + .../Sound/SoundModulationDestination.h | 14 +- .../Classes/VT/RuntimeVirtualTextureVolume.h | 2 +- Engine/Source/Runtime/Engine/Engine.Build.cs | 18 +- .../Private/AI/Navigation/NavigationTypes.cpp | 3 +- .../Runtime/Engine/Private/ActiveSound.cpp | 43 + .../Source/Runtime/Engine/Private/Actor.cpp | 79 +- .../Runtime/Engine/Private/ActorEditor.cpp | 8 + .../Runtime/Engine/Private/AnimBlueprint.cpp | 9 + .../Animation/AnimBlueprintClassSubsystem.cpp | 18 + .../Animation/AnimBlueprintGeneratedClass.cpp | 78 +- .../Private/Animation/AnimClassData.cpp | 88 +- .../Private/Animation/AnimClassInterface.cpp | 25 + .../Engine/Private/Animation/AnimInstance.cpp | 29 + .../Private/Animation/AnimInstanceProxy.cpp | 22 +- .../Engine/Private/Animation/AnimNodeBase.cpp | 218 +- .../Animation/AnimNode_Inertialization.cpp | 19 +- .../Engine/Private/Animation/AnimSequence.cpp | 8 +- .../Engine/Private/Animation/AnimTrace.cpp | 2 +- .../Engine/Private/Animation/Skeleton.cpp | 6 +- .../Runtime/Engine/Private/AssetManager.cpp | 335 +- .../Runtime/Engine/Private/AudioDevice.cpp | 6 + .../Runtime/Engine/Private/AudioVolume.cpp | 10 + .../Private/BlueprintFunctionLibrary.cpp | 90 +- .../Private/BlueprintGeneratedClass.cpp | 52 +- .../Runtime/Engine/Private/Character.cpp | 7 +- .../Runtime/Engine/Private/ChartCreation.cpp | 4 + .../Private/Collision/WorldCollisionAsync.cpp | 20 +- .../Private/ComponentInstanceDataCache.cpp | 19 +- .../Private/Components/ActorComponent.cpp | 8 +- .../Components/CharacterMovementComponent.cpp | 21 +- .../Components/ChildActorComponent.cpp | 4 +- .../Components/DirectionalLightComponent.cpp | 10 + .../Private/Components/PrimitiveComponent.cpp | 2 + .../RuntimeVirtualTextureComponent.cpp | 11 + .../Private/Components/SceneComponent.cpp | 8 +- .../Components/SkeletalMeshComponent.cpp | 28 +- .../Private/Components/SkyLightComponent.cpp | 14 +- .../Private/Components/SplineComponent.cpp | 51 + .../Components/StaticMeshComponent.cpp | 3 +- .../Components/TextRenderComponent.cpp | 4 +- .../Runtime/Engine/Private/Controller.cpp | 68 +- .../Runtime/Engine/Private/DataChannel.cpp | 189 +- .../Engine/Private/DataReplication.cpp | 9 + .../Private/DebugViewModeMaterialProxy.cpp | 2 +- .../Private/DebugViewModeMaterialProxy.h | 2 +- .../Runtime/Engine/Private/DemoNetDriver.cpp | 2626 +++---------- .../Engine/Private/EdGraph/EdGraphPin.cpp | 3 + .../Private/Engine/ViewportStatsSubsystem.cpp | 138 + .../Runtime/Engine/Private/GameEngine.cpp | 79 +- .../Runtime/Engine/Private/GameInstance.cpp | 182 +- .../Engine/Private/GameUserSettings.cpp | 75 +- .../Engine/Private/GameViewportClient.cpp | 6 +- .../Engine/Private/GameplayStatics.cpp | 2 +- .../Private/HLOD/HLODEngineSubsystem.cpp | 75 +- .../Runtime/Engine/Private/HLODProxy.cpp | 7 +- .../Private/InheritableComponentHandler.cpp | 15 +- .../Engine/Private/KismetRenderingLibrary.cpp | 24 +- .../Engine/Private/KismetSystemLibrary.cpp | 12 +- .../Runtime/Engine/Private/LODActor.cpp | 18 +- .../Source/Runtime/Engine/Private/Level.cpp | 10 + .../Runtime/Engine/Private/LocalPlayer.cpp | 16 +- .../Materials/HLSLMaterialTranslator.cpp | 11 +- .../Engine/Private/Materials/Material.cpp | 774 ++-- .../Private/Materials/MaterialCachedData.cpp | 81 +- .../Private/Materials/MaterialExpressions.cpp | 13 +- .../Private/Materials/MaterialInstance.cpp | 418 +- .../Private/Materials/MaterialInterface.cpp | 3 +- .../Private/Materials/MaterialShader.cpp | 78 +- .../Private/Materials/MaterialShared.cpp | 50 +- .../Materials/MaterialUniformExpressions.cpp | 147 +- .../Materials/MaterialUniformExpressions.h | 41 +- .../Net/NetworkGranularMemoryLogging.cpp | 3 + .../Private/Net/ReplayPlaylistTracker.cpp | 50 +- .../Runtime/Engine/Private/NetConnection.cpp | 267 +- .../Runtime/Engine/Private/NetDriver.cpp | 186 +- .../Engine/Private/PackageMapClient.cpp | 10 +- .../StatelessConnectHandlerComponent.cpp | 13 +- .../Particles/ParticleGpuSimulation.cpp | 18 +- .../Private/PhysicsEngine/BodyInstance.cpp | 13 +- .../Private/PhysicsEngine/BodySetup.cpp | 49 + .../PhysicsEngine/ConstraintInstance.cpp | 21 +- .../Experimental/PhysInterface_Chaos.cpp | 63 +- .../Experimental/PhysScene_Chaos.cpp | 117 +- .../ImmediatePhysicsSimulation_Chaos.cpp | 2 +- .../PhysicsEngine/PhysicsHandleComponent.cpp | 189 +- .../Engine/Private/PlayerController.cpp | 26 +- .../Engine/Private/PrimitiveSceneProxy.cpp | 5 +- .../PrimitiveUniformShaderParameters.cpp | 5 +- .../ProfilingDebugging/HealthSnapshot.cpp | 14 +- .../Rendering/NaniteStreamingManager.cpp | 2 +- .../Runtime/Engine/Private/RepLayout.cpp | 81 +- .../Runtime/Engine/Private/ReplayHelper.cpp | 1956 +++++++++ .../Engine/Private/ReplayNetConnection.cpp | 299 ++ .../Engine/Private/ReplaySubsystem.cpp | 434 ++ .../Source/Runtime/Engine/Private/Scene.cpp | 6 + .../Runtime/Engine/Private/SceneView.cpp | 10 + .../Private/ShaderCompiler/ShaderCompiler.cpp | 23 +- .../Private/SimpleConstructionScript.cpp | 15 +- .../Private/SkeletalMeshComponentPhysics.cpp | 93 +- .../Engine/Private/SkeletalMeshLODModel.cpp | 6 +- .../Engine/Private/SkeletalMeshRenderData.cpp | 2 +- .../Engine/Private/SkeletalRenderGPUSkin.cpp | 2 +- .../Engine/Private/Slate/SceneViewport.cpp | 6 +- .../Private/SoundModulationDestination.cpp | 22 +- .../Runtime/Engine/Private/SoundWave.cpp | 32 +- .../Runtime/Engine/Private/StaticMesh.cpp | 51 +- .../Engine/Private/StaticMeshRender.cpp | 19 +- .../Engine/Private/Subsystems/Subsystem.cpp | 7 +- .../Engine/Private/SupportedRangeTypes.cpp | 7 + .../Runtime/Engine/Private/Texture2D.cpp | 9 +- .../Engine/Private/TextureDerivedDataTask.cpp | 12 +- .../Engine/Private/TextureLODSettings.cpp | 4 +- .../Engine/Private/TextureRenderTarget2D.cpp | 5 +- .../Private/TextureRenderTargetVolume.cpp | 399 ++ .../Engine/Private/TickTaskManager.cpp | 15 +- Engine/Source/Runtime/Engine/Private/URL.cpp | 11 +- .../Runtime/Engine/Private/UnrealEngine.cpp | 254 +- .../Runtime/Engine/Private/UnrealNetwork.cpp | 6 + .../Source/Runtime/Engine/Private/World.cpp | 86 +- .../Runtime/Engine/Public/ActiveSound.h | 2 + .../Engine/Public/Animation/AnimCurveTypes.h | 147 +- .../Animation/AnimInstanceSubsystemData.h | 12 + .../Runtime/Engine/Public/AudioDevice.h | 1 + .../Source/Runtime/Engine/Public/BonePose.h | 43 +- .../Runtime/Engine/Public/ChartCreation.h | 4 + .../Public/ComponentInstanceDataCache.h | 17 + .../ComputeFramework/ComputeKernelResource.h | 4 +- .../ComputeFramework/ComputeKernelShaderMap.h | 7 + .../Engine/Public/GeneratedCodeHelpers.h | 6 + .../Engine/Public/HLOD/HLODEngineSubsystem.h | 22 + .../Runtime/Engine/Public/IPropertyAccess.h | 56 + .../Runtime/Engine/Public/MaterialShared.h | 133 +- .../Runtime/Engine/Public/Net/RepLayout.h | 7 +- .../Engine/Public/Net/ReplayPlaylistTracker.h | 6 +- .../Runtime/Engine/Public/Net/UnrealNetwork.h | 26 + .../StatelessConnectHandlerComponent.h | 6 +- .../Public/Performance/LatencyMarkerModule.h | 2 + .../Performance/MaxTickRateHandlerModule.h | 2 + .../Physics/Experimental/PhysScene_Chaos.h | 5 +- .../Experimental/PhysicsUserData_Chaos.h | 10 +- .../ImmediatePhysicsSimulation_Chaos.h | 2 +- .../Engine/Public/PrimitiveSceneProxy.h | 8 + .../Public/PrimitiveUniformShaderParameters.h | 18 +- .../ProfilingDebugging/HealthSnapshot.h | 5 +- .../Public/Rendering/SkeletalMeshLODModel.h | 4 - .../Runtime/Engine/Public/ReplayHelper.h | 459 +++ .../Engine/Public/ReplayNetConnection.h | 68 + .../Runtime/Engine/Public/ReplaySubsystem.h | 104 + .../Runtime/Engine/Public/ReplayTypes.h | 532 +++ .../Runtime/Engine/Public/Scalability.h | 21 +- .../Runtime/Engine/Public/SceneInterface.h | 3 +- .../Runtime/Engine/Public/SceneManagement.h | 4 + .../Source/Runtime/Engine/Public/SceneTypes.h | 1 + .../Source/Runtime/Engine/Public/SceneView.h | 1 + .../Runtime/Engine/Public/SkeletalMeshTypes.h | 2 + .../Engine/Public/Subsystems/Subsystem.h | 3 + .../Engine/Public/SupportedRangeTypes.h | 11 + .../TextureRenderTargetVolumeResource.h | 112 + .../Runtime/Engine/Public/WorldCollision.h | 6 +- .../Private/Chaos/ChaosMarshallingManager.cpp | 77 +- .../Private/Chaos/CollisionResolution.cpp | 193 +- .../Private/Chaos/CollisionResolutionUtil.cpp | 74 +- .../Chaos/Evolution/PBDMinEvolution.cpp | 243 +- .../Chaos/Evolution/PBDMinEvolution.ispc | 297 ++ .../Chaos/Framework/PhysicsSolverBase.cpp | 67 +- .../Chaos/Private/Chaos/HeightField.cpp | 39 +- .../Private/Chaos/PBDCollisionConstraints.cpp | 13 +- .../Chaos/PBDCollisionConstraintsContact.cpp | 41 +- .../Private/Chaos/PBDConstraintGraph.cpp | 23 +- .../Private/Chaos/PBDJointConstraintData.cpp | 18 + .../Private/Chaos/PBDJointConstraints.cpp | 11 +- .../Private/Chaos/PBDRigidClustering.cpp | 38 +- .../Private/Chaos/PBDRigidsEvolution.cpp | 143 +- .../Private/Chaos/PBDRigidsEvolutionGBF.cpp | 8 +- .../Chaos/Public/Chaos/AABBTree.h | 10 +- .../Chaos/Public/Chaos/BoundingVolume.h | 3 +- .../Public/Chaos/ChaosMarshallingManager.h | 136 +- .../Chaos/Collision/PBDCollisionConstraint.h | 3 +- .../Collision/SpatialAccelerationBroadPhase.h | 19 +- .../Public/Chaos/Evolution/PBDMinEvolution.h | 3 + .../Public/Chaos/Evolution/SimulationSpace.h | 6 +- .../Public/Chaos/ExternalCollisionData.h | 13 +- .../Public/Chaos/Framework/PhysicsProxy.h | 6 +- .../Public/Chaos/Framework/PhysicsProxyBase.h | 3 + .../Chaos/Framework/PhysicsSolverBase.h | 173 +- .../Experimental/Chaos/Public/Chaos/GJK.h | 6 +- .../Chaos/Public/Chaos/GeometryParticles.h | 14 + .../Chaos/Public/Chaos/ISpatialAcceleration.h | 15 +- .../Chaos/Joint/PBDJointSolverGaussSeidel.h | 6 + .../Public/Chaos/KinematicGeometryParticles.h | 4 + .../Public/Chaos/PBDCollisionConstraints.h | 16 + .../Chaos/PBDCollisionConstraintsContact.h | 1 + .../Public/Chaos/PBDJointConstraintData.h | 143 +- .../Chaos/Public/Chaos/PBDJointConstraints.h | 1 + .../Chaos/Public/Chaos/PBDRigidParticles.h | 5 + .../Chaos/Public/Chaos/PBDRigidsEvolution.h | 135 +- .../Public/Chaos/PBDRigidsEvolutionGBF.h | 2 + .../Chaos/Public/Chaos/PBDRigidsSOAs.h | 42 +- .../Chaos/Public/Chaos/ParticleHandle.h | 5 +- .../Chaos/Public/Chaos/Particles.h | 2 + .../Chaos/Public/Chaos/PendingSpatialData.h | 99 + .../Chaos/Public/Chaos/RigidParticles.h | 16 + .../ChaosCore/Public/Chaos/Core.h | 4 +- .../Private/ChaosSolversModule.cpp | 11 +- .../ChaosSolvers/Private/EventDefaults.cpp | 38 +- .../Private/PBDRigidActiveParticlesBuffer.cpp | 36 +- .../ChaosSolvers/Private/PBDRigidsSolver.cpp | 395 +- .../GeometryCollectionPhysicsProxy.cpp | 96 +- .../PhysicsProxy/JointConstraintProxy.cpp | 341 +- .../PhysicsProxy/PerSolverFieldSystem.cpp | 36 +- .../SingleParticlePhysicsProxy.cpp | 12 +- .../PhysicsProxy/SkeletalMeshPhysicsProxy.cpp | 3 +- .../PhysicsProxy/StaticMeshPhysicsObject.cpp | 4 +- .../Public/GeometryCollectionProxyData.h | 3 +- .../Public/PBDRigidActiveParticlesBuffer.h | 7 +- .../ChaosSolvers/Public/PBDRigidsSolver.h | 16 +- .../GeometryCollectionPhysicsProxy.h | 27 +- .../PhysicsProxy/JointConstraintProxy.h | 18 +- .../PhysicsProxy/SingleParticlePhysicsProxy.h | 16 +- .../PhysicsProxy/SkeletalMeshPhysicsProxy.h | 2 +- .../PhysicsProxy/StaticMeshPhysicsProxy.h | 2 +- .../Private/ThrustSystem.cpp | 20 +- .../ChaosVehiclesCore/Private/WheelSystem.cpp | 12 +- .../ChaosVehiclesCore/Public/EngineSystem.h | 6 +- .../ChaosVehiclesCore/Public/ThrustSystem.h | 12 +- .../ChaosVehiclesCore/Public/VehicleUtility.h | 11 +- .../GeometryCollectionEngine.Build.cs | 5 +- .../GeometryCollectionActor.cpp | 2 + .../GeometryCollectionComponent.cpp | 398 +- .../GeometryCollectionComponent.h | 97 +- .../Private/BaseGizmos/TransformGizmo.cpp | 16 +- .../Private/InputRouter.cpp | 4 +- .../Public/BaseGizmos/TransformGizmo.h | 7 + .../Private/FriendsAndChatStyle.cpp | 384 +- .../Classes/BlueprintGameplayTagLibrary.h | 4 +- .../Classes/GameplayTagContainer.h | 3 + .../Private/GameplayTagContainer.cpp | 33 + .../BundlePrereqCombinedStatusHelper.cpp | 2 + .../Private/InstallBundleCache.cpp | 18 +- .../Private/InstallBundleTypes.cpp | 6 +- .../Private/InstallBundleUtils.cpp | 2 + .../Public/InstallBundleCache.h | 5 +- .../Public/InstallBundleManagerInterface.h | 1 - .../Public/InstallBundleTypes.h | 15 +- .../Public/JsonObjectConverter.h | 18 +- .../Landscape/Classes/LandscapeProxy.h | 2 +- .../Runtime/Landscape/Private/Landscape.cpp | 6 +- .../Landscape/Private/LandscapeCollision.cpp | 11 - .../Landscape/Private/LandscapeEdit.cpp | 6 +- .../Launch/Private/Android/AndroidJNI.cpp | 65 + .../Launch/Private/LaunchEngineLoop.cpp | 16 +- .../Launch/Public/Android/AndroidJNI.h | 5 + .../LevelSequence/Private/LevelSequence.cpp | 16 +- .../Public/LevelSequenceBurnIn.h | 2 +- .../Public/LiveLinkPresetTypes.h | 16 +- .../Classes/ShaderPlatformQualitySettings.h | 9 +- .../Private/MaterialShaderQualitySettings.cpp | 7 + ...rialShaderQualitySettingsCustomization.cpp | 14 + .../Runtime/Media/Public/IMediaOptions.h | 22 +- .../Runtime/Media/Public/IMediaPlayer.h | 2 + .../Runtime/Media/Public/IMediaSamples.h | 1 + .../Media/Public/IMediaTextureSample.h | 37 + .../Private/Assets/MediaSource.cpp | 6 + .../Private/Assets/MediaTexture.cpp | 41 +- .../Private/Misc/MediaTextureResource.cpp | 65 +- .../Runtime/MediaAssets/Public/MediaSource.h | 1 + .../Runtime/MediaAssets/Public/MediaTexture.h | 32 + .../MediaUtils/Private/MediaPlayerFacade.cpp | 123 +- .../MediaUtils/Private/MediaSamples.cpp | 9 + .../MediaUtils/Public/MediaPlayerFacade.h | 2 +- .../MediaUtils/Public/MediaSampleQueue.h | 2 +- .../Runtime/MediaUtils/Public/MediaSamples.h | 1 + .../Channels/MovieSceneFloatChannel.cpp | 2 +- .../MovieSceneCompiledDataManager.cpp | 55 +- .../MovieSceneCompiledVolatilityManager.cpp | 2 +- ...ovieSceneEvaluationTreePopulationRules.cpp | 15 +- .../EntitySystem/BuiltInComponentTypes.cpp | 28 +- .../IMovieSceneEntityProvider.cpp | 12 +- .../EntitySystem/MovieSceneEntityLedger.cpp | 84 +- .../EntitySystem/MovieSceneEntityManager.cpp | 105 +- .../EntitySystem/MovieSceneEntitySystem.cpp | 18 +- .../MovieSceneEntitySystemGraphs.cpp | 23 +- .../MovieSceneEntitySystemLinker.cpp | 50 +- .../MovieSceneEntitySystemRunner.cpp | 2 +- .../EntitySystem/MovieSceneEvalTimeSystem.cpp | 36 +- .../MovieScenePropertyRegistry.cpp | 19 - .../MovieSceneSequenceInstance.cpp | 6 +- .../MovieSceneSequenceUpdaters.cpp | 35 +- .../MovieSceneSpawnablesSystem.cpp | 12 +- .../Evaluation/MovieSceneEvaluationField.cpp | 159 +- .../MovieSceneEvaluationTemplateInstance.cpp | 39 + .../Evaluation/MovieScenePreAnimatedState.cpp | 8 +- .../MovieSceneSequenceHierarchy.cpp | 6 + .../Runtime/MovieScene/Private/MovieScene.cpp | 13 +- .../Private/MovieSceneCommonHelpers.cpp | 21 + .../MovieScene/Private/MovieSceneSection.cpp | 18 +- .../MovieScene/Private/MovieSceneSequence.cpp | 11 + .../Private/MovieSceneSequencePlayer.cpp | 3 +- .../Private/MovieSceneSequenceTickManager.cpp | 2 +- .../Sections/MovieSceneSpawnSection.cpp | 33 +- .../Private/Sections/MovieSceneSubSection.cpp | 20 + .../MovieSceneCompiledDataManager.h | 4 +- .../EntitySystem/BuiltInComponentTypes.h | 17 + .../EntitySystem/IMovieSceneEntityProvider.h | 41 +- .../IMovieScenePropertyComponentHandler.h | 14 + .../MovieSceneCachedEntityFilterResult.h | 8 + .../MovieSceneComponentAccessors.h | 9 +- .../MovieSceneComponentTypeInfo.h | 8 +- .../MovieSceneDecompositionQuery.h | 7 +- .../EntitySystem/MovieSceneEntityFactory.h | 30 +- .../EntitySystem/MovieSceneEntityLedger.h | 31 +- .../EntitySystem/MovieSceneEntityManager.h | 41 +- .../EntitySystem/MovieSceneEntitySystem.h | 22 +- .../MovieSceneEntitySystemGraphs.h | 4 +- .../MovieSceneEntitySystemLinker.h | 50 +- .../EntitySystem/MovieSceneEntitySystemTask.h | 179 +- .../MovieSceneEntitySystemTypes.h | 165 + .../MovieSceneOverlappingEntityTracker.h | 43 +- .../MovieScenePropertyComponentHandler.h | 163 +- .../EntitySystem/MovieScenePropertyRegistry.h | 56 +- .../MovieSceneEvaluationCustomVersion.h | 3 + .../Evaluation/MovieSceneEvaluationField.h | 375 +- .../MovieSceneEvaluationTemplateInstance.h | 4 + .../Evaluation/MovieSceneEvaluationTrack.h | 7 +- .../Evaluation/MovieSceneEvaluationTree.h | 6 + .../Evaluation/MovieScenePreAnimatedState.h | 2 +- .../Evaluation/MovieSceneSequenceHierarchy.h | 5 + .../Runtime/MovieScene/Public/MovieScene.h | 6 +- .../Public/MovieSceneCommonHelpers.h | 8 + .../MovieScene/Public/MovieSceneTrack.h | 2 +- .../Public/Sections/MovieSceneSpawnSection.h | 2 +- .../Public/Sections/MovieSceneSubSection.h | 4 + .../Public/SkeletalMeshRestoreState.h | 81 + ...eSceneInterrogatedPropertyInstantiator.cpp | 298 ++ .../MovieSceneInterrogationLinker.cpp | 855 ++++ .../MovieSceneParameterTemplate.cpp | 36 +- .../MovieSceneSkeletalAnimationTemplate.cpp | 22 +- .../Sections/MovieScene3DAttachSection.cpp | 4 +- .../Sections/MovieScene3DTransformSection.cpp | 63 +- .../MovieSceneEventRepeaterSection.cpp | 11 +- .../Sections/MovieSceneEventSectionBase.cpp | 54 +- .../MovieSceneEventTriggerSection.cpp | 8 +- .../Sections/MovieSceneFloatSection.cpp | 6 +- .../MovieSceneLevelVisibilitySection.cpp | 2 +- .../MovieScene3DTransformPropertySystem.cpp | 2 + .../MovieSceneComponentAttachmentSystem.cpp | 2 + .../MovieSceneComponentMobilitySystem.cpp | 45 +- .../MovieSceneComponentTransformSystem.cpp | 5 + ...MovieSceneEulerTransformPropertySystem.cpp | 2 + .../Systems/MovieSceneFloatPropertySystem.cpp | 2 + .../MovieSceneLevelVisibilitySystem.cpp} | 2 +- .../MovieSceneLevelVisibilitySystem.h} | 2 +- .../MovieScenePiecewiseFloatBlenderSystem.cpp | 79 +- .../MovieScenePropertyInstantiator.cpp | 31 +- .../Systems/MovieScenePropertySystem.cpp | 20 +- .../WeightAndEasingEvaluatorSystem.cpp | 347 +- .../MovieSceneCameraCutTrackInstance.cpp | 50 +- .../Tracks/MovieSceneTransformTrack.cpp | 6 - .../Public/Channels/MovieSceneEvent.h | 16 +- ...vieSceneInterrogatedPropertyInstantiator.h | 109 + .../MovieSceneInterrogationLinker.h | 354 ++ .../Evaluation/MovieSceneParameterTemplate.h | 2 +- .../Sections/MovieScene3DTransformSection.h | 4 + .../Sections/MovieSceneEventRepeaterSection.h | 2 +- .../Sections/MovieSceneEventSectionBase.h | 22 +- .../Sections/MovieSceneEventTriggerSection.h | 2 +- .../Systems/MovieScenePropertyInstantiator.h | 82 +- .../Systems/WeightAndEasingEvaluatorSystem.h | 57 + .../Public/Tracks/MovieSceneTransformTrack.h | 5 - .../Private/NavMesh/PImplRecastNavMesh.cpp | 8 +- .../NavMesh/RecastNavMeshDataChunk.cpp | 10 +- .../NavMesh/RecastNavMeshGenerator.cpp | 8 +- .../Private/NavigationData.cpp | 5 +- .../Private/NavigationSystem.cpp | 31 +- .../Public/NavMesh/RecastQueryFilter.h | 3 +- .../NavigationSystem/Public/NavigationData.h | 6 + .../Public/NavigationSystem.h | 5 +- .../Private/Detour/DetourNavMeshBuilder.cpp | 2 +- .../Public/Detour/DetourNavMeshQuery.h | 2 +- .../Runtime/Net/Common/NetCommon.Build.cs | 18 + .../Private/Net/Common/NetCommonModule.cpp | 28 + .../Public/Net/Common/Packets}/PacketTraits.h | 25 +- .../Public/Net/Common/Packets/PacketView.h | 139 + .../Public/Net/Common/Sockets/SocketErrors.h | 69 + .../Core/Private/Net/Core/NetCoreModule.cpp | 3 - .../BackgroundHTTP/BackgroundHTTP.Build.cs | 1 + .../Private/BackgroundHttpManagerImpl.cpp | 278 +- .../Private/BackgroundHttpRequestImpl.cpp | 3 + .../GenericPlatformBackgroundHttp.cpp | 13 - .../GenericPlatformBackgroundHttpResponse.cpp | 7 +- .../IOS/ApplePlatformBackgroundHttp.cpp | 10 - .../ApplePlatformBackgroundHttpManager.cpp | 21 +- .../Public/BackgroundHttpManagerImpl.h | 44 +- .../GenericPlatformBackgroundHttp.h | 16 - .../Public/IOS/ApplePlatformBackgroundHttp.h | 14 - .../IOS/ApplePlatformBackgroundHttpManager.h | 5 +- .../Interfaces/IBackgroundHttpManager.h | 14 +- .../BackgroundHTTPFileHash.Build.cs | 18 + .../Private/BackgroundHttpFileHashHelper.cpp | 305 ++ .../Private/BackgroundHttpFileHashModule.cpp | 7 + .../Private/BackgroundHttpFileHashPrivate.h} | 4 +- .../Public/BackgroundHttpFileHashHelper.h | 113 + .../Private/Common/SpeedRecorder.cpp | 3 + .../Private/Installer/DownloadService.cpp | 3 + .../Statistics/FileOperationTracker.cpp | 3 + .../Private/Tests/Mock/HttpRequest.mock.h | 16 + .../Online/HTTP/Private/Apple/AppleHTTP.cpp | 85 +- .../Online/HTTP/Private/Apple/AppleHTTP.h | 6 + .../Online/HTTP/Private/Curl/CurlHttp.cpp | 94 +- .../Online/HTTP/Private/Curl/CurlHttp.h | 2 + .../GenericPlatform/HttpRequestImpl.cpp | 20 + .../Online/HTTP/Private/HttpManager.cpp | 23 +- .../Online/HTTP/Private/IXML/HttpIXML.h | 12 +- .../Online/HTTP/Private/IXML/HttpIXml.cpp | 64 +- .../Runtime/Online/HTTP/Private/NullHttp.cpp | 14 +- .../WinHttp/Support/WinHttpConnectionHttp.cpp | 20 + .../Private/WinHttp/WinHttpHttpManager.cpp | 48 +- .../HTTP/Private/WinHttp/WinHttpHttpManager.h | 10 + .../Private/WinHttp/WinHttpHttpRequest.cpp | 42 +- .../HTTP/Private/WinHttp/WinHttpHttpRequest.h | 20 +- .../Public/GenericPlatform/HttpRequestImpl.h | 9 + .../Runtime/Online/HTTP/Public/HttpManager.h | 18 + .../Online/HTTP/Public/HttpRequestAdapter.h | 3 + .../HTTP/Public/Interfaces/IHttpRequest.h | 22 + .../HTTPServer/Private/HttpConnection.cpp | 2 +- .../HTTPServer/Private/HttpServerModule.cpp | 2 + .../Runtime/Online/ICMP/Private/UDPPing.cpp | 1226 +++++- .../Source/Runtime/Online/ICMP/Public/Icmp.h | 88 +- .../Private/HoloLens/HoloLensWebSocket.cpp | 3 + .../Support/WinHttpConnectionWebSocket.cpp | 16 +- .../WinHttp/WinHttpWebSocketsManager.cpp | 2 + .../Private/XmppJingle/XmppChatJingle.cpp | 2 + .../XmppJingle/XmppConnectionJingle.cpp | 3 + .../Private/XmppJingle/XmppMessagesJingle.cpp | 3 + .../XmppJingle/XmppMultiUserChatJingle.cpp | 2 + .../Private/XmppJingle/XmppPresenceJingle.cpp | 3 + .../Private/XmppJingle/XmppPubSubJingle.cpp | 2 + .../Online/XMPP/Private/XmppModule.cpp | 2 + .../XmppStrophe/XmppConnectionStrophe.cpp | 3 + .../XmppStrophe/XmppMessagesStrophe.cpp | 3 + .../XmppStrophe/XmppMultiUserChatStrophe.cpp | 3 + .../Private/XmppStrophe/XmppPingStrophe.cpp | 3 + .../XmppStrophe/XmppPresenceStrophe.cpp | 3 + .../XmppStrophe/XmppPrivateChatStrophe.cpp | 3 + .../Private/XmppStrophe/XmppPubSubStrophe.cpp | 2 + .../OpenGLDrv/Private/OpenGLDevice.cpp | 2 +- .../OpenGLDrv/Private/OpenGLRenderTarget.cpp | 17 +- .../OpenGLDrv/Private/OpenGLShaders.cpp | 43 +- .../Public/RSAEncryptionHandlerComponent.h | 8 - .../RSAKeyAESEncryptionHandlerComponent.h | 8 - .../PacketHandler/Private/PacketHandler.cpp | 37 +- .../PacketHandler/Public/PacketHandler.h | 131 +- .../Public/ReliabilityHandlerComponent.h | 3 - .../PakFile/Private/IPlatformFilePak.cpp | 54 +- .../Runtime/PakFile/Public/IPlatformFilePak.h | 14 +- .../Private/ChaosEngineInterface.cpp | 85 +- .../PhysicsCore/Private/ChaosScene.cpp | 210 +- .../PhysicsCore/Private/PhysicalMaterial.cpp | 1 + .../Public/Chaos/ChaosEngineInterface.h | 8 +- .../PhysicsCore/Public/Chaos/ChaosScene.h | 3 +- .../PhysicalMaterials/PhysicalMaterial.h | 4 + .../Public/PhysicsInterfaceTypesCore.h | 6 +- .../Runtime/PhysicsCore/Public/SQVerifier.h | 22 +- .../Private/PreLoadSettingsContainer.cpp | 20 +- .../Private/PreLoadSlateThreading.cpp | 13 + .../Public/PreLoadSettingsContainer.h | 15 +- .../Public/PreLoadSlateThreading.h | 25 +- .../Projects/Private/PluginManager.cpp | 5 + .../RHI/Private/PipelineStateCache.cpp | 173 +- Engine/Source/Runtime/RHI/Private/RHI.cpp | 15 +- .../Runtime/RHI/Private/RHIUtilities.cpp | 14 +- .../Runtime/RHI/Public/PipelineStateCache.h | 30 +- Engine/Source/Runtime/RHI/Public/RHI.h | 3 + .../Runtime/RHI/Public/RHICommandList.h | 26 + .../Public/RHICommandListCommandExecutes.inl | 8 + Engine/Source/Runtime/RHI/Public/RHIContext.h | 7 + .../Runtime/RHI/Public/RHIDefinitions.h | 3 +- .../Source/Runtime/RHI/Public/RHIResources.h | 38 +- .../Source/Runtime/RHI/Public/RHIUtilities.h | 3 + .../RenderCore/Private/GlobalShader.cpp | 21 +- .../RenderCore/Private/RenderGraphUtils.cpp | 4 +- .../RenderCore/Private/RenderTargetPool.cpp | 8 +- .../RenderCore/Private/RenderUtils.cpp | 14 + .../RenderCore/Private/RenderingThread.cpp | 10 +- .../Runtime/RenderCore/Private/Shader.cpp | 5 + .../Runtime/RenderCore/Private/ShaderMap.cpp | 25 +- .../RenderCore/Private/ShaderResource.cpp | 4 +- .../Runtime/RenderCore/Public/GlobalShader.h | 2 +- .../ProfilingDebugging/RealtimeGPUProfiler.h | 2 + .../RenderCore/Public/RenderResource.h | 7 + .../Runtime/RenderCore/Public/RenderUtils.h | 9 + .../Source/Runtime/RenderCore/Public/Shader.h | 8 +- .../Renderer/Private/BasePassRendering.cpp | 2 +- .../Renderer/Private/BasePassRendering.h | 5 + .../PostProcessAmbientOcclusion.cpp | 4 +- .../Private/DeferredShadingRenderer.cpp | 133 +- .../Private/DistanceFieldAmbientOcclusion.cpp | 9 +- .../Private/IndirectLightRendering.cpp | 6 +- .../Renderer/Private/LightRendering.cpp | 51 +- .../Runtime/Renderer/Private/LightRendering.h | 1 + .../Renderer/Private/MeshPassProcessor.cpp | 1 + .../Private/MobileBasePassRendering.cpp | 1 + .../Renderer/Private/MobileDecalRendering.cpp | 6 +- .../Private/MobileSceneCaptureRendering.cpp | 2 +- .../Private/MobileShadingRenderer.cpp | 8 +- .../Private/MobileTranslucentRendering.cpp | 1 + .../PostProcess/PostProcessEyeAdaptation.cpp | 6 + .../Private/PostProcess/PostProcessMobile.cpp | 12 + .../PostProcess/PostProcessTonemap.cpp | 2 +- .../Private/PostProcess/PostProcessing.cpp | 2 +- .../Private/PostProcess/TemporalAA.cpp | 53 +- .../Renderer/Private/PrimitiveSceneInfo.cpp | 3 +- .../RayTracing/RayTracingAmbientOcclusion.cpp | 4 +- .../RayTracingDeferredReflections.cpp | 6 +- .../RayTracing/RayTracingDynamicGeometry.cpp | 1 + .../RayTracingGlobalIllumination.cpp | 43 +- .../RayTracing/RayTracingIESLightProfiles.h | 4 +- .../Private/RayTracing/RayTracingLighting.cpp | 213 +- .../Private/RayTracing/RayTracingLighting.h | 17 +- .../RayTracingMaterialHitShaders.cpp | 116 +- .../RayTracing/RayTracingPrimaryRays.cpp | 4 +- .../RayTracing/RayTracingReflections.cpp | 4 +- .../Private/RayTracing/RayTracingShadows.cpp | 22 + .../Private/RayTracing/RaytracingSkylight.cpp | 25 +- .../ReflectionEnvironmentRealTimeCapture.cpp | 349 +- .../Renderer/Private/RendererScene.cpp | 1 + .../Private/SceneHitProxyRendering.cpp | 7 +- .../Runtime/Renderer/Private/ScenePrivate.h | 22 +- .../Renderer/Private/SceneRendering.cpp | 148 +- .../Runtime/Renderer/Private/SceneRendering.h | 47 +- .../Renderer/Private/SceneVisibility.cpp | 95 +- .../Renderer/Private/ScreenSpaceDenoise.cpp | 5 +- .../Renderer/Private/ShadowRendering.cpp | 7 +- .../Runtime/Renderer/Private/ShadowSetup.cpp | 19 + .../Private/SingleLayerWaterRendering.cpp | 1 + .../Private/SingleLayerWaterRendering.h | 1 + .../Private/SkyAtmosphereRendering.cpp | 9 + .../Renderer/Private/TranslucentRendering.cpp | 2 +- .../Renderer/Private/TranslucentRendering.h | 2 + .../Private/VolumetricCloudRendering.cpp | 78 +- .../Private/VolumetricCloudRendering.h | 2 +- .../Private/VolumetricRenderTarget.cpp | 113 +- .../Renderer/Public/MeshDrawShaderBindings.h | 10 +- .../Renderer/Public/MeshPassProcessor.h | 1 + .../Renderer/Public/PrimitiveSceneInfo.h | 7 +- .../SignalProcessing/Private/Filter.cpp | 34 + .../Private/LinkwitzRileyBandSplitter.cpp | 263 ++ .../Private/VariablePoleFilter.cpp | 85 + .../SignalProcessing/Public/DSP/Filter.h | 2 + .../Public/DSP/LinkwitzRileyBandSplitter.h | 112 + .../Public/DSP/VariablePoleFilter.h | 48 + .../Application/SlateApplication.cpp | 2 +- .../Private/Framework/Text/TextLayout.cpp | 8 +- .../Private/Widgets/Input/SEditableText.cpp | 11 + .../Widgets/Input/SEditableTextBox.cpp | 5 + .../Input/SMultiLineEditableTextBox.cpp | 7 + .../Widgets/Text/SMultiLineEditableText.cpp | 11 + .../Slate/Private/Widgets/Text/STextBlock.cpp | 16 +- .../Widgets/Text/SlateEditableTextLayout.cpp | 3 +- .../Slate/Public/Framework/Text/TextLayout.h | 2 + .../Public/Widgets/Input/SEditableText.h | 15 + .../Public/Widgets/Input/SEditableTextBox.h | 11 + .../Widgets/Input/SMultiLineEditableTextBox.h | 14 + .../Widgets/Text/ISlateEditableTextWidget.h | 3 + .../Widgets/Text/SMultiLineEditableText.h | 15 + .../Private/Animation/CurveSequence.cpp | 2 + .../ConsoleSlateDebuggerInvalidate.cpp | 734 ++++ .../ConsoleSlateDebuggerInvalidate.h | 123 + .../ConsoleSlateDebuggerInvalidationRoot.cpp | 318 ++ .../ConsoleSlateDebuggerInvalidationRoot.h | 84 + .../Debugging/ConsoleSlateDebuggerPaint.cpp | 126 +- .../Debugging/ConsoleSlateDebuggerPaint.h | 7 +- .../Debugging/ConsoleSlateDebuggerUpdate.cpp | 441 +++ .../Debugging/ConsoleSlateDebuggerUpdate.h | 100 + .../Private/Debugging/SlateDebugging.cpp | 98 + .../FastUpdate/SlateInvalidationRoot.cpp | 100 +- .../FastUpdate/SlateInvalidationRootList.h | 50 + .../Private/FastUpdate/WidgetProxy.cpp | 69 +- .../SlateCore/Private/Fonts/FontCache.cpp | 2 + .../Private/Fonts/FontCacheCompositeFont.cpp | 135 +- .../Private/Fonts/FontCacheCompositeFont.h | 8 + .../Private/Fonts/FontCacheFreeType.cpp | 36 +- .../Private/Fonts/FontCacheFreeType.h | 19 +- .../Private/Fonts/SlateFontRenderer.cpp | 26 +- .../Private/Fonts/SlateTextShaper.cpp | 6 +- .../Private/Rendering/DrawElements.cpp | 56 +- .../Private/Rendering/ElementBatcher.cpp | 43 +- .../SlateCore/Private/SlateCoreClasses.cpp | 16 +- .../SlateCore/Private/SlateCoreModule.cpp | 9 + .../Private/Widgets/Images/SLayeredImage.cpp | 174 + .../SlateCore/Private/Widgets/SOverlay.cpp | 10 +- .../SlateCore/Private/Widgets/SWidget.cpp | 31 +- .../SlateCore/Private/Widgets/SWindow.cpp | 12 +- .../Public/Debugging/SlateDebugging.h | 62 + .../Public/FastUpdate/SlateInvalidationRoot.h | 29 +- .../FastUpdate/SlateInvalidationRootHandle.h | 2 +- .../SlateCore/Public/FastUpdate/WidgetProxy.h | 26 +- .../Public/FastUpdate/WidgetUpdateFlags.h | 27 + .../Runtime/SlateCore/Public/SlateGlobals.h | 11 +- .../Public/Widgets/Images/SLayeredImage.h | 69 + .../SlateCore/Public/Widgets/SWidget.h | 20 +- .../Private/Slate3DRenderer.cpp | 20 +- .../Private/SlateMaterialResource.cpp | 88 +- .../Private/SlateRHIRenderer.cpp | 22 +- .../Private/SlateRHIRenderingPolicy.cpp | 10 + .../Private/SlateUTextureResource.cpp | 14 +- .../Runtime/Sockets/Public/SocketTypes.h | 108 +- .../Source/Runtime/Sockets/Sockets.Build.cs | 3 + .../MovieScene2DTransformPropertySystem.cpp | 2 + .../MovieScene2DTransformSection.cpp | 4 +- .../MovieSceneWidgetMaterialTemplate.cpp | 5 + .../Runtime/UMG/Private/Components/Widget.cpp | 2 +- .../Private/Components/WidgetComponent.cpp | 116 +- .../UMG/Private/Slate/SRetainerWidget.cpp | 10 +- .../Source/Runtime/UMG/Private/UserWidget.cpp | 86 +- .../Source/Runtime/UMG/Private/WidgetTree.cpp | 7 +- .../Runtime/UMG/Public/Blueprint/UserWidget.h | 15 +- .../UMG/Public/Components/RichTextBlock.h | 1 + .../UMG/Public/Components/WidgetComponent.h | 55 +- .../Source/Runtime/VectorVM/Public/VectorVM.h | 17 +- .../Private/Android/VulkanAndroidPlatform.cpp | 314 +- .../Private/Android/VulkanAndroidPlatform.h | 65 +- .../Runtime/VulkanRHI/Private/VulkanContext.h | 6 + .../Runtime/VulkanRHI/Private/VulkanDevice.h | 1 - .../Private/VulkanGenericPlatform.cpp | 10 + .../VulkanRHI/Private/VulkanGenericPlatform.h | 13 +- .../VulkanRHI/Private/VulkanLayers.cpp | 4 - .../VulkanRHI/Private/VulkanPipeline.cpp | 1 + .../Runtime/VulkanRHI/Private/VulkanRHI.cpp | 2 +- .../VulkanRHI/Private/VulkanRenderTarget.cpp | 60 +- .../VulkanRHI/Private/VulkanResources.h | 2 + .../VulkanRHI/Private/VulkanSwapChain.cpp | 263 +- .../VulkanRHI/Private/VulkanSwapChain.h | 50 - .../Runtime/VulkanRHI/Private/VulkanUAV.cpp | 6 +- .../Runtime/VulkanRHI/Private/VulkanUtil.cpp | 10 +- .../VulkanRHI/Private/VulkanViewport.cpp | 2 +- .../Runtime/VulkanRHI/VulkanRHI.Build.cs | 3 +- .../Private/IOS/IOSPlatformWebBrowser.cpp | 39 +- .../Windows/D3D11RHI/Private/D3D11Query.cpp | 12 +- .../Windows/D3D11RHI/Private/D3D11RHI.cpp | 4 +- .../D3D11RHI/Private/D3D11RHIPrivate.h | 4 +- .../D3D11RHI/Private/D3D11RenderTarget.cpp | 21 +- .../Windows/D3D11RHI/Private/D3D11Util.cpp | 2 +- .../Private/Windows/WindowsD3D11Device.cpp | 30 +- .../Private/Windows/WindowsD3D11Viewport.cpp | 34 +- .../Windows/D3D11RHI/Public/D3D11Util.h | 3 + .../libunwind/libunwind/src/aarch64/Ginit.c | 45 +- .../libunwind/libunwind/src/arm/Ginit.c | 41 +- .../libunwind/libunwind/src/x86/Ginit.c | 41 +- .../libunwind/libunwind/src/x86_64/Ginit.c | 44 +- .../BuildScripts/Mac/BuildAll.command | 49 + .../BuildScripts/Mac/Common/Common.sh | 133 + .../BuildForUE/Mac/BuildForMac.command | 78 + .../BuildForUE/Mac/BuildForMac.command | 104 +- .../FreeType2/UE4_BuildThirdPartyLib.bat | 41 - .../UE4_BuildThirdPartyLib_Mac.command | 35 - .../GoogleGameSDK/GoogleGameSDK_APL.xml | 1 + ...dForMac.command => BuildForMacOLD.command} | 0 .../BuildForUE/Mac/BuildForMac.command | 101 +- .../ThirdParty/ICU/UE4_BuildThirdPartyLib.bat | 19 - .../ICU/UE4_BuildThirdPartyLib_Mac.command | 99 - .../BuildForUE/Mac/BuildForMac.command | 107 +- .../BuildForUE/Mac/BuildForMac.command | 129 + .../MikkTSpace/BuildForUE/BuildForMac.command | 79 + .../ThirdParty/MikkTSpace/BuildMacLib.sh | 6 - .../BuildForUE/Mac/BuildForMac.command | 73 + .../ThirdParty/OpenSSL/OpenSSL.Build.cs | 6 +- .../BuildForUE/Mac/BuildForMac.command | 76 + .../compiler/cmake/mac/CMakeLists.txt | 15 +- .../LowLevelCloth/src/neon/SimdTypes.h | 2 +- .../src/neon/SwCollisionHelpers.h | 7 +- .../clothing/src/simd/neon/NvNeonSimdTypes.h | 2 +- .../PhysX3/BuildForUE/Mac/BuildForMac.command | 163 + .../Source/ThirdParty/PhysX3/EpicChanges.txt | 18 + .../NvCloth/src/NvSimd/neon/NvNeonSimdTypes.h | 2 +- .../NvCloth/src/neon/SwCollisionHelpers.h | 12 +- .../Source/compiler/cmake/mac/CMakeLists.txt | 20 +- .../src/compiler/cmake/mac/CMakeLists.txt | 22 +- .../BuildForUE/Mac/BuildForMac.command | 75 + .../BuildForUE/Mac/BuildForMac.command | 82 + .../ThirdParty/libOpus/opus-1.1/Makefile.am | 18 + .../ThirdParty/libOpus/opus-1.1/Makefile.in | 2226 +++++++---- ...UE4_BuildThirdPartyLib_IOSAppleTV.command} | 0 .../ThirdParty/libPNG/UElibPNG.Build.cs | 1 + .../ThirdParty/libPNG/libPNG-1.5.2/pngwutil.c | 4 +- .../BuildForUE/Mac/BuildForMac.command | 89 + .../BuildForUE/Mac/BuildForMac.command | 254 ++ .../BuildForUE/Mac/BuildForMac.command | 68 + .../mtlpp-master-7efad47/src/debugger.mm | 5 + .../BuildForUE/Mac/BuildForMac.command | 95 + 2361 files changed, 110950 insertions(+), 41134 deletions(-) create mode 100644 Engine/Extras/RoboMerge/v3/functional_tests/framework/src/tests/test-terminal.ts create mode 100644 Engine/Plugins/Developer/PropertyAccessNode/PropertyAccessNode.uplugin create mode 100644 Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/K2Node_PropertyAccess.cpp create mode 100644 Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/K2Node_PropertyAccess.h create mode 100644 Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/PropertyAccessNodeFactory.cpp create mode 100644 Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/PropertyAccessNodeFactory.h create mode 100644 Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/PropertyAccessNodeModule.cpp create mode 100644 Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/SPropertyAccessNode.cpp create mode 100644 Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/SPropertyAccessNode.h create mode 100644 Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/PropertyAccessNode.Build.cs rename Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/{DataAssetIndexer.cpp => GenericObjectIndexer.cpp} (66%) rename Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/{DataAssetIndexer.h => GenericObjectIndexer.h} (54%) create mode 100644 Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/MaterialExpressionIndexer.cpp create mode 100644 Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/MaterialExpressionIndexer.h create mode 100644 Engine/Plugins/Experimental/ActorPalette/ActorPalette.uplugin create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/ActorPalette.Build.cs create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPalette.cpp create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPalette.h create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteCommands.cpp create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteModule.cpp create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteSettings.cpp create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteSettings.h create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteStyle.cpp create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewport.cpp create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewport.h create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewportClient.cpp create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewportClient.h create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Public/ActorPaletteCommands.h create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Public/ActorPaletteModule.h create mode 100644 Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Public/ActorPaletteStyle.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/ChaosCaching.uplugin create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/ChaosCaching.Build.cs create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/CacheAdapter.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/GeometryCollectionComponentCacheAdapter.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/StaticMeshComponentCacheAdapter.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/CacheCollection.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/CacheEvents.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/CacheManagerActor.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/ChaosCache.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/ChaosCachingPlugin.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/Adapters/CacheAdapter.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/Adapters/GeometryCollectionComponentCacheAdapter.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/Adapters/StaticMeshComponentCacheAdapter.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/CacheCollection.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/CacheEvents.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/CacheManagerActor.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/ChaosCache.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/ChaosCachingPlugin.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/ChaosCachingEditor.Build.cs create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/ActorFactoryCacheManager.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/AssetTypeActions_ChaosCacheCollection.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheCollectionCustomization.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheCollectionFactory.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheEditorCommands.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheManagerCustomization.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/ChaosCachingEditorPlugin.cpp create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/ActorFactoryCacheManager.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/AssetTypeActions_ChaosCacheCollection.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheCollectionCustomization.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheCollectionFactory.h create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheEditorCommands.h rename Engine/Plugins/Experimental/{LiveStreamAnimation/Source/LSAEditor/Private/LSAHandleDetailCustomization.h => ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheManagerCustomization.h} (50%) create mode 100644 Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/ChaosCachingEditorPlugin.h rename Engine/Plugins/Experimental/{HairStrands/Shaders/Private => ChaosNiagara/Shaders}/NiagaraDataInterfaceFieldSystem.ush (86%) rename Engine/Plugins/Experimental/{HairStrands/Source/HairStrandsNiagara/Public => ChaosNiagara/Source/ChaosNiagara/Classes}/NiagaraDataInterfaceFieldSystem.h (63%) create mode 100644 Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Private/NiagaraDataInterfaceFieldSystem.cpp delete mode 100644 Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosTireConfig.cpp delete mode 100644 Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/SimpleCarActor.cpp delete mode 100644 Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/VehicleContactModification.cpp delete mode 100644 Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosTireConfig.h delete mode 100644 Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/SimpleCarActor.h delete mode 100644 Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/VehicleContactModification.h delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/ForwardingChannels.uplugin delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/ForwardingChannels.Build.cs delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannel.cpp delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannelsModule.cpp delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannelsModule.h delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannelsSubsystem.cpp delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingGroup.cpp delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannel.h delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelFactory.h delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsFwd.h delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsSubsystem.h delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsUtils.h delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingGroup.h delete mode 100644 Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingPacket.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupEdgeInserter.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MeshProjectionHull.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Parameterization/MeshUVPacking.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshCurvatureMapBaker.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshImageBakingCache.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshNormalMapBaker.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshOcclusionMapBaker.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshPropertyMapBaker.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshResampleImageBaker.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/MeshSimpleShapeApproximation.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/ShapeDetection3.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/SimpleShapeSet3.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/GroupEdgeInserter.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MeshProjectionHull.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Parameterization/MeshUVPacking.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshCurvatureMapBaker.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshImageBaker.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshImageBakingCache.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshNormalMapBaker.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshOcclusionMapBaker.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshPropertyMapBaker.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshResampleImageBaker.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ShapeApproximation/MeshSimpleShapeApproximation.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ShapeApproximation/ShapeDetection3.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ShapeApproximation/SimpleShapeSet3.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Intersection/IntersectionQueries3.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Generators/CapsuleGenerator.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/HalfspaceTypes.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Image/ImageBuilder.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntersectionQueries3.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Sampling/VectorSetAnalysis.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/DenseGrid2.h create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ConvexHull2.cpp create mode 100644 Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/ConvexHull2.h create mode 100644 Engine/Plugins/Experimental/HairStrands/Shaders/Private/NiagaraDirectSolver.ush delete mode 100644 Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfaceFieldSystem.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/LiveStreamAnimation.uplugin delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/LSAEditor.Build.cs delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAEditorModule.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAEditorModule.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAHandleDetailCustomization.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LiveLink/LSALiveLinkFrameTranslatorAssetActions.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LiveLink/LSALiveLinkFrameTranslatorFactory.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LiveLink/LSALiveLinkFrameTranslatorFactory.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/LiveStreamAnimation.Build.cs delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/ControlPacket.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/ControlPacket.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkPacket.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkPacket.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkStreamingHelper.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkStreamingHelper.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkFrameData.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkSource.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkSource.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/Test/SkelMeshToLiveLinkSource.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationChannel.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationHandle.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationLog.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationModule.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationModule.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationPacket.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationPacket.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationSettings.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationSubsystem.cpp delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkFrameData.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkRole.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkSourceOptions.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/Test/SkelMeshToLiveLinkSource.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationChannel.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationFwd.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationHandle.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationSettings.h delete mode 100644 Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationSubsystem.h delete mode 100644 Engine/Plugins/Experimental/MattRadford_RnD/MattRadford_RnD.uplugin create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/EdgeLoopInsertionTool.cpp create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/CollisionGeometryVisualization.cpp create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/CollisionPropertySets.cpp create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/ExtractCollisionGeometryTool.cpp create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/PhysicsInspectorTool.cpp create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/SetCollisionGeometryTool.cpp create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/EdgeLoopInsertionTool.h create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/CollisionGeometryVisualization.h create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/CollisionPropertySets.h create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/ExtractCollisionGeometryTool.h create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/PhysicsInspectorTool.h create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/SetCollisionGeometryTool.h create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/UVLayoutPreview.cpp create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Mechanics/CurveControlPointsMechanic.cpp create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Physics/PhysicsDataCollection.cpp create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/UVLayoutPreview.h create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Mechanics/CurveControlPointsMechanic.h create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Physics/CollisionGeometryConversion.h create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Physics/PhysicsDataCollection.h create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperators/Private/CuttingOps/EdgeLoopInsertionOp.cpp create mode 100644 Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperators/Public/CuttingOps/EdgeLoopInsertionOp.h create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/MotionTrailEditorMode.uplugin create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/MotionTrailEditorMode.Build.cs create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorMode.cpp create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorModeCommands.cpp create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorModeModule.cpp create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorModeToolkit.cpp create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorToolset.cpp create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MovieSceneTransformTrail.cpp create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MovieSceneTransformTrail.h create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/SequencerTrailHierarchy.cpp create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/SequencerTrailHierarchy.h create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/Trail.cpp create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/TrailHierarchy.cpp create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/TrajectoryCache.cpp create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/TrajectoryDrawInfo.cpp create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorMode.h create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorModeCommands.h create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorModeModule.h create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorModeToolkit.h create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorToolset.h create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/Trail.h create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/TrailHierarchy.h create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/TrajectoryCache.h create mode 100644 Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/TrajectoryDrawInfo.h create mode 100644 Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceRenderTarget2D.h create mode 100644 Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceRenderTarget2D.cpp create mode 100644 Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraComponentBroker.h create mode 100644 Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraScriptBase.cpp create mode 100644 Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraScriptBase.h rename Engine/Plugins/{MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders => Media/AppleProResMedia/Source/AppleProResMedia}/Private/MoviePipelineAppleProResOutput.cpp (99%) rename Engine/Plugins/{MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders => Media/AppleProResMedia/Source/AppleProResMedia}/Private/MoviePipelineAppleProResOutput.h (99%) rename Engine/Plugins/{MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders => Media/AvidDNxMedia/Source/Source}/Private/MoviePipelineAvidDNxOutput.cpp (100%) rename Engine/Plugins/{MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders => Media/AvidDNxMedia/Source/Source}/Private/MoviePipelineAvidDNxOutput.h (98%) create mode 100644 Engine/Plugins/Media/WebMMedia/Source/ThirdParty/vpx/build/EpicChanges.txt create mode 100644 Engine/Plugins/Media/WebMMedia/Source/ThirdParty/vpx/build/Mac/BuildForMac.command delete mode 100644 Engine/Plugins/Media/WebMMedia/Source/ThirdParty/vpx/build/Mac/build-libvpx-mac.sh create mode 100755 Engine/Plugins/Media/WebMMedia/Source/ThirdParty/webm/build/Mac/BuildForMac.command delete mode 100644 Engine/Plugins/Media/WebMMedia/Source/ThirdParty/webm/build/Mac/build-libwebm-mac.sh create mode 100644 Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineFCPXMLExporterSetting.cpp create mode 100644 Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineQueueEngineSubsystem.cpp create mode 100644 Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineFCPXMLExporterSetting.h create mode 100644 Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineQueueEngineSubsystem.h create mode 100644 Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineEXROutput.cpp create mode 100644 Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineEXROutput.h create mode 100644 Engine/Plugins/MovieScene/MovieRenderPipeline/Source/openexrRTTI/Private/OpenExrRTTIModule.cpp create mode 100644 Engine/Plugins/MovieScene/MovieRenderPipeline/Source/openexrRTTI/Public/IOpenExrRTTIModule.h create mode 100644 Engine/Plugins/MovieScene/MovieRenderPipeline/Source/openexrRTTI/UEOpenExrRTTI.Build.cs delete mode 100644 Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/MovieRenderPipelineEncoders.uplugin delete mode 100644 Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/MovieRenderPipelineEncoders.Build.cs delete mode 100644 Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Evaluation/TemplateSequenceInstanceData.cpp delete mode 100644 Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Evaluation/TemplateSequenceSectionTemplate.cpp create mode 100644 Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Systems/TemplateSequenceSystem.cpp create mode 100644 Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Systems/TemplateSequenceSystem.h delete mode 100644 Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Evaluation/TemplateSequenceInstanceData.h delete mode 100644 Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Evaluation/TemplateSequenceSectionTemplate.h create mode 100644 Engine/Plugins/Online/OnlineSubsystem/Source/Private/OnlineSessionInterface.cpp create mode 100644 Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGenerator.cpp rename Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/{SoundModulatorLFO.cpp => SoundModulationGeneratorLFO.cpp} (52%) rename Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/{SoundModulatorLFOProxy.cpp => SoundModulationGeneratorLFOProxy.cpp} (88%) rename Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/{SoundModulatorLFOProxy.h => SoundModulationGeneratorLFOProxy.h} (85%) create mode 100644 Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationGenerator.h rename Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/{SoundModulatorLFO.h => SoundModulationGeneratorLFO.h} (75%) create mode 100644 Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulationGeneratorLFO.cpp rename Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/{AssetTypeActions_SoundModulatorLFO.h => AssetTypeActions_SoundModulationGeneratorLFO.h} (70%) delete mode 100644 Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulatorLFO.cpp create mode 100644 Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulationGeneratorLFOFactory.cpp rename Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/{SoundModulatorLFOFactory.h => SoundModulationGeneratorLFOFactory.h} (56%) delete mode 100644 Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulatorLFOFactory.cpp rename Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/{SoundControlBusMixChannelLayout.cpp => SoundControlBusMixStageLayout.cpp} (87%) rename Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/{SoundControlBusMixChannelLayout.h => SoundControlBusMixStageLayout.h} (83%) create mode 100644 Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockRootMotionComponent.cpp create mode 100644 Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockRootMotionSimulation.cpp create mode 100644 Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockRootMotionSourceDataAsset.cpp create mode 100644 Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockRootMotionComponent.h create mode 100644 Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockRootMotionSimulation.h create mode 100644 Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockRootMotionSourceDataAsset.h create mode 100644 Engine/Plugins/Runtime/PropertyAccess/PropertyAccess.uplugin create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Private/AnimBlueprintClassSubsystem_PropertyAccess.cpp create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Private/PropertyAccess.cpp rename Engine/Plugins/{MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MovieRenderPipelineEncoders.cpp => Runtime/PropertyAccess/Source/PropertyAccess/Private/PropertyAccessModule.cpp} (53%) create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/PropertyAccess.Build.cs create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Public/AnimBlueprintClassSubsystem_PropertyAccess.h create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Public/PropertyAccess.h create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Public/PropertyEventInterfaces.h create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/AnimBlueprintCompilerSubsystem_PropertyAccess.cpp create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/AnimBlueprintCompilerSubsystem_PropertyAccess.h create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/PropertyAccessEditor.cpp create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/PropertyAccessEditor.h create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/PropertyAccessEditorModule.cpp create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/SPropertyBinding.cpp create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/SPropertyBinding.h create mode 100644 Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/PropertyAccessEditor.Build.cs create mode 100644 Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Classes/SubmixEffects/SubmixEffectMultiBandCompressor.h create mode 100644 Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/SubmixEffectMultiBandCompressor.cpp create mode 100644 Engine/Shaders/Private/RayTracing/GenerateCulledLightListCS.usf create mode 100644 Engine/Shaders/Private/RayTracing/RayTracingLightCullingCommon.ush create mode 100644 Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_TextureRenderTargetVolume.h create mode 100644 Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompiler.cpp create mode 100644 Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompiler.h create mode 100644 Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem.cpp create mode 100644 Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystemCollection.h create mode 100644 Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_Base.cpp create mode 100644 Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_Base.h create mode 100644 Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_CachedPose.cpp create mode 100644 Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_CachedPose.h create mode 100644 Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_LinkedAnimGraph.cpp create mode 100644 Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_LinkedAnimGraph.h create mode 100644 Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_StateMachine.cpp create mode 100644 Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_StateMachine.h create mode 100644 Engine/Source/Editor/AnimGraph/Public/AnimBlueprintCompilerSubsystem.h create mode 100644 Engine/Source/Editor/AnimGraph/Public/IClassVariableCreator.h create mode 100644 Engine/Source/Editor/AnimGraph/Public/IPropertyAccessCompilerSubsystem.h create mode 100644 Engine/Source/Editor/Kismet/Private/BPFunctionClipboardData.cpp create mode 100644 Engine/Source/Editor/Kismet/Private/BPFunctionClipboardData.h create mode 100644 Engine/Source/Editor/Kismet/Private/ISCSEditorUICustomization.cpp create mode 100644 Engine/Source/Editor/Kismet/Private/ReplaceNodeReferencesHelper.cpp create mode 100644 Engine/Source/Editor/Kismet/Private/SCSEditorExtensionContext.cpp create mode 100644 Engine/Source/Editor/Kismet/Public/ISCSEditorUICustomization.h create mode 100644 Engine/Source/Editor/Kismet/Public/ReplaceNodeReferencesHelper.h create mode 100644 Engine/Source/Editor/Kismet/Public/SCSEditorExtensionContext.h delete mode 100644 Engine/Source/Editor/KismetCompiler/Private/AnimBlueprintCompiler.cpp delete mode 100644 Engine/Source/Editor/KismetCompiler/Private/AnimBlueprintCompiler.h delete mode 100644 Engine/Source/Editor/LevelEditor/Private/ActorDetailsExtensionContext.cpp delete mode 100644 Engine/Source/Editor/LevelEditor/Public/ActorDetailsExtensionContext.h create mode 100644 Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLMetadataExport.cpp create mode 100644 Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLMetadataExport.h create mode 100644 Engine/Source/Editor/MovieSceneTools/Public/MovieSceneExportMetadata.h delete mode 100644 Engine/Source/Editor/UMGEditor/Private/Details/SPropertyBinding.cpp delete mode 100644 Engine/Source/Editor/UMGEditor/Private/Details/SPropertyBinding.h create mode 100644 Engine/Source/Editor/UnrealEd/Classes/Factories/TextureRenderTargetVolumeFactoryNew.h create mode 100644 Engine/Source/Editor/UnrealEd/Public/IPropertyAccessCompiler.h create mode 100644 Engine/Source/Editor/UnrealEd/Public/IPropertyAccessEditor.h create mode 100644 Engine/Source/Programs/BuildAgent/Issues/DumpIssuesMode.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Automation/P4Automation.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Automation/VisualStudioAutomation.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedBuildWindow.Designer.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedBuildWindow.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedBuildWindow.resx create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/P4Handler.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/UGSHandler.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/UriHandler.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/VisualStudioHandler.cs create mode 100644 Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSPaymentTransactionObserver.cpp create mode 100644 Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSPaymentTransactionObserver.h create mode 100644 Engine/Source/Runtime/Core/Private/HAL/IPlatformFileManagedStorageWrapper.cpp create mode 100644 Engine/Source/Runtime/Core/Private/HAL/PlatformMemory.cpp create mode 100644 Engine/Source/Runtime/Core/Private/IO/IoDispatcherFileBackendTypes.h create mode 100644 Engine/Source/Runtime/Core/Public/HAL/IPlatformFileManagedStorageWrapper.h create mode 100644 Engine/Source/Runtime/D3D12RHI/Private/D3D12TimedIntervalQuery.cpp create mode 100644 Engine/Source/Runtime/D3D12RHI/Private/D3D12TimedIntervalQuery.h create mode 100644 Engine/Source/Runtime/D3D12RHI/Public/D3D12RHIBridge.h create mode 100644 Engine/Source/Runtime/Engine/Classes/Animation/AnimBlueprintClassSubsystem.h create mode 100644 Engine/Source/Runtime/Engine/Classes/Engine/TextureRenderTargetVolume.h create mode 100644 Engine/Source/Runtime/Engine/Classes/Engine/ViewportStatsSubsystem.h create mode 100644 Engine/Source/Runtime/Engine/Private/Animation/AnimBlueprintClassSubsystem.cpp create mode 100644 Engine/Source/Runtime/Engine/Private/Animation/AnimClassInterface.cpp create mode 100644 Engine/Source/Runtime/Engine/Private/Engine/ViewportStatsSubsystem.cpp create mode 100644 Engine/Source/Runtime/Engine/Private/ReplayHelper.cpp create mode 100644 Engine/Source/Runtime/Engine/Private/ReplayNetConnection.cpp create mode 100644 Engine/Source/Runtime/Engine/Private/ReplaySubsystem.cpp create mode 100644 Engine/Source/Runtime/Engine/Private/SupportedRangeTypes.cpp create mode 100644 Engine/Source/Runtime/Engine/Private/TextureRenderTargetVolume.cpp create mode 100644 Engine/Source/Runtime/Engine/Public/Animation/AnimInstanceSubsystemData.h create mode 100644 Engine/Source/Runtime/Engine/Public/IPropertyAccess.h create mode 100644 Engine/Source/Runtime/Engine/Public/ReplayHelper.h create mode 100644 Engine/Source/Runtime/Engine/Public/ReplayNetConnection.h create mode 100644 Engine/Source/Runtime/Engine/Public/ReplaySubsystem.h create mode 100644 Engine/Source/Runtime/Engine/Public/ReplayTypes.h create mode 100644 Engine/Source/Runtime/Engine/Public/SupportedRangeTypes.h create mode 100644 Engine/Source/Runtime/Engine/Public/TextureRenderTargetVolumeResource.h create mode 100644 Engine/Source/Runtime/Experimental/Chaos/Private/Chaos/Evolution/PBDMinEvolution.ispc create mode 100644 Engine/Source/Runtime/Experimental/Chaos/Public/Chaos/PendingSpatialData.h create mode 100644 Engine/Source/Runtime/MovieScene/Public/SkeletalMeshRestoreState.h create mode 100644 Engine/Source/Runtime/MovieSceneTracks/Private/EntitySystem/Interrogation/MovieSceneInterrogatedPropertyInstantiator.cpp create mode 100644 Engine/Source/Runtime/MovieSceneTracks/Private/EntitySystem/Interrogation/MovieSceneInterrogationLinker.cpp rename Engine/Source/Runtime/MovieSceneTracks/Private/{Evaluation/MovieSceneLevelVisibilityTemplate.cpp => Systems/MovieSceneLevelVisibilitySystem.cpp} (99%) rename Engine/Source/Runtime/MovieSceneTracks/Private/{Evaluation/MovieSceneLevelVisibilityTemplate.h => Systems/MovieSceneLevelVisibilitySystem.h} (97%) create mode 100644 Engine/Source/Runtime/MovieSceneTracks/Public/EntitySystem/Interrogation/MovieSceneInterrogatedPropertyInstantiator.h create mode 100644 Engine/Source/Runtime/MovieSceneTracks/Public/EntitySystem/Interrogation/MovieSceneInterrogationLinker.h create mode 100644 Engine/Source/Runtime/Net/Common/NetCommon.Build.cs create mode 100644 Engine/Source/Runtime/Net/Common/Private/Net/Common/NetCommonModule.cpp rename Engine/Source/Runtime/Net/{Core/Public/Net/Core/Misc => Common/Public/Net/Common/Packets}/PacketTraits.h (64%) create mode 100644 Engine/Source/Runtime/Net/Common/Public/Net/Common/Packets/PacketView.h create mode 100644 Engine/Source/Runtime/Net/Common/Public/Net/Common/Sockets/SocketErrors.h create mode 100644 Engine/Source/Runtime/Online/BackgroundHTTPFileHash/BackgroundHTTPFileHash.Build.cs create mode 100644 Engine/Source/Runtime/Online/BackgroundHTTPFileHash/Private/BackgroundHttpFileHashHelper.cpp create mode 100644 Engine/Source/Runtime/Online/BackgroundHTTPFileHash/Private/BackgroundHttpFileHashModule.cpp rename Engine/{Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationLog.h => Source/Runtime/Online/BackgroundHTTPFileHash/Private/BackgroundHttpFileHashPrivate.h} (52%) create mode 100644 Engine/Source/Runtime/Online/BackgroundHTTPFileHash/Public/BackgroundHttpFileHashHelper.h create mode 100644 Engine/Source/Runtime/SignalProcessing/Private/LinkwitzRileyBandSplitter.cpp create mode 100644 Engine/Source/Runtime/SignalProcessing/Private/VariablePoleFilter.cpp create mode 100644 Engine/Source/Runtime/SignalProcessing/Public/DSP/LinkwitzRileyBandSplitter.h create mode 100644 Engine/Source/Runtime/SignalProcessing/Public/DSP/VariablePoleFilter.h create mode 100644 Engine/Source/Runtime/SlateCore/Private/Debugging/ConsoleSlateDebuggerInvalidate.cpp create mode 100644 Engine/Source/Runtime/SlateCore/Private/Debugging/ConsoleSlateDebuggerInvalidate.h create mode 100644 Engine/Source/Runtime/SlateCore/Private/Debugging/ConsoleSlateDebuggerInvalidationRoot.cpp create mode 100644 Engine/Source/Runtime/SlateCore/Private/Debugging/ConsoleSlateDebuggerInvalidationRoot.h create mode 100644 Engine/Source/Runtime/SlateCore/Private/Debugging/ConsoleSlateDebuggerUpdate.cpp create mode 100644 Engine/Source/Runtime/SlateCore/Private/Debugging/ConsoleSlateDebuggerUpdate.h create mode 100644 Engine/Source/Runtime/SlateCore/Private/FastUpdate/SlateInvalidationRootList.h create mode 100644 Engine/Source/Runtime/SlateCore/Private/Widgets/Images/SLayeredImage.cpp create mode 100644 Engine/Source/Runtime/SlateCore/Public/FastUpdate/WidgetUpdateFlags.h create mode 100644 Engine/Source/Runtime/SlateCore/Public/Widgets/Images/SLayeredImage.h create mode 100755 Engine/Source/ThirdParty/BuildScripts/Mac/BuildAll.command create mode 100644 Engine/Source/ThirdParty/BuildScripts/Mac/Common/Common.sh create mode 100755 Engine/Source/ThirdParty/FreeImage/FreeImage-3.18.0/BuildForUE/Mac/BuildForMac.command mode change 100644 => 100755 Engine/Source/ThirdParty/FreeType2/FreeType2-2.10.0/BuildForUE/Mac/BuildForMac.command delete mode 100755 Engine/Source/ThirdParty/FreeType2/UE4_BuildThirdPartyLib.bat delete mode 100755 Engine/Source/ThirdParty/FreeType2/UE4_BuildThirdPartyLib_Mac.command rename Engine/Source/ThirdParty/HarfBuzz/harfbuzz-1.2.4/BuildForUE/Mac/{BuildForMac.command => BuildForMacOLD.command} (100%) mode change 100644 => 100755 Engine/Source/ThirdParty/HarfBuzz/harfbuzz-2.4.0/BuildForUE/Mac/BuildForMac.command delete mode 100755 Engine/Source/ThirdParty/ICU/UE4_BuildThirdPartyLib.bat delete mode 100755 Engine/Source/ThirdParty/ICU/UE4_BuildThirdPartyLib_Mac.command mode change 100644 => 100755 Engine/Source/ThirdParty/ICU/icu4c-64_1/BuildForUE/Mac/BuildForMac.command create mode 100755 Engine/Source/ThirdParty/Intel/TBB/IntelTBB-2019u8/BuildForUE/Mac/BuildForMac.command create mode 100755 Engine/Source/ThirdParty/MikkTSpace/BuildForUE/BuildForMac.command delete mode 100755 Engine/Source/ThirdParty/MikkTSpace/BuildMacLib.sh create mode 100755 Engine/Source/ThirdParty/Ogg/libogg-1.2.2/BuildForUE/Mac/BuildForMac.command create mode 100755 Engine/Source/ThirdParty/PLCrashReporter/plcrashreporter-master-0c55d20-2020_07_10/BuildForUE/Mac/BuildForMac.command create mode 100755 Engine/Source/ThirdParty/PhysX3/BuildForUE/Mac/BuildForMac.command create mode 100644 Engine/Source/ThirdParty/PhysX3/EpicChanges.txt create mode 100755 Engine/Source/ThirdParty/Vorbis/libvorbis-1.3.2/BuildForUE/Mac/BuildForMac.command create mode 100755 Engine/Source/ThirdParty/libOpus/opus-1.1/BuildForUE/Mac/BuildForMac.command rename Engine/Source/ThirdParty/libPNG/{UE4_BuildThirdPartyLib_Mac.command => UE4_BuildThirdPartyLib_IOSAppleTV.command} (100%) create mode 100755 Engine/Source/ThirdParty/libPNG/libPNG-1.5.27/BuildForUE/Mac/BuildForMac.command create mode 100755 Engine/Source/ThirdParty/libcurl/BuildForUE/Mac/BuildForMac.command create mode 100755 Engine/Source/ThirdParty/mtlpp/mtlpp-master-7efad47/BuildForUE/Mac/BuildForMac.command create mode 100755 Engine/Source/ThirdParty/openexr/OpenEXR-2.3.0/BuildForUE/Mac/BuildForMac.command diff --git a/Engine/Binaries/DotNET/CsvTools/ReportGraphs.xml b/Engine/Binaries/DotNET/CsvTools/ReportGraphs.xml index ab48a99a809b..d57621b3fd3f 100644 --- a/Engine/Binaries/DotNET/CsvTools/ReportGraphs.xml +++ b/Engine/Binaries/DotNET/CsvTools/ReportGraphs.xml @@ -47,11 +47,11 @@ exclusive/gamethread/eventwait/* gamethreadtime - - exclusive/gamethread/eventwait/EndPhysics exclusive/gamethread/Physics exclusive/gamethread/vehicle* - + + exclusive/gamethread/eventwait/EndPhysics exclusive/gamethread/Physics exclusive/gamethread/vehicle* + - + exclusive/gamethread/* gamethreadtime @@ -102,6 +102,14 @@ CSVProfiler/Num* + + + AndroidMemory/MemoryWarningState + + + + AndroidCPU/CPUTemp AndroidCPU/ThermalStatus + diff --git a/Engine/Binaries/DotNET/CsvTools/ReportTypes.xml b/Engine/Binaries/DotNET/CsvTools/ReportTypes.xml index 94977d978366..5aeefde60171 100644 --- a/Engine/Binaries/DotNET/CsvTools/ReportTypes.xml +++ b/Engine/Binaries/DotNET/CsvTools/ReportTypes.xml @@ -97,6 +97,9 @@ + + + @@ -149,6 +152,8 @@ + + diff --git a/Engine/Build/Android/Java/src/com/epicgames/ue4/GameActivity.java.template b/Engine/Build/Android/Java/src/com/epicgames/ue4/GameActivity.java.template index a06efeb8f354..473ed7e6ce8b 100644 --- a/Engine/Build/Android/Java/src/com/epicgames/ue4/GameActivity.java.template +++ b/Engine/Build/Android/Java/src/com/epicgames/ue4/GameActivity.java.template @@ -82,6 +82,7 @@ import android.os.SystemClock; import android.os.Looper; import android.os.Handler; import android.os.HandlerThread; +import android.os.PowerManager; import android.app.AlertDialog; import android.app.Dialog; @@ -139,6 +140,7 @@ import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.Display; import android.view.Window; import android.widget.LinearLayout; import android.widget.PopupWindow; @@ -155,6 +157,7 @@ import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.games.Games; +import com.google.android.apps.internal.games.memoryadvice.MemoryAdvisor; import com.google.android.gms.plus.Plus; @@ -511,6 +514,12 @@ public class GameActivity extends $${gameActivitySuperClass}$$ implements Surfac public static final int ANDROID_BUILD_VERSION = android.os.Build.VERSION.SDK_INT; private StoreHelper IapStoreHelper; + + private MemoryAdvisor MemAdvisor; + private static final int MemoryAdvisorPollDelayMs = 100; + private static final int ProcessMemoryInfoPollDelayMs = 10000; + private long LastMemoryInfoPollTime; + private MemoryAdvisor.MemoryState MemState = MemoryAdvisor.MemoryState.OK; //$${gameActivityClassAdditions}$$ @@ -2351,39 +2360,77 @@ public class GameActivity extends $${gameActivitySuperClass}$$ implements Surfac { Logger.SuppressLogs(); } + + MemAdvisor = new MemoryAdvisor(this); + LastMemoryInfoPollTime = System.currentTimeMillis(); // update memory stats every 10 seconds memoryRunnable = new Runnable() { @Override public void run() { - int ProcessMemory = 0; - - ActivityManager activityManager = (ActivityManager)_activity.getSystemService(Context.ACTIVITY_SERVICE); - int pid = android.os.Process.myPid(); - int pids[] = new int[] { pid }; - android.os.Debug.MemoryInfo[] memoryInfo = activityManager.getProcessMemoryInfo(pids); - if (memoryInfo.length > 0) + JSONObject Advice = MemAdvisor.getAdvice(); + MemoryAdvisor.MemoryState CurrentMemoryState = MemoryAdvisor.getMemoryState(Advice); + if (CurrentMemoryState != MemState) { - ProcessMemory = memoryInfo[0].dalvikPss + memoryInfo[0].nativePss + memoryInfo[0].otherPss; - - if (Build.VERSION.SDK_INT >= 23) + switch (CurrentMemoryState) { - Map memstats = memoryInfo[0].getMemoryStats(); - if (memstats.containsKey("summary.total-pss")) + case UNKNOWN: + Log.warn("Cannot determine memory state"); + nativeOnMemoryWarningChanged(_activity, -1); + break; + case OK: + nativeOnMemoryWarningChanged(_activity, 0); + break; + case APPROACHING_LIMIT: + Log.warn("Approaching memory limit. Estimated available memory is " + MemoryAdvisor.availabilityEstimate(Advice) + " bytes"); + nativeOnMemoryWarningChanged(_activity, 1); + break; + case CRITICAL: + Log.warn("Critical memory limit. Estimated available memory is " + MemoryAdvisor.availabilityEstimate(Advice) + " bytes"); + nativeOnMemoryWarningChanged(_activity, 2); + break; + } + + MemState = CurrentMemoryState; + } + + int ProcessMemory = 0; + + long CurrentTimeMs = System.currentTimeMillis(); + if (CurrentTimeMs - LastMemoryInfoPollTime >= ProcessMemoryInfoPollDelayMs) + { + ActivityManager activityManager = (ActivityManager)_activity.getSystemService(Context.ACTIVITY_SERVICE); + int pid = android.os.Process.myPid(); + int pids[] = new int[] { pid }; + android.os.Debug.MemoryInfo[] memoryInfo = activityManager.getProcessMemoryInfo(pids); + if (memoryInfo.length > 0) + { + ProcessMemory = memoryInfo[0].dalvikPss + memoryInfo[0].nativePss + memoryInfo[0].otherPss; + + if (Build.VERSION.SDK_INT >= 23) { - ProcessMemory = Integer.parseInt(memstats.get("summary.total-pss")); + Map memstats = memoryInfo[0].getMemoryStats(); + if (memstats.containsKey("summary.total-pss")) + { + ProcessMemory = Integer.parseInt(memstats.get("summary.total-pss")); + } } } + Log.debug("Used memory: " + ProcessMemory + " ("+FindLineFromStatus("VmRSS:")+")"); + LastMemoryInfoPollTime = CurrentTimeMs; } - Log.debug("Used memory: " + ProcessMemory + " ("+FindLineFromStatus("VmRSS:")+")"); - + synchronized(_activity) { - _activity.UsedMemory = ProcessMemory; + if (ProcessMemory > 0) + { + _activity.UsedMemory = ProcessMemory; + } + if (_activity.memoryHandler != null) { - _activity.memoryHandler.postDelayed(this, 10000); + _activity.memoryHandler.postDelayed(this, _activity.MemoryAdvisorPollDelayMs); } } } @@ -3102,6 +3149,19 @@ public class GameActivity extends $${gameActivitySuperClass}$$ implements Surfac //$${gameActivityAfterMainViewCreatedAdditions}$$ clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + + if (Build.VERSION.SDK_INT >= 29) + { + PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); + powerManager.addThermalStatusListener(getMainExecutor(), new PowerManager.OnThermalStatusChangedListener() { + @Override + public void onThermalStatusChanged(int status) + { + Log.debug("=== Thermal status changed to " + status); + nativeOnThermalStatusChangedListener(null, status); + } + }); + } //$${gameActivityOnCreateAdditions}$$ //$${gameActivityOnCreateFinalAdditions}$$ @@ -5232,6 +5292,99 @@ public class GameActivity extends $${gameActivitySuperClass}$$ implements Surfac return false; } + public int[] AndroidThunkJava_GetSupportedNativeDisplayRefreshRates() + { + if(ANDROID_BUILD_VERSION >= 24) + { + WindowManager windowManager = getWindowManager(); + Display display = windowManager.getDefaultDisplay(); + Display.Mode currentmode = display.getMode(); + Display.Mode[] modes = display.getSupportedModes(); + ArrayList refreshlist = new ArrayList(); + + for (int i = 0; i < modes.length; i++) + { + if (modes[i].getPhysicalHeight() == currentmode.getPhysicalHeight() && + modes[i].getPhysicalWidth() == currentmode.getPhysicalWidth()) + { + refreshlist.add((int)modes[i].getRefreshRate()); + } + } + if (refreshlist.size() == 0) + { + refreshlist.add(60); + } + int[] result = new int[refreshlist.size()]; + for (int i=0; i < result.length; i++) + { + result[i] = refreshlist.get(i).intValue(); + } + return result; + } + else + { + int[] result = new int[1]; + result[0] = 60; + return result; + } + } + + public boolean AndroidThunkJava_SetNativeDisplayRefreshRate(int RefreshRate) + { + if(ANDROID_BUILD_VERSION >= 24) + { + WindowManager windowManager = getWindowManager(); + Display display = windowManager.getDefaultDisplay(); + Display.Mode currentmode = display.getMode(); + int currentmodeid = currentmode.getModeId(); + Display.Mode[] modes = display.getSupportedModes(); + + for (int i = 0; i < modes.length; i++) + { + if (modes[i].getPhysicalHeight() == currentmode.getPhysicalHeight() && + modes[i].getPhysicalWidth() == currentmode.getPhysicalWidth() && + (int)modes[i].getRefreshRate() == RefreshRate) + { + final int modeid = modes[i].getModeId(); + if(currentmodeid != modeid) + { + _activity.runOnUiThread(new Runnable() + { + public void run() + { + Window w = getWindow(); + WindowManager.LayoutParams l = w.getAttributes(); + l.preferredDisplayModeId = modeid; + w.setAttributes(l); + } + }); + Log.debug("Found mode " + modeid + " for native refresh rate "+RefreshRate); + } + return true; + } + } + + return false; + } + else + { + return (RefreshRate == 60); + } + } + + public int AndroidThunkJava_GetNativeDisplayRefreshRate() + { + if(ANDROID_BUILD_VERSION >= 24) + { + WindowManager windowManager = getWindowManager(); + Display display = windowManager.getDefaultDisplay(); + Display.Mode currentmode = display.getMode(); + return (int)currentmode.getRefreshRate(); + } + + return 60; + } + @SuppressWarnings("deprecation") @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public static boolean isAirplaneModeOn(Context context) @@ -6218,6 +6371,9 @@ public class GameActivity extends $${gameActivitySuperClass}$$ implements Surfac public native void nativeOnInitialDownloadStarted(); public native void nativeOnInitialDownloadCompleted(); + + public native void nativeOnThermalStatusChangedListener(GameActivity activity, int status); + public native void nativeOnMemoryWarningChanged(GameActivity activity, int status); static { diff --git a/Engine/Build/BatchFiles/Mac/Build.sh b/Engine/Build/BatchFiles/Mac/Build.sh index 1c8b3863ec0a..5073c3a1ead6 100755 --- a/Engine/Build/BatchFiles/Mac/Build.sh +++ b/Engine/Build/BatchFiles/Mac/Build.sh @@ -7,18 +7,25 @@ cd "`dirname "$0"`/../../../.." # Setup Environment and Mono source Engine/Build/BatchFiles/Mac/SetupEnvironment.sh -mono Engine/Build/BatchFiles/Mac -# First make sure that the UnrealBuildTool is up-to-date -if ! xbuild /property:Configuration=Development /verbosity:quiet /nologo /p:NoWarn=1591 Engine/Source/Programs/UnrealBuildTool/UnrealBuildTool.csproj; then - echo "Failed to build to build tool (UnrealBuildTool)" - exit 1 +# Skip UBT and SWC compile step if we're coming in on an SSH connection (ie remote toolchain) +if [ -z "$SSH_CONNECTION" ]; then + # First make sure that the UnrealBuildTool is up-to-date + if ! xbuild /property:Configuration=Development /verbosity:quiet /nologo /p:NoWarn=1591 Engine/Source/Programs/UnrealBuildTool/UnrealBuildTool.csproj; then + echo "Failed to build to build tool (UnrealBuildTool)" + exit 1 + fi + + # build SCW if specified + for i in "$@" ; do + if [[ $i == "-buildscw" ]] ; then + echo Building ShaderCompileWorker... + mono Engine/Binaries/DotNET/UnrealBuildTool.exe ShaderCompileWorker Mac Development + break + fi + done fi -if [ "$4" == "-buildscw" ] || [ "$5" == "-buildscw" ]; then - echo Building ShaderCompileWorker... - mono Engine/Binaries/DotNET/UnrealBuildTool.exe ShaderCompileWorker Mac Development -fi - -echo Running command : Engine/Binaries/DotNET/UnrealBuildTool.exe "$@" +echo Running Engine/Binaries/DotNET/UnrealBuildTool.exe "$@" mono Engine/Binaries/DotNET/UnrealBuildTool.exe "$@" ExitCode=$? diff --git a/Engine/Build/BatchFiles/Mac/XcodeBuild.sh b/Engine/Build/BatchFiles/Mac/XcodeBuild.sh index c2c5a4f5095d..c41a18fc6411 100755 --- a/Engine/Build/BatchFiles/Mac/XcodeBuild.sh +++ b/Engine/Build/BatchFiles/Mac/XcodeBuild.sh @@ -1,6 +1,7 @@ #!/bin/sh -# This script gets called every time Xcode does a build or clean operation, even though it's called "Build.sh". +# This script gets called every time Xcode does a build or clean operation. It is similar to Build.sh +# (and can take the same arguments) but performs some interpretation of arguments that come from Xcode # Values for $ACTION: "" = building, "clean" = cleaning # Setup Environment & Mono @@ -12,96 +13,91 @@ if ! xbuild /property:Configuration=Development /verbosity:quiet /nologo /p:NoWa exit 1 fi -# override env if action is specified on command line -if [ $1 == "clean" ]; then - ACTION="clean" +#echo "Raw Args: $*" + +case $1 in + "clean") + ACTION="clean" + ;; + + "install") + ACTION="install" + ;; +esac + +if [ ["$ACTION"] == [""] ]; then + ACTION="build" + TARGET=$1 + PLATFORM=$2 + CONFIGURATION=$3 + TRAILINGARGS=${@:4} +else + # non build actions are all shifted by one + TARGET=$2 + PLATFORM=$3 + CONFIGURATION=$4 + TRAILINGARGS=${@:5} fi -case $ACTION in - "") - echo Building $1... - - Platform="" - AdditionalFlags="" - - case $CLANG_STATIC_ANALYZER_MODE in - "deep") - AdditionalFlags+="-SkipActionHistory" - ;; - "shallow") - AdditionalFlags+="-SkipActionHistory" - ;; - esac - - case $ENABLE_THREAD_SANITIZER in - "YES"|"1") - # Disable TSAN atomic->non-atomic race reporting as we aren't C++11 memory-model conformant so UHT will fail - export TSAN_OPTIONS="suppress_equal_stacks=true suppress_equal_addresses=true report_atomic_races=false" - ;; - esac - - case $2 in - "iphoneos"|"IOS") - Platform="IOS" - AdditionalFlags+=" -deploy " - ;; - "appletvos") - Platform="TVOS" - AdditionalFlags+=" -deploy " - ;; - "iphonesimulator") - Platform="IOS" - AdditionalFlags+=" -deploy -simulator" - ;; - "macosx") - Platform="Mac" - ;; - *) - Platform="$2" -# AdditionalFlags+=" -deploy " - ;; - esac - - BuildTasks=$(defaults read com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks) - export NumUBTBuildTasks=$BuildTasks - - if [ "$4" == "-buildscw" ] || [ "$5" == "-buildscw" ]; then - echo Building ShaderCompileWorker... - mono Engine/Binaries/DotNET/UnrealBuildTool.exe ShaderCompileWorker Mac Development - fi - - echo Running command : Engine/Binaries/DotNET/UnrealBuildTool.exe $1 $Platform $3 $AdditionalFlags "${@:4}" - mono Engine/Binaries/DotNET/UnrealBuildTool.exe $1 $Platform $3 $AdditionalFlags "${@:4}" - ;; - "clean") - echo "Cleaning $2 $3 $4..." - - Platform="" - AdditionalFlags="-clean" - - case $3 in - "iphoneos"|"IOS") - Platform="IOS" - AdditionalFlags+=" " - ;; - "iphonesimulator") - Platform="IOS" - AdditionalFlags+=" -simulator" - AdditionalFlags+=" " - ;; - "macosx") - Platform="Mac" - ;; - *) - Platform="$3" - ;; - - esac - echo Running command: mono Engine/Binaries/DotNET/UnrealBuildTool.exe $2 $Platform $4 $AdditionalFlags "${@:5}" - mono Engine/Binaries/DotNET/UnrealBuildTool.exe $2 $Platform $4 $AdditionalFlags "${@:5}" - ;; +# Convert platform to UBT terms +case $PLATFORM in + "iphoneos"|"IOS"|"iphonesimulator") + PLATFORM="IOS" + ;; + "appletvos") + PLATFORM="TVOS" + ;; + "macosx") + PLATFORM="Mac" + ;; esac +echo "Processing $ACTION for Target=$TARGET Platform=$PLATFORM Configuration=$CONFIGURATION $TRAILINGARGS" + +# Add additional flags based on actions, arguments, and env properties +AdditionalFlags="" + +if [ "$ACTION" == "build" ]; then + + # flags based on platform + case $PLATFORM in + "IOS") + AdditionalFlags += " -deploy" + ;; + + "TVOS") + AdditionalFlags += " -deploy" + ;; + esac + + case $CLANG_STATIC_ANALYZER_MODE in + "deep") + AdditionalFlags+="-SkipActionHistory" + ;; + "shallow") + AdditionalFlags+="-SkipActionHistory" + ;; + esac + + case $ENABLE_THREAD_SANITIZER in + "YES"|"1") + # Disable TSAN atomic->non-atomic race reporting as we aren't C++11 memory-model conformant so UHT will fail + export TSAN_OPTIONS="suppress_equal_stacks=true suppress_equal_addresses=true report_atomic_races=false" + ;; + esac + + # Build SCW if this is an editor target + if [[ "$TARGET" == *"Editor" ]]; then + mono Engine/Binaries/DotNET/UnrealBuildTool.exe ShaderCompileWorker Mac Development + fi + +elif [ $ACTION == "clean" ]; then + AdditionalFlags="-clean" +fi + +echo Running Engine/Binaries/DotNET/UnrealBuildTool.exe $TARGET $PLATFORM $CONFIGURATION $TRAILINGARGS $AdditionalFlags +mono Engine/Binaries/DotNET/UnrealBuildTool.exe $TARGET $PLATFORM $CONFIGURATION $TRAILINGARGS $AdditionalFlags + ExitCode=$? if [ $ExitCode -eq 254 ] || [ $ExitCode -eq 255 ] || [ $ExitCode -eq 2 ]; then exit 0 diff --git a/Engine/Build/Commit.gitdeps.xml b/Engine/Build/Commit.gitdeps.xml index 407fecae8c37..f38ccbf2ebc8 100644 --- a/Engine/Build/Commit.gitdeps.xml +++ b/Engine/Build/Commit.gitdeps.xml @@ -8,7 +8,7 @@ - + @@ -6154,7 +6154,7 @@ - + @@ -6203,7 +6203,7 @@ - + @@ -6308,7 +6308,7 @@ - + @@ -8470,16 +8470,18 @@ - - + + + + @@ -9913,41 +9915,41 @@ - + - + - + - + - + - + - + - - - + + + - + - + - + - + @@ -10536,7 +10538,7 @@ - + @@ -10882,6 +10884,8 @@ + + @@ -10893,7 +10897,7 @@ - + @@ -11074,6 +11078,7 @@ + @@ -28807,214 +28812,214 @@ - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - + - + - + - + - + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + @@ -34539,6 +34544,7 @@ + @@ -35843,21 +35849,17 @@ - - - - - - - - - - - - + + + + + + + + - - + + @@ -35870,12 +35872,11 @@ - - - - + + + @@ -36307,7 +36308,7 @@ - + @@ -36868,6 +36869,11 @@ + + + + + @@ -38201,7 +38207,7 @@ - + @@ -38209,10 +38215,10 @@ - - - - + + + + @@ -38230,13 +38236,13 @@ - - + + - - - - + + + + @@ -38503,151 +38509,18 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + + @@ -38839,7 +38712,7 @@ - + @@ -39788,7 +39661,7 @@ - + @@ -39805,7 +39678,7 @@ - + @@ -39813,7 +39686,7 @@ - + @@ -39825,6 +39698,9 @@ + + + @@ -39864,9 +39740,12 @@ + + + @@ -39878,28 +39757,79 @@ - - - + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + - - + + - - + + + + + - + + + + + + + + + + + + + + + + + + + - + + + + @@ -40153,35 +40083,40 @@ - - + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - + + + + + @@ -40390,7 +40325,7 @@ - + @@ -40411,6 +40346,7 @@ + @@ -40453,6 +40389,7 @@ + @@ -40462,10 +40399,14 @@ + + + + @@ -40496,7 +40437,7 @@ - + @@ -40584,7 +40525,7 @@ - + @@ -40592,8 +40533,8 @@ - - + + @@ -40603,7 +40544,8 @@ - + + @@ -40642,7 +40584,11 @@ - + + + + + @@ -40670,22 +40616,22 @@ - + - + - + - + @@ -40697,37 +40643,38 @@ - + - + - + - - + + - - + + - - + + + - + - - + + @@ -40736,7 +40683,7 @@ - + @@ -40747,7 +40694,7 @@ - + @@ -40763,7 +40710,7 @@ - + @@ -40783,12 +40730,12 @@ - - + + - + @@ -41327,7 +41274,8 @@ - + + @@ -41466,17 +41414,17 @@ - + + - + - @@ -42067,14 +42015,23 @@ - - + + + + + + + + + + + @@ -42291,6 +42248,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -42492,7 +42485,7 @@ - + @@ -42500,16 +42493,18 @@ + - + + @@ -44307,12 +44302,12 @@ - + - + @@ -44421,6 +44416,21 @@ + + + + + + + + + + + + + + + @@ -44934,8 +44944,8 @@ - - + + @@ -45953,14 +45963,14 @@ - - - - - - - - + + + + + + + + @@ -47377,6 +47387,7 @@ + @@ -49199,6 +49210,7 @@ + @@ -49240,8 +49252,8 @@ - - + + @@ -49387,6 +49399,7 @@ + @@ -49592,6 +49605,7 @@ + @@ -56841,6 +56855,7 @@ + @@ -56951,7 +56966,7 @@ - + @@ -57587,6 +57602,7 @@ + @@ -57704,12 +57720,12 @@ - - + + - + @@ -57886,6 +57902,7 @@ + @@ -58138,6 +58155,7 @@ + @@ -58499,7 +58517,7 @@ - + @@ -59919,7 +59937,6 @@ - @@ -59987,6 +60004,7 @@ + @@ -60007,7 +60025,7 @@ - + @@ -60061,7 +60079,6 @@ - @@ -60173,7 +60190,6 @@ - @@ -60193,7 +60209,7 @@ - + @@ -60244,6 +60260,7 @@ + @@ -60268,7 +60285,6 @@ - @@ -60389,7 +60405,6 @@ - @@ -60421,13 +60436,14 @@ - + + @@ -60464,6 +60480,7 @@ + @@ -60473,6 +60490,7 @@ + @@ -60483,6 +60501,7 @@ + @@ -60499,7 +60518,7 @@ - + @@ -60524,7 +60543,6 @@ - @@ -60561,6 +60579,7 @@ + @@ -60685,7 +60704,6 @@ - @@ -60705,7 +60723,6 @@ - @@ -60784,7 +60801,7 @@ - + @@ -60797,7 +60814,7 @@ - + @@ -60846,6 +60863,7 @@ + @@ -60900,6 +60918,7 @@ + @@ -61029,7 +61048,6 @@ - @@ -61052,6 +61070,7 @@ + @@ -61064,6 +61083,7 @@ + @@ -61121,6 +61141,7 @@ + @@ -61134,7 +61155,7 @@ - + @@ -61150,6 +61171,7 @@ + @@ -61234,6 +61256,7 @@ + @@ -61372,6 +61395,7 @@ + @@ -61391,7 +61415,6 @@ - @@ -61430,7 +61453,7 @@ - + @@ -61488,7 +61511,7 @@ - + @@ -61501,11 +61524,9 @@ - - @@ -61564,7 +61585,7 @@ - + @@ -61575,7 +61596,7 @@ - + @@ -61678,6 +61699,7 @@ + @@ -61699,9 +61721,9 @@ - + @@ -61733,6 +61755,7 @@ + @@ -61873,13 +61896,13 @@ - + @@ -61900,7 +61923,6 @@ - @@ -61986,7 +62008,7 @@ - + @@ -62000,7 +62022,6 @@ - @@ -62013,6 +62034,7 @@ + @@ -62092,7 +62114,6 @@ - @@ -62140,7 +62161,6 @@ - @@ -62189,6 +62209,7 @@ + @@ -62196,6 +62217,7 @@ + @@ -62262,6 +62284,7 @@ + @@ -62343,7 +62366,6 @@ - @@ -62423,6 +62445,7 @@ + @@ -62455,7 +62478,7 @@ - + @@ -62498,7 +62521,8 @@ - + + @@ -62508,6 +62532,7 @@ + @@ -62526,7 +62551,6 @@ - @@ -62591,7 +62615,7 @@ - + @@ -62606,12 +62630,14 @@ + + @@ -62658,14 +62684,13 @@ - - + @@ -62675,7 +62700,6 @@ - @@ -62689,7 +62713,7 @@ - + @@ -62804,7 +62828,7 @@ - + @@ -62812,7 +62836,6 @@ - @@ -62845,7 +62868,6 @@ - @@ -62860,7 +62882,7 @@ - + @@ -62951,11 +62973,12 @@ - + + @@ -63120,6 +63143,7 @@ + @@ -63211,7 +63235,7 @@ - + @@ -63229,7 +63253,6 @@ - @@ -63285,7 +63308,6 @@ - @@ -63306,7 +63328,6 @@ - @@ -63329,7 +63350,7 @@ - + @@ -63366,6 +63387,7 @@ + @@ -63428,6 +63450,7 @@ + @@ -63545,6 +63568,7 @@ + @@ -63576,14 +63600,15 @@ + - + @@ -63623,7 +63648,6 @@ - @@ -63656,6 +63680,7 @@ + @@ -63663,6 +63688,7 @@ + @@ -63755,6 +63781,7 @@ + @@ -63804,11 +63831,11 @@ - + @@ -63816,6 +63843,7 @@ + @@ -63833,7 +63861,6 @@ - @@ -63846,6 +63873,7 @@ + @@ -63907,7 +63935,6 @@ - @@ -63996,7 +64023,6 @@ - @@ -64017,6 +64043,7 @@ + @@ -64028,7 +64055,6 @@ - @@ -64041,6 +64067,7 @@ + @@ -64076,7 +64103,6 @@ - @@ -64225,6 +64251,7 @@ + @@ -64351,7 +64378,6 @@ - @@ -64489,6 +64515,7 @@ + @@ -64557,12 +64584,11 @@ - - + @@ -64572,7 +64598,7 @@ - + @@ -64608,7 +64634,7 @@ - + @@ -64622,7 +64648,6 @@ - @@ -64654,7 +64679,7 @@ - + @@ -64664,6 +64689,7 @@ + @@ -64729,7 +64755,6 @@ - @@ -64848,7 +64873,7 @@ - + @@ -64881,16 +64906,17 @@ - + - + + @@ -65002,6 +65028,7 @@ + @@ -65044,7 +65071,7 @@ - + @@ -65059,8 +65086,10 @@ + + @@ -65148,7 +65177,6 @@ - @@ -65248,8 +65276,9 @@ + - + @@ -65358,7 +65387,6 @@ - @@ -65370,7 +65398,7 @@ - + @@ -65431,6 +65459,7 @@ + @@ -65449,10 +65478,12 @@ + + @@ -65523,7 +65554,7 @@ - + @@ -65534,7 +65565,7 @@ - + @@ -65611,10 +65642,9 @@ - - + @@ -65656,6 +65686,7 @@ + @@ -65672,7 +65703,7 @@ - + @@ -65692,7 +65723,6 @@ - @@ -65750,7 +65780,7 @@ - + @@ -65776,7 +65806,7 @@ - + @@ -65784,6 +65814,7 @@ + @@ -65795,6 +65826,7 @@ + @@ -65832,6 +65864,7 @@ + @@ -65850,6 +65883,7 @@ + @@ -65875,12 +65909,14 @@ + + @@ -65899,7 +65935,6 @@ - @@ -65938,6 +65973,7 @@ + @@ -66089,7 +66125,7 @@ - + @@ -66158,6 +66194,7 @@ + @@ -66174,7 +66211,7 @@ - + @@ -66234,6 +66271,7 @@ + @@ -66287,6 +66325,7 @@ + @@ -66311,7 +66350,7 @@ - + @@ -66454,7 +66493,9 @@ - + + + @@ -66496,7 +66537,6 @@ - @@ -66571,7 +66611,6 @@ - @@ -66611,7 +66650,7 @@ - + @@ -66628,6 +66667,7 @@ + @@ -66655,7 +66695,6 @@ - @@ -66708,12 +66747,13 @@ - + + @@ -66794,7 +66834,9 @@ + + @@ -66805,7 +66847,7 @@ - + @@ -66919,7 +66961,7 @@ - + @@ -66985,8 +67027,7 @@ - - + @@ -67002,7 +67043,7 @@ - + @@ -67053,7 +67094,6 @@ - @@ -67065,6 +67105,7 @@ + @@ -67109,8 +67150,6 @@ - - @@ -67131,8 +67170,8 @@ + - @@ -67176,6 +67215,7 @@ + @@ -67185,7 +67225,6 @@ - @@ -67214,7 +67253,6 @@ - @@ -67355,7 +67393,6 @@ - @@ -67372,7 +67409,6 @@ - @@ -67399,10 +67435,10 @@ - + @@ -67410,7 +67446,6 @@ - @@ -67433,6 +67468,7 @@ + @@ -67568,6 +67604,7 @@ + @@ -67601,6 +67638,7 @@ + @@ -67644,7 +67682,6 @@ - @@ -67658,7 +67695,7 @@ - + @@ -67750,7 +67787,6 @@ - @@ -67764,7 +67800,6 @@ - @@ -67832,6 +67867,7 @@ + @@ -67840,6 +67876,7 @@ + @@ -67862,7 +67899,7 @@ - + @@ -67921,7 +67958,7 @@ - + @@ -67948,6 +67985,7 @@ + @@ -67991,6 +68029,7 @@ + @@ -68020,6 +68059,7 @@ + @@ -68098,7 +68138,6 @@ - @@ -68121,7 +68160,7 @@ - + @@ -68167,7 +68206,6 @@ - @@ -68196,7 +68234,7 @@ - + @@ -68269,7 +68307,6 @@ - @@ -68383,9 +68420,11 @@ + + @@ -68428,12 +68467,10 @@ - - @@ -68472,7 +68509,6 @@ - @@ -68519,7 +68555,7 @@ - + @@ -68577,7 +68613,7 @@ - + @@ -68597,6 +68633,7 @@ + @@ -68834,6 +68871,7 @@ + @@ -68920,7 +68958,6 @@ - @@ -69080,7 +69117,6 @@ - @@ -69118,17 +69154,14 @@ - - - @@ -69216,6 +69249,8 @@ + + @@ -69324,6 +69359,7 @@ + @@ -69355,7 +69391,6 @@ - @@ -69487,7 +69522,7 @@ - + @@ -69556,6 +69591,7 @@ + @@ -69582,7 +69618,6 @@ - @@ -69606,6 +69641,7 @@ + @@ -69652,6 +69688,7 @@ + @@ -69715,7 +69752,6 @@ - @@ -69765,7 +69801,6 @@ - @@ -69873,6 +69908,7 @@ + @@ -69892,7 +69928,6 @@ - @@ -69935,12 +69970,12 @@ - + - + @@ -70068,6 +70103,7 @@ + @@ -70080,6 +70116,7 @@ + @@ -70100,7 +70137,6 @@ - @@ -70153,7 +70189,6 @@ - @@ -70162,20 +70197,19 @@ - - - + + - + @@ -70213,7 +70247,7 @@ - + @@ -70250,6 +70284,7 @@ + @@ -70386,6 +70421,7 @@ + @@ -70393,6 +70429,7 @@ + @@ -70421,7 +70458,6 @@ - @@ -70448,6 +70484,7 @@ + @@ -70472,6 +70509,7 @@ + @@ -70540,7 +70578,6 @@ - @@ -70581,6 +70618,7 @@ + @@ -70613,6 +70651,7 @@ + @@ -70678,7 +70717,6 @@ - @@ -70714,6 +70752,7 @@ + @@ -70728,7 +70767,6 @@ - @@ -70774,6 +70812,7 @@ + @@ -70791,7 +70830,6 @@ - @@ -70799,7 +70837,6 @@ - @@ -70809,12 +70846,10 @@ - - @@ -70830,7 +70865,6 @@ - @@ -70852,11 +70886,9 @@ - - @@ -70944,7 +70976,7 @@ - + @@ -70957,7 +70989,6 @@ - @@ -70977,7 +71008,6 @@ - @@ -70997,6 +71027,7 @@ + @@ -71045,7 +71076,7 @@ - + @@ -71058,6 +71089,7 @@ + @@ -71085,6 +71117,7 @@ + @@ -71106,7 +71139,7 @@ - + @@ -71193,6 +71226,7 @@ + @@ -71215,7 +71249,6 @@ - @@ -71238,7 +71271,6 @@ - @@ -71307,7 +71339,6 @@ - @@ -71334,7 +71365,6 @@ - @@ -71392,6 +71422,7 @@ + @@ -71405,7 +71436,8 @@ - + + @@ -71420,7 +71452,7 @@ - + @@ -71450,6 +71482,7 @@ + @@ -71571,6 +71604,7 @@ + @@ -71578,7 +71612,7 @@ - + @@ -71624,7 +71658,7 @@ - + @@ -71660,8 +71694,10 @@ + + @@ -71804,6 +71840,7 @@ + @@ -71927,6 +71964,7 @@ + @@ -71961,7 +71999,6 @@ - @@ -72013,7 +72050,6 @@ - @@ -72066,7 +72102,6 @@ - @@ -72075,7 +72110,6 @@ - @@ -72299,6 +72333,7 @@ + @@ -72384,7 +72419,7 @@ - + @@ -72450,7 +72485,7 @@ - + @@ -72530,7 +72565,7 @@ - + @@ -72542,7 +72577,6 @@ - @@ -72676,6 +72710,7 @@ + @@ -72742,7 +72777,6 @@ - @@ -72778,6 +72812,7 @@ + @@ -72842,7 +72877,7 @@ - + @@ -72969,6 +73004,7 @@ + @@ -72981,7 +73017,7 @@ - + @@ -73008,8 +73044,8 @@ + - @@ -73036,7 +73072,6 @@ - @@ -73052,6 +73087,7 @@ + @@ -73080,6 +73116,7 @@ + @@ -73143,7 +73180,6 @@ - @@ -73162,6 +73198,7 @@ + @@ -73169,7 +73206,7 @@ - + @@ -73192,7 +73229,7 @@ - + @@ -73222,14 +73259,13 @@ - + - @@ -73400,6 +73436,7 @@ + @@ -73426,7 +73463,6 @@ - @@ -73441,7 +73477,6 @@ - @@ -73495,6 +73530,7 @@ + @@ -73527,12 +73563,12 @@ + - @@ -73569,7 +73605,6 @@ - @@ -73585,7 +73620,6 @@ - @@ -73600,6 +73634,7 @@ + @@ -73626,6 +73661,7 @@ + @@ -73663,7 +73699,7 @@ - + @@ -73762,11 +73798,12 @@ - + + @@ -73777,6 +73814,7 @@ + @@ -73812,6 +73850,7 @@ + @@ -73911,7 +73950,7 @@ - + @@ -73945,10 +73984,10 @@ - + - + @@ -73956,6 +73995,7 @@ + @@ -74011,7 +74051,7 @@ - + @@ -74156,7 +74196,6 @@ - @@ -74179,12 +74218,13 @@ + + - @@ -74195,6 +74235,7 @@ + @@ -74235,14 +74276,16 @@ + - + - + + @@ -74254,14 +74297,12 @@ - - @@ -74276,7 +74317,7 @@ - + @@ -74357,7 +74398,7 @@ - + @@ -74384,7 +74425,6 @@ - @@ -74400,7 +74440,6 @@ - @@ -74451,6 +74490,7 @@ + @@ -74502,12 +74542,12 @@ - + - + @@ -74532,6 +74572,7 @@ + @@ -74571,6 +74612,7 @@ + @@ -74631,7 +74673,6 @@ - @@ -74689,6 +74730,7 @@ + @@ -74706,6 +74748,7 @@ + @@ -74746,6 +74789,7 @@ + @@ -74762,7 +74806,6 @@ - @@ -74777,24 +74820,21 @@ - - - - + @@ -74852,9 +74892,7 @@ - - @@ -74871,6 +74909,7 @@ + @@ -74910,6 +74949,7 @@ + @@ -74978,7 +75018,6 @@ - @@ -74989,7 +75028,6 @@ - @@ -75034,7 +75072,6 @@ - @@ -75047,7 +75084,6 @@ - @@ -75085,7 +75121,6 @@ - @@ -75115,7 +75150,6 @@ - @@ -75162,7 +75196,6 @@ - @@ -75179,12 +75212,14 @@ + + @@ -75193,6 +75228,7 @@ + @@ -75206,7 +75242,6 @@ - @@ -75266,7 +75301,7 @@ - + @@ -75304,6 +75339,7 @@ + @@ -75429,6 +75465,7 @@ + @@ -75457,6 +75494,7 @@ + @@ -75525,7 +75563,6 @@ - @@ -75592,7 +75629,6 @@ - @@ -75650,8 +75686,6 @@ - - @@ -75682,9 +75716,8 @@ - + - @@ -75694,13 +75727,12 @@ - - + + - @@ -75719,7 +75751,6 @@ - @@ -75906,7 +75937,7 @@ - + @@ -75942,6 +75973,7 @@ + @@ -75980,10 +76012,9 @@ - + - @@ -76024,7 +76055,7 @@ - + @@ -76060,7 +76091,7 @@ - + @@ -76107,9 +76138,11 @@ + + @@ -76151,6 +76184,7 @@ + @@ -76179,7 +76213,7 @@ - + @@ -76325,7 +76359,7 @@ - + @@ -76336,7 +76370,6 @@ - @@ -76589,6 +76622,7 @@ + @@ -76704,7 +76738,6 @@ - @@ -76733,7 +76766,6 @@ - @@ -76800,6 +76832,7 @@ + @@ -76855,6 +76888,7 @@ + @@ -76867,6 +76901,7 @@ + @@ -77058,6 +77093,7 @@ + @@ -77076,7 +77112,6 @@ - @@ -77090,12 +77125,10 @@ - - @@ -77224,7 +77257,6 @@ - @@ -77263,7 +77295,6 @@ - @@ -77342,6 +77373,7 @@ + @@ -77505,7 +77537,6 @@ - @@ -77537,7 +77568,6 @@ - @@ -77557,7 +77587,6 @@ - @@ -77609,7 +77638,7 @@ - + @@ -77647,6 +77676,7 @@ + @@ -77669,7 +77699,6 @@ - @@ -77701,7 +77730,6 @@ - @@ -77714,7 +77742,6 @@ - @@ -77725,11 +77752,13 @@ + + @@ -77754,6 +77783,7 @@ + @@ -77833,7 +77863,7 @@ - + @@ -77883,7 +77913,6 @@ - @@ -77930,7 +77959,6 @@ - @@ -77946,7 +77974,6 @@ - @@ -77979,6 +78006,7 @@ + @@ -78077,7 +78105,7 @@ - + @@ -78137,6 +78165,7 @@ + @@ -78147,7 +78176,7 @@ - + @@ -78167,6 +78196,7 @@ + @@ -78191,7 +78221,7 @@ - + @@ -78242,6 +78272,7 @@ + @@ -78304,6 +78335,7 @@ + @@ -78383,7 +78415,7 @@ - + @@ -78452,6 +78484,7 @@ + @@ -78514,6 +78547,7 @@ + @@ -78537,7 +78571,6 @@ - @@ -78616,6 +78649,7 @@ + @@ -78626,6 +78660,7 @@ + @@ -78710,12 +78745,12 @@ + - @@ -78777,7 +78812,6 @@ - @@ -78899,7 +78933,6 @@ - @@ -78985,7 +79018,7 @@ - + @@ -79101,7 +79134,7 @@ - + @@ -79148,6 +79181,7 @@ + @@ -79215,7 +79249,7 @@ - + @@ -79295,7 +79329,7 @@ - + @@ -79320,9 +79354,9 @@ - + @@ -79348,7 +79382,7 @@ - + @@ -79367,7 +79401,7 @@ - + @@ -79381,7 +79415,6 @@ - @@ -79400,12 +79433,10 @@ - - @@ -79448,6 +79479,7 @@ + @@ -79456,12 +79488,12 @@ - + @@ -79493,7 +79525,7 @@ - + @@ -79547,7 +79579,6 @@ - @@ -79572,7 +79603,7 @@ - + @@ -79602,6 +79633,7 @@ + @@ -79657,7 +79689,6 @@ - @@ -79670,7 +79701,6 @@ - @@ -79749,7 +79779,6 @@ - @@ -79763,6 +79792,7 @@ + @@ -79792,7 +79822,6 @@ - @@ -79961,7 +79990,6 @@ - @@ -80032,7 +80060,6 @@ - @@ -80054,6 +80081,7 @@ + @@ -80072,7 +80100,6 @@ - @@ -80082,6 +80109,7 @@ + @@ -80168,7 +80196,6 @@ - @@ -80203,7 +80230,6 @@ - @@ -80290,7 +80316,7 @@ - + @@ -80335,11 +80361,9 @@ - - @@ -80354,6 +80378,7 @@ + @@ -80425,7 +80450,6 @@ - @@ -80471,7 +80495,7 @@ - + @@ -80549,7 +80573,6 @@ - @@ -80637,7 +80660,7 @@ - + @@ -80690,6 +80713,7 @@ + @@ -80705,7 +80729,7 @@ - + @@ -80806,6 +80830,7 @@ + @@ -80827,6 +80852,7 @@ + @@ -80930,10 +80956,12 @@ + + @@ -81001,6 +81029,7 @@ + @@ -81079,7 +81108,6 @@ - @@ -81162,7 +81190,7 @@ - + @@ -81202,6 +81230,7 @@ + @@ -81218,7 +81247,7 @@ - + @@ -81257,12 +81286,12 @@ - + - + @@ -81401,7 +81430,6 @@ - @@ -81434,6 +81462,7 @@ + @@ -81453,6 +81482,7 @@ + @@ -81469,7 +81499,6 @@ - @@ -81496,7 +81525,7 @@ - + @@ -81592,7 +81621,6 @@ - @@ -81628,6 +81656,7 @@ + @@ -81685,6 +81714,7 @@ + @@ -81762,7 +81792,6 @@ - @@ -81847,7 +81876,6 @@ - @@ -81855,7 +81883,7 @@ - + @@ -81986,7 +82014,7 @@ - + @@ -81995,7 +82023,6 @@ - @@ -82004,13 +82031,15 @@ + + - + @@ -82047,7 +82076,6 @@ - @@ -82070,7 +82098,6 @@ - @@ -82122,6 +82149,7 @@ + @@ -82139,10 +82167,10 @@ + - @@ -82180,7 +82208,6 @@ - @@ -82202,7 +82229,7 @@ - + @@ -82219,7 +82246,7 @@ - + @@ -82260,6 +82287,7 @@ + @@ -82287,7 +82315,6 @@ - @@ -82357,6 +82384,7 @@ + @@ -82437,7 +82465,6 @@ - @@ -82462,6 +82489,7 @@ + @@ -82502,7 +82530,6 @@ - @@ -82544,6 +82571,7 @@ + @@ -82569,6 +82597,7 @@ + @@ -82621,7 +82650,6 @@ - @@ -82691,6 +82719,7 @@ + @@ -82698,6 +82727,7 @@ + @@ -82772,6 +82802,7 @@ + @@ -82875,6 +82906,7 @@ + @@ -82901,6 +82933,7 @@ + @@ -82912,7 +82945,6 @@ - @@ -82969,7 +83001,7 @@ - + @@ -83002,7 +83034,6 @@ - @@ -83081,7 +83112,6 @@ - @@ -83120,6 +83150,7 @@ + @@ -83148,6 +83179,7 @@ + @@ -83164,7 +83196,7 @@ - + @@ -83242,7 +83274,7 @@ - + @@ -83266,7 +83298,7 @@ - + @@ -83331,7 +83363,7 @@ - + @@ -83375,7 +83407,6 @@ - @@ -83447,6 +83478,7 @@ + @@ -83454,6 +83486,7 @@ + @@ -83474,6 +83507,7 @@ + @@ -83541,7 +83575,6 @@ - @@ -83559,6 +83592,7 @@ + @@ -83636,7 +83670,6 @@ - @@ -83759,7 +83792,6 @@ - @@ -83809,7 +83841,6 @@ - @@ -83868,14 +83899,13 @@ - - + @@ -83913,7 +83943,7 @@ - + @@ -83962,11 +83992,10 @@ - - + @@ -84025,7 +84054,7 @@ - + @@ -84057,6 +84086,7 @@ + @@ -84091,7 +84121,7 @@ - + @@ -84102,6 +84132,7 @@ + @@ -84135,10 +84166,8 @@ - - @@ -84204,10 +84233,10 @@ - + - + @@ -84294,7 +84323,6 @@ - @@ -84357,12 +84385,13 @@ + + - @@ -84431,7 +84460,6 @@ - @@ -84483,7 +84511,6 @@ - @@ -84498,7 +84525,6 @@ - @@ -84550,6 +84576,7 @@ + @@ -84588,7 +84615,7 @@ - + @@ -84602,11 +84629,12 @@ - + - + + @@ -84841,7 +84869,7 @@ - + @@ -84865,7 +84893,6 @@ - @@ -84900,9 +84927,8 @@ - - + @@ -84992,7 +85018,7 @@ - + @@ -85052,7 +85078,6 @@ - @@ -85073,6 +85098,7 @@ + @@ -85214,13 +85240,11 @@ - - @@ -85284,7 +85308,7 @@ - + @@ -85309,6 +85333,7 @@ + @@ -85334,7 +85359,6 @@ - @@ -85372,6 +85396,7 @@ + @@ -85379,7 +85404,7 @@ - + @@ -85397,6 +85422,7 @@ + @@ -85472,6 +85498,7 @@ + @@ -85546,7 +85573,6 @@ - @@ -85559,7 +85585,6 @@ - @@ -85592,7 +85617,7 @@ - + @@ -85609,7 +85634,6 @@ - @@ -85617,6 +85641,7 @@ + @@ -85664,13 +85689,11 @@ - - @@ -85701,6 +85724,7 @@ + @@ -85717,6 +85741,7 @@ + @@ -85780,6 +85805,7 @@ + @@ -85800,7 +85826,6 @@ - @@ -85839,6 +85864,7 @@ + @@ -85891,7 +85917,6 @@ - @@ -85966,7 +85991,6 @@ - @@ -85994,7 +86018,7 @@ - + @@ -86006,6 +86030,7 @@ + @@ -86023,6 +86048,7 @@ + @@ -86057,7 +86083,6 @@ - @@ -86107,9 +86132,12 @@ + + + @@ -86149,7 +86177,6 @@ - @@ -86158,7 +86185,6 @@ - @@ -86207,6 +86233,7 @@ + @@ -86216,6 +86243,7 @@ + @@ -86245,6 +86273,7 @@ + @@ -86256,7 +86285,6 @@ - @@ -86278,7 +86306,7 @@ - + @@ -86354,7 +86382,6 @@ - @@ -86435,7 +86462,6 @@ - @@ -86446,6 +86472,7 @@ + @@ -86464,6 +86491,7 @@ + @@ -86477,6 +86505,7 @@ + @@ -86501,7 +86530,7 @@ - + @@ -86532,7 +86561,6 @@ - @@ -86597,6 +86625,7 @@ + @@ -86666,11 +86695,14 @@ + + + @@ -86738,7 +86770,6 @@ - @@ -86753,7 +86784,6 @@ - @@ -86767,7 +86797,7 @@ - + @@ -86790,7 +86820,7 @@ - + @@ -86801,7 +86831,7 @@ - + @@ -86815,7 +86845,6 @@ - @@ -86866,7 +86895,6 @@ - @@ -86906,7 +86934,6 @@ - @@ -86920,7 +86947,6 @@ - @@ -86994,9 +87020,11 @@ - + + + @@ -87017,16 +87045,16 @@ - + - + - + @@ -87071,6 +87099,7 @@ + @@ -87086,7 +87115,6 @@ - @@ -87094,7 +87122,7 @@ - + @@ -87105,7 +87133,7 @@ - + @@ -87130,6 +87158,7 @@ + @@ -87139,7 +87168,6 @@ - @@ -87193,6 +87221,7 @@ + @@ -87201,8 +87230,8 @@ - + @@ -87223,9 +87252,10 @@ + + - @@ -87266,7 +87296,8 @@ - + + @@ -87347,6 +87378,7 @@ + @@ -87355,6 +87387,7 @@ + @@ -87431,7 +87464,6 @@ - @@ -87473,6 +87505,7 @@ + @@ -87488,7 +87521,6 @@ - @@ -87517,6 +87549,7 @@ + @@ -87569,6 +87602,7 @@ + @@ -87576,6 +87610,7 @@ + @@ -87664,6 +87699,7 @@ + @@ -87683,7 +87719,6 @@ - @@ -87720,7 +87755,6 @@ - @@ -87748,7 +87782,6 @@ - @@ -87799,7 +87832,6 @@ - @@ -87808,13 +87840,13 @@ + - @@ -87880,6 +87912,7 @@ + @@ -87889,7 +87922,7 @@ - + @@ -87944,6 +87977,7 @@ + @@ -88002,6 +88036,7 @@ + @@ -88017,8 +88052,7 @@ - - + @@ -88117,6 +88151,7 @@ + @@ -88159,6 +88194,7 @@ + @@ -88201,7 +88237,6 @@ - @@ -88254,7 +88289,7 @@ - + @@ -88287,6 +88322,7 @@ + @@ -88381,7 +88417,6 @@ - @@ -88400,6 +88435,7 @@ + @@ -88435,7 +88471,6 @@ - @@ -88492,7 +88527,9 @@ + + @@ -88518,7 +88555,6 @@ - @@ -88558,7 +88594,7 @@ - + @@ -88571,7 +88607,7 @@ - + @@ -88647,6 +88683,7 @@ + @@ -88667,7 +88704,7 @@ - + @@ -88689,8 +88726,6 @@ - - @@ -88710,8 +88745,9 @@ + - + @@ -88747,9 +88783,9 @@ - + @@ -88877,6 +88913,7 @@ + @@ -88885,7 +88922,6 @@ - @@ -88944,6 +88980,7 @@ + @@ -88990,6 +89027,7 @@ + @@ -89053,7 +89091,7 @@ - + @@ -89139,7 +89177,6 @@ - @@ -89172,7 +89209,6 @@ - @@ -89190,7 +89226,7 @@ - + @@ -89204,6 +89240,7 @@ + @@ -89254,7 +89291,6 @@ - @@ -89265,6 +89301,7 @@ + @@ -89301,6 +89338,7 @@ + @@ -89327,6 +89365,7 @@ + @@ -89358,6 +89397,7 @@ + @@ -89473,7 +89513,6 @@ - @@ -89502,6 +89541,7 @@ + @@ -89551,7 +89591,8 @@ - + + @@ -89573,12 +89614,12 @@ - + - + @@ -89687,6 +89728,7 @@ + @@ -89864,12 +89906,11 @@ - - + @@ -89959,8 +90000,9 @@ - + + @@ -89970,12 +90012,14 @@ + + @@ -89986,7 +90030,6 @@ - @@ -89996,10 +90039,10 @@ + - - + @@ -90017,13 +90060,14 @@ + - + @@ -90079,6 +90123,7 @@ + @@ -90118,6 +90163,7 @@ + @@ -90127,7 +90173,6 @@ - @@ -90264,7 +90309,6 @@ - @@ -90412,6 +90456,7 @@ + @@ -90443,6 +90488,7 @@ + @@ -90571,7 +90617,7 @@ - + @@ -90623,6 +90669,7 @@ + @@ -90658,7 +90705,7 @@ - + @@ -90709,7 +90756,6 @@ - @@ -90718,14 +90764,15 @@ + + - @@ -90740,7 +90787,7 @@ - + @@ -90781,6 +90828,7 @@ + @@ -90788,7 +90836,7 @@ - + @@ -90899,6 +90947,7 @@ + @@ -90915,7 +90964,6 @@ - @@ -90941,6 +90989,7 @@ + @@ -90970,7 +91019,6 @@ - @@ -91015,6 +91063,7 @@ + @@ -91096,7 +91145,6 @@ - @@ -91169,7 +91217,7 @@ - + @@ -91216,7 +91264,6 @@ - @@ -91306,7 +91353,7 @@ - + @@ -91314,7 +91361,6 @@ - @@ -91338,7 +91384,7 @@ - + @@ -91362,6 +91408,7 @@ + @@ -91390,6 +91437,7 @@ + @@ -91439,6 +91487,7 @@ + @@ -91519,11 +91568,11 @@ + - @@ -91572,7 +91621,7 @@ - + @@ -91645,7 +91694,6 @@ - @@ -91653,9 +91701,9 @@ - + @@ -91784,7 +91832,6 @@ - @@ -91827,7 +91874,7 @@ - + @@ -91859,7 +91906,7 @@ - + @@ -91908,7 +91955,7 @@ - + @@ -91927,9 +91974,10 @@ - + + @@ -91941,7 +91989,7 @@ - + @@ -92055,7 +92103,6 @@ - @@ -92066,8 +92113,9 @@ - + + @@ -92082,6 +92130,7 @@ + @@ -92131,7 +92180,7 @@ - + @@ -92246,7 +92295,6 @@ - @@ -92384,10 +92432,10 @@ + - @@ -92441,6 +92489,7 @@ + @@ -92529,7 +92578,6 @@ - @@ -92545,7 +92593,6 @@ - @@ -92591,6 +92638,7 @@ + @@ -92605,7 +92653,7 @@ - + @@ -92659,7 +92707,7 @@ - + @@ -92711,7 +92759,7 @@ - + @@ -92732,7 +92780,6 @@ - @@ -92781,6 +92828,7 @@ + @@ -92937,14 +92985,12 @@ - - @@ -92992,7 +93038,6 @@ - @@ -93105,7 +93150,6 @@ - @@ -93142,7 +93186,6 @@ - @@ -93190,6 +93233,7 @@ + @@ -93230,6 +93274,7 @@ + @@ -93390,7 +93435,7 @@ - + @@ -93410,6 +93455,7 @@ + @@ -93459,7 +93505,6 @@ - @@ -93481,7 +93526,6 @@ - @@ -93540,7 +93584,6 @@ - @@ -93562,6 +93605,7 @@ + @@ -93645,6 +93689,7 @@ + @@ -93724,7 +93769,6 @@ - @@ -93819,6 +93863,7 @@ + @@ -93846,7 +93891,7 @@ - + @@ -93863,7 +93908,6 @@ - @@ -93883,6 +93927,7 @@ + @@ -93898,6 +93943,7 @@ + @@ -93919,7 +93965,6 @@ - @@ -93945,7 +93990,6 @@ - @@ -93973,7 +94017,6 @@ - @@ -94032,7 +94075,7 @@ - + @@ -94068,6 +94111,7 @@ + @@ -94088,7 +94132,6 @@ - @@ -94099,7 +94142,6 @@ - @@ -94122,6 +94164,7 @@ + @@ -94201,7 +94244,6 @@ - @@ -94223,6 +94265,7 @@ + @@ -94270,7 +94313,7 @@ - + @@ -94371,6 +94414,7 @@ + @@ -94404,6 +94448,7 @@ + @@ -94498,7 +94543,7 @@ - + @@ -94512,6 +94557,7 @@ + @@ -94561,7 +94607,6 @@ - @@ -94629,7 +94674,6 @@ - @@ -94687,7 +94731,6 @@ - @@ -94707,6 +94750,7 @@ + @@ -94741,7 +94785,9 @@ + + @@ -94820,7 +94866,7 @@ - + @@ -94828,7 +94874,7 @@ - + @@ -94852,7 +94898,6 @@ - @@ -94964,9 +95009,9 @@ - + @@ -94991,6 +95036,7 @@ + @@ -95025,6 +95071,7 @@ + @@ -95053,7 +95100,6 @@ - @@ -95108,6 +95154,7 @@ + @@ -95135,7 +95182,6 @@ - @@ -95169,7 +95215,6 @@ - @@ -95318,6 +95363,7 @@ + @@ -95401,7 +95447,7 @@ - + @@ -95447,6 +95493,7 @@ + @@ -95500,10 +95547,10 @@ + - @@ -95531,6 +95578,7 @@ + @@ -95603,6 +95651,7 @@ + @@ -95761,7 +95810,6 @@ - @@ -95771,11 +95819,10 @@ - + - @@ -95807,6 +95854,7 @@ + @@ -95826,7 +95874,6 @@ - @@ -95934,6 +95981,7 @@ + @@ -95980,7 +96028,7 @@ - + @@ -96013,6 +96061,7 @@ + @@ -96024,10 +96073,9 @@ - - + @@ -96058,13 +96106,10 @@ - - - @@ -96086,7 +96131,7 @@ - + @@ -96130,7 +96175,6 @@ - @@ -96177,7 +96221,6 @@ - @@ -96185,7 +96228,6 @@ - @@ -96220,13 +96262,11 @@ - - @@ -96277,7 +96317,6 @@ - @@ -96318,7 +96357,6 @@ - @@ -96368,6 +96406,7 @@ + @@ -96384,7 +96423,7 @@ - + @@ -96491,12 +96530,13 @@ - + + @@ -96527,6 +96567,7 @@ + @@ -96720,9 +96761,10 @@ - + + @@ -96752,12 +96794,12 @@ - + - + @@ -96892,7 +96934,6 @@ - @@ -96901,7 +96942,7 @@ - + @@ -96984,7 +97025,7 @@ - + @@ -97006,9 +97047,11 @@ + + @@ -97018,7 +97061,6 @@ - @@ -97074,6 +97116,7 @@ + @@ -97084,6 +97127,7 @@ + @@ -97106,7 +97150,6 @@ - @@ -97176,7 +97219,6 @@ - @@ -97204,12 +97246,12 @@ - + @@ -97315,7 +97357,6 @@ - @@ -97342,12 +97383,10 @@ - - @@ -97386,8 +97425,9 @@ - + + @@ -97416,7 +97456,7 @@ - + @@ -97433,6 +97473,7 @@ + @@ -97462,10 +97503,8 @@ - - @@ -97542,7 +97581,7 @@ - + @@ -97553,7 +97592,6 @@ - @@ -97570,7 +97608,7 @@ - + @@ -97589,7 +97627,7 @@ - + @@ -97633,7 +97671,7 @@ - + @@ -97649,7 +97687,6 @@ - @@ -97671,6 +97708,7 @@ + @@ -97751,11 +97789,9 @@ - - @@ -97776,7 +97812,6 @@ - @@ -97827,6 +97862,7 @@ + @@ -97867,6 +97903,7 @@ + @@ -97941,12 +97978,12 @@ + - @@ -97966,6 +98003,7 @@ + @@ -98053,7 +98091,7 @@ - + @@ -98086,6 +98124,7 @@ + @@ -98095,9 +98134,11 @@ + + @@ -98142,7 +98183,6 @@ - @@ -98209,6 +98249,7 @@ + @@ -98230,7 +98271,6 @@ - @@ -98256,6 +98296,7 @@ + @@ -98348,7 +98389,6 @@ - @@ -98363,6 +98403,7 @@ + @@ -98383,7 +98424,7 @@ - + @@ -98429,7 +98470,7 @@ - + @@ -98448,7 +98489,7 @@ - + @@ -98458,7 +98499,7 @@ - + @@ -98512,6 +98553,7 @@ + @@ -98538,6 +98580,7 @@ + @@ -98576,6 +98619,7 @@ + @@ -98635,6 +98679,7 @@ + @@ -98658,7 +98703,6 @@ - @@ -98681,13 +98725,13 @@ - + @@ -98695,7 +98739,7 @@ - + @@ -98706,8 +98750,10 @@ + + @@ -98789,6 +98835,7 @@ + @@ -98849,7 +98896,6 @@ - @@ -98861,11 +98907,10 @@ - - + @@ -98882,6 +98927,7 @@ + @@ -98929,6 +98975,7 @@ + @@ -98992,7 +99039,7 @@ - + @@ -99025,7 +99072,7 @@ - + @@ -99051,6 +99098,7 @@ + @@ -99073,18 +99121,17 @@ - - + @@ -99114,6 +99161,7 @@ + @@ -99165,7 +99213,6 @@ - @@ -99176,7 +99223,6 @@ - @@ -99186,7 +99232,6 @@ - @@ -99218,7 +99263,7 @@ - + @@ -99274,7 +99319,6 @@ - @@ -99409,7 +99453,7 @@ - + @@ -99430,7 +99474,6 @@ - @@ -99448,7 +99491,7 @@ - + @@ -99531,6 +99574,7 @@ + @@ -99619,7 +99663,6 @@ - @@ -99708,7 +99751,6 @@ - @@ -99754,7 +99796,6 @@ - @@ -99809,7 +99850,6 @@ - @@ -99840,7 +99880,6 @@ - @@ -99880,7 +99919,7 @@ - + @@ -99967,7 +100006,6 @@ - @@ -100001,7 +100039,6 @@ - @@ -100064,8 +100101,8 @@ - + @@ -100084,7 +100121,7 @@ - + @@ -100118,12 +100155,13 @@ - + + @@ -100162,7 +100200,7 @@ - + @@ -100204,7 +100242,7 @@ - + @@ -100243,7 +100281,7 @@ - + @@ -100269,7 +100307,6 @@ - @@ -100324,7 +100361,6 @@ - @@ -100371,6 +100407,7 @@ + @@ -100378,6 +100415,7 @@ + @@ -100388,7 +100426,6 @@ - @@ -100399,12 +100436,12 @@ - + @@ -100423,13 +100460,11 @@ - - @@ -100481,7 +100516,7 @@ - + @@ -100511,6 +100546,7 @@ + @@ -100644,7 +100680,6 @@ - @@ -100755,7 +100790,6 @@ - @@ -100798,7 +100832,6 @@ - @@ -100811,7 +100844,6 @@ - @@ -100826,6 +100858,7 @@ + @@ -100834,7 +100867,6 @@ - @@ -100886,7 +100918,6 @@ - @@ -100916,6 +100947,7 @@ + @@ -100942,6 +100974,7 @@ + @@ -100955,6 +100988,7 @@ + @@ -100963,7 +100997,6 @@ - @@ -101036,19 +101069,22 @@ - + + + + @@ -101072,7 +101108,7 @@ - + @@ -101110,7 +101146,6 @@ - @@ -101124,7 +101159,9 @@ + + @@ -101133,6 +101170,7 @@ + @@ -101143,12 +101181,14 @@ + + @@ -101161,15 +101201,14 @@ - + - @@ -101205,12 +101244,9 @@ - - - @@ -101260,7 +101296,9 @@ + + @@ -101297,7 +101335,6 @@ - @@ -101354,6 +101391,7 @@ + @@ -101390,11 +101428,13 @@ + + @@ -101406,9 +101446,9 @@ - + @@ -101423,7 +101463,6 @@ - @@ -101437,10 +101476,10 @@ - + @@ -101464,6 +101503,7 @@ + @@ -101480,7 +101520,6 @@ - @@ -101488,7 +101527,6 @@ - @@ -101499,12 +101537,10 @@ - - @@ -101523,6 +101559,7 @@ + @@ -101549,6 +101586,7 @@ + @@ -101558,6 +101596,7 @@ + @@ -101642,6 +101681,7 @@ + @@ -101702,10 +101742,12 @@ + + @@ -101714,7 +101756,6 @@ - @@ -101742,6 +101783,7 @@ + @@ -101752,6 +101794,7 @@ + @@ -101792,15 +101835,13 @@ - + - - @@ -101821,6 +101862,8 @@ + + @@ -101829,6 +101872,7 @@ + @@ -101847,20 +101891,17 @@ - - - - + @@ -101884,7 +101925,6 @@ - @@ -101897,7 +101937,6 @@ - @@ -101908,6 +101947,7 @@ + @@ -101974,9 +102014,9 @@ - + @@ -102011,6 +102051,7 @@ + @@ -102027,6 +102068,7 @@ + @@ -102057,10 +102099,10 @@ - + @@ -102075,7 +102117,7 @@ - + @@ -102083,8 +102125,8 @@ - + @@ -102097,6 +102139,7 @@ + @@ -102110,6 +102153,7 @@ + @@ -102120,6 +102164,7 @@ + @@ -102128,16 +102173,13 @@ - - - @@ -102167,24 +102209,26 @@ + + + - + - @@ -102234,7 +102278,6 @@ - @@ -102247,6 +102290,7 @@ + @@ -102279,7 +102323,9 @@ + + @@ -102290,6 +102336,7 @@ + @@ -102298,7 +102345,6 @@ - @@ -102307,7 +102353,6 @@ - @@ -102319,8 +102364,10 @@ + + @@ -102343,7 +102390,7 @@ - + @@ -102359,6 +102406,7 @@ + @@ -102395,8 +102443,8 @@ - + @@ -102408,6 +102456,7 @@ + @@ -102444,9 +102493,8 @@ + - - @@ -102472,6 +102520,7 @@ + @@ -102482,17 +102531,17 @@ + + - - @@ -102527,12 +102576,14 @@ + + @@ -102559,7 +102610,6 @@ - @@ -102577,7 +102627,6 @@ - @@ -102599,6 +102648,7 @@ + @@ -102614,6 +102664,7 @@ + @@ -102639,13 +102690,12 @@ - - + @@ -102668,7 +102718,6 @@ - @@ -102700,6 +102749,7 @@ + @@ -102735,6 +102785,7 @@ + @@ -102770,7 +102821,6 @@ - @@ -102779,7 +102829,6 @@ - @@ -102788,14 +102837,17 @@ + + + @@ -102839,8 +102891,8 @@ - + @@ -102852,6 +102904,7 @@ + @@ -102870,7 +102923,6 @@ - @@ -102879,7 +102931,6 @@ - @@ -102889,7 +102940,6 @@ - @@ -102954,15 +103004,16 @@ + - + @@ -102971,6 +103022,7 @@ + @@ -102988,9 +103040,11 @@ + + @@ -103018,13 +103072,14 @@ + - + @@ -103065,7 +103120,6 @@ - @@ -103082,10 +103136,12 @@ + + @@ -103095,6 +103151,7 @@ + @@ -103130,7 +103187,6 @@ - @@ -103147,7 +103203,6 @@ - @@ -103159,7 +103214,6 @@ - @@ -103179,7 +103233,6 @@ - @@ -103217,6 +103270,8 @@ + + @@ -103227,11 +103282,11 @@ + - @@ -103255,6 +103310,7 @@ + @@ -103279,6 +103335,7 @@ + @@ -103287,6 +103344,7 @@ + @@ -103310,6 +103368,7 @@ + @@ -103324,9 +103383,11 @@ + + @@ -103336,7 +103397,6 @@ - @@ -103354,10 +103414,8 @@ - - @@ -103372,9 +103430,9 @@ - + @@ -103383,7 +103441,6 @@ - @@ -103403,7 +103460,6 @@ - @@ -103418,7 +103474,6 @@ - @@ -103431,13 +103486,12 @@ + - - @@ -103488,17 +103542,18 @@ + + + - - @@ -103514,6 +103569,7 @@ + @@ -103534,6 +103590,7 @@ + @@ -103588,6 +103645,7 @@ + @@ -103596,7 +103654,6 @@ - @@ -103620,6 +103677,7 @@ + @@ -103641,7 +103699,6 @@ - @@ -103662,6 +103719,7 @@ + @@ -103685,6 +103743,7 @@ + @@ -103699,16 +103758,14 @@ + - - - @@ -103756,6 +103813,7 @@ + @@ -103764,7 +103822,6 @@ - @@ -103791,7 +103848,6 @@ - @@ -103846,16 +103902,15 @@ - - + + - @@ -103882,6 +103937,7 @@ + @@ -103911,6 +103967,7 @@ + @@ -103930,6 +103987,7 @@ + @@ -103956,6 +104014,7 @@ + @@ -103976,7 +104035,6 @@ - @@ -103993,7 +104051,6 @@ - @@ -104009,11 +104066,13 @@ + + @@ -104026,6 +104085,7 @@ + @@ -104039,6 +104099,7 @@ + @@ -104056,6 +104117,7 @@ + @@ -104068,6 +104130,8 @@ + + @@ -104081,7 +104145,6 @@ - @@ -104095,7 +104158,6 @@ - @@ -104106,25 +104168,24 @@ - + - + - @@ -104139,6 +104200,7 @@ + @@ -104156,7 +104218,6 @@ - @@ -104189,6 +104250,7 @@ + @@ -104206,6 +104268,7 @@ + @@ -104230,10 +104293,10 @@ - + @@ -104274,6 +104337,7 @@ + @@ -104283,7 +104347,6 @@ - @@ -104310,6 +104373,7 @@ + @@ -104331,6 +104395,7 @@ + @@ -104357,11 +104422,11 @@ + - - + @@ -104372,10 +104437,12 @@ + + @@ -104419,7 +104486,6 @@ - @@ -104438,14 +104504,15 @@ + + - @@ -104454,12 +104521,10 @@ - + - - @@ -104484,7 +104549,6 @@ - @@ -104494,19 +104558,18 @@ - + - - + @@ -104518,6 +104581,7 @@ + @@ -104532,6 +104596,7 @@ + @@ -104553,9 +104618,11 @@ + + @@ -104572,9 +104639,9 @@ + - @@ -104600,6 +104667,7 @@ + @@ -104612,7 +104680,9 @@ + + @@ -104625,8 +104695,8 @@ + - @@ -104639,6 +104709,7 @@ + diff --git a/Engine/Config/Android/AndroidEngine.ini b/Engine/Config/Android/AndroidEngine.ini index d7db8e015dec..dca4f21c1f11 100644 --- a/Engine/Config/Android/AndroidEngine.ini +++ b/Engine/Config/Android/AndroidEngine.ini @@ -81,4 +81,24 @@ r.Shadow.ForceSerialSingleRenderPass=1 [OnlineSubsystemGooglePlay.Store] bSupportsInAppPurchasing=true bUseStoreV2=true -bUseGooglePlayBillingApiV2=true \ No newline at end of file +bUseGooglePlayBillingApiV2=true + +[ThermalSensors] ++SensorLocations=/sys/devices/virtual/thermal/thermal_zone1/temp ++SensorLocations=/sys/devices/system/cpu/cpu0/cpufreq/cpu_temp ++SensorLocations=/sys/class/thermal/thermal_zone0/temp ++SensorLocations=/sys/class/thermal/thermal_zone1/temp ++SensorLocations=/sys/class/hwmon/hwmon0/device/temp1_input ++SensorLocations=/sys/class/hwmon/hwmon0/temp1_input ++SensorLocations=/sys/devices/virtual/hwmon/hwmon1/temp1_input ++SensorLocations=/sys/devices/system/cpu/cpu0/cpufreq/FakeShmoo_cpu_temp ++SensorLocations=/sys/class/i2c-adapter/i2c-4/4-004c/temperature ++SensorLocations=/sys/devices/platform/tegra-i2c.3/i2c-4/4-004c/temperature ++SensorLocations=/sys/devices/platform/tegra-i2c.3/i2c-4/4-004c/ext_temperature ++SensorLocations=/sys/devices/platform/omap/omap_temp_sensor.0/temperature ++SensorLocations=/sys/devices/platform/tegra_tmon/temp1_input ++SensorLocations=/sys/kernel/debug/tegra_thermal/temp_tj ++SensorLocations=/sys/devices/platform/tegra-tsensor/tsensor_temperature ++SensorLocations=/sys/devices/platform/s5p-tmu/temperature ++SensorLocations=/sys/devices/platform/s5p-tmu/curr_temp ++SensorLocations=/sys/htc/cpu_temp diff --git a/Engine/Config/Android/DataDrivenPlatformInfo.ini b/Engine/Config/Android/DataDrivenPlatformInfo.ini index be42f16eb661..cddd8a4fd905 100644 --- a/Engine/Config/Android/DataDrivenPlatformInfo.ini +++ b/Engine/Config/Android/DataDrivenPlatformInfo.ini @@ -14,3 +14,30 @@ bUsesHostCompiler=false bUATClosesAfterLaunch=true PlatformGroupName=Mobile + +[PreviewPlatform AndroidES31] +EnabledCVar=ini:Engine:Android:/Script/AndroidRuntimeSettings.AndroidRuntimeSettings:bBuildForES31 +ShaderFormat=GLSL_ES3_1_ANDROID +ActiveIconName=LevelEditor.PreviewMode.AndroidES31.Enabled +InactiveIconName=LevelEditor.PreviewMode.AndroidES31.Disabled +MenuText=NSLOCTEXT("PreviewPlatform", "PreviewMenuText_ES31", "Android ES 3.1") +MenuTooltip=NSLOCTEXT("PreviewPlatform", "PreviewMenuTooltip_ES31", "Mobile preview using Android ES3.1 quality settings.") +IconText=NSLOCTEXT("PreviewPlatform", "PreviewIconText_ES31", "Android ES3.1") + +[PreviewPlatform AndroidVulkan] +EnabledCVar=ini:Engine:Android:/Script/AndroidRuntimeSettings.AndroidRuntimeSettings:bSupportsVulkan +ShaderFormat=SF_VULKAN_ES31_ANDROID +ActiveIconName=LevelEditor.PreviewMode.AndroidVulkan.Enabled +InactiveIconName=LevelEditor.PreviewMode.AndroidVulkan.Disabled +MenuText=NSLOCTEXT("PreviewPlatform", "PreviewMenuText_AndroidVulkan", "Android Vulkan") +MenuTooltip=NSLOCTEXT("PreviewPlatform", "PreviewMenuTooltip_AndroidVulkan", "Mobile preview using Android Vulkan quality settings.") +IconText=NSLOCTEXT("PreviewPlatform", "PreviewIconText_AndroidVulkan", "Android Vulkan") + +[PreviewPlatform AndroidVulkanSM5] +EnabledCVar=ini:Engine:Android:/Script/AndroidRuntimeSettings.AndroidRuntimeSettings:bSupportsVulkanSM5 +ShaderFormat=SF_VULKAN_SM5_ANDROID +ActiveIconName=LevelEditor.PreviewMode.AndroidVulkanSM5.Enabled +InactiveIconName=LevelEditor.PreviewMode.AndroidVulkanSM5.Disabled +MenuText=NSLOCTEXT("PreviewPlatform", "PreviewMenuText_AndroidVulkanSM5", "Android Vulkan SM5") +MenuTooltip=NSLOCTEXT("PreviewPlatform", "PreviewMenuTooltip_AndroidVulkanSM5", "Mobile preview using Android Vulkan SM5 quality settings.") +IconText=NSLOCTEXT("PreviewPlatform", "PreviewIconText_AndroidVulkanSM5", "Android VK SM5") diff --git a/Engine/Config/BaseEngine.ini b/Engine/Config/BaseEngine.ini index 0067c04db481..a697d77add26 100644 --- a/Engine/Config/BaseEngine.ini +++ b/Engine/Config/BaseEngine.ini @@ -33,9 +33,19 @@ HttpSendTimeout=30 bAllowSeekFunction=false [BackgroundHttp] +; How many BackgroundHTTP tasks can be running simultaneously. Turning this number too high may cause timeouts +MaxActiveDownloads=4 + ; How many days we will wait to delete any found BackgroundHTTP Temp Files that haven't been claimed. Default is 259200 seconds (3 days). -1 to disable. -BackgroundHttp.TempFileTimeOutSeconds=259200 -BackgroundHttp.MaxActiveDownloads=4 +TempFileTimeOutSeconds=259200 + +; If we should delete any Temp Files in our BackgroundHTTP temp folder that don't have associated URL Mapping entries. +; This means we don't really know what URL this temp work belongs to anymore +DeleteTempFilesWithoutURLMappingEntries=true + +; If we should remove URL Mapping Entries on launch for any entries that don't have a corresponding temp file +; This could be stale data from a previous run that was either never actually used, wasn't saved after the temp file was moved, etc. +RemoveURLMappingEntriesWithoutPhysicalTempFiles=true [WebSockets.LibWebSockets] ThreadStackSize=131072 @@ -2237,7 +2247,7 @@ bTarget32Bit=false [/Script/MacTargetPlatform.MacTargetSettings] -MaxShaderLanguageVersion=3 +MaxShaderLanguageVersion=4 +TargetedRHIs=SF_METAL_SM5 UseFastIntrinsics=False ForceFloats=False diff --git a/Engine/Config/BaseScalability.ini b/Engine/Config/BaseScalability.ini index 31fdbea6899c..0d152ee78f61 100644 --- a/Engine/Config/BaseScalability.ini +++ b/Engine/Config/BaseScalability.ini @@ -169,10 +169,9 @@ r.CapsuleShadows=1 [PostProcessQuality@0] r.MotionBlurQuality=0 -r.AmbientOcclusionMipLevelFactor=1.0 -r.AmbientOcclusionMaxQuality=0 +r.AmbientOcclusion.Method=1 +r.GTAO.Downsample=1 r.AmbientOcclusionLevels=0 -r.AmbientOcclusionRadiusScale=1.2 r.DepthOfFieldQuality=0 r.RenderTargetPoolMin=300 r.LensFlareQuality=0 @@ -188,10 +187,10 @@ r.Tonemapper.Quality=0 [PostProcessQuality@1] r.MotionBlurQuality=3 -r.AmbientOcclusionMipLevelFactor=1.0 -r.AmbientOcclusionMaxQuality=60 +r.AmbientOcclusion.Method=1 r.AmbientOcclusionLevels=-1 -r.AmbientOcclusionRadiusScale=1.5 +r.AmbientOcclusionMaxQuality=20 +r.GTAO.Downsample=1 r.DepthOfFieldQuality=1 r.RenderTargetPoolMin=350 r.LensFlareQuality=0 @@ -219,10 +218,10 @@ r.DOF.Kernel.MaxBackgroundRadius=0.006 ; required because low gathering and no [PostProcessQuality@2] r.MotionBlurQuality=3 -r.AmbientOcclusionMipLevelFactor=0.6 -r.AmbientOcclusionMaxQuality=100 +r.AmbientOcclusion.Method=1 r.AmbientOcclusionLevels=-1 -r.AmbientOcclusionRadiusScale=1.5 +r.AmbientOcclusionMaxQuality=20 +r.GTAO.Downsample=0 r.DepthOfFieldQuality=2 r.RenderTargetPoolMin=400 r.LensFlareQuality=2 @@ -252,10 +251,10 @@ r.DOF.Kernel.MaxBackgroundRadius=0.012 ; required because of AccumulatorQualit [PostProcessQuality@3] r.MotionBlurQuality=4 -r.AmbientOcclusionMipLevelFactor=0.4 -r.AmbientOcclusionMaxQuality=100 +r.AmbientOcclusion.Method=1 r.AmbientOcclusionLevels=-1 -r.AmbientOcclusionRadiusScale=1.0 +r.AmbientOcclusionMaxQuality=40 +r.GTAO.Downsample=0 r.DepthOfFieldQuality=2 r.RenderTargetPoolMin=400 r.LensFlareQuality=2 @@ -287,10 +286,11 @@ r.DOF.Kernel.MaxBackgroundRadius=0.025 [PostProcessQuality@Cine] r.MotionBlurQuality=4 -r.AmbientOcclusionMipLevelFactor=0.4 -r.AmbientOcclusionMaxQuality=100 +r.AmbientOcclusion.Method=1 r.AmbientOcclusionLevels=-1 -r.AmbientOcclusionRadiusScale=1.0 +r.AmbientOcclusionMaxQuality=100 +r.GTAO.Downsample=0 +r.GTAO.Numangles=4 r.DepthOfFieldQuality=4 r.RenderTargetPoolMin=1000 r.LensFlareQuality=3 @@ -408,6 +408,7 @@ r.SkyAtmosphere.SampleCountMax=16.0 r.SkyAtmosphere.TransmittanceLUT.UseSmallFormat=1 r.SkyAtmosphere.TransmittanceLUT.SampleCount=10.0 r.SkyAtmosphere.MultiScatteringLUT.SampleCount=15.0 +r.SkyLight.RealTimeReflectionCapture=0 fx.Niagara.QualityLevel=0 [EffectsQuality@1] @@ -437,6 +438,7 @@ r.SkyAtmosphere.SampleCountMax=32.0 r.SkyAtmosphere.TransmittanceLUT.UseSmallFormat=0 r.SkyAtmosphere.TransmittanceLUT.SampleCount=10.0 r.SkyAtmosphere.MultiScatteringLUT.SampleCount=15.0 +r.SkyLight.RealTimeReflectionCapture=0 fx.Niagara.QualityLevel=1 [EffectsQuality@2] diff --git a/Engine/Config/IOS/DataDrivenPlatformInfo.ini b/Engine/Config/IOS/DataDrivenPlatformInfo.ini index de79f1295ee1..96fa52e61f6a 100644 --- a/Engine/Config/IOS/DataDrivenPlatformInfo.ini +++ b/Engine/Config/IOS/DataDrivenPlatformInfo.ini @@ -15,3 +15,20 @@ Linux:bIsEnabled=false bUsesHostCompiler=false bUATClosesAfterLaunch=true PlatformGroupName=Mobile + +[PreviewPlatform IOSMetal] +ShaderFormat=SF_METAL +ActiveIconName=LevelEditor.PreviewMode.iOS.Enabled +InactiveIconName=LevelEditor.PreviewMode.iOS.Disabled +MenuText=NSLOCTEXT("PreviewPlatform", "PreviewMenuText_iOS", "iOS") +MenuTooltip=NSLOCTEXT("PreviewPlatform", "PreviewMenuTooltip_iOS", "Mobile preview using iOS material quality settings.") +IconText=NSLOCTEXT("PreviewPlatform", "PreviewIconText_iOS", "iOS") + +[PreviewPlatform IOSMetalSM5] +ShaderFormat=SF_METAL_MRT +EnabledCVar=ini:Engine:IOS:/Script/IOSRuntimeSettings.IOSRuntimeSettings:bSupportsMetalMRT +ActiveIconName=LevelEditor.PreviewMode.iOSSM5.Enabled +InactiveIconName=LevelEditor.PreviewMode.iOSSM5.Disabled +MenuText=NSLOCTEXT("PreviewPlatform", "PreviewMenuText_iOSSM5", "iOS (SM5 Renderer)") +MenuTooltip=NSLOCTEXT("PreviewPlatform", "PreviewMenuTooltip_iOSSM5", "Mobile preview using iOS SM5 material quality settings.") +IconText=NSLOCTEXT("PreviewPlatform", "PreviewIconText_iOSSM5", "iOS SM5") diff --git a/Engine/Config/IOS/IOSEngine.ini b/Engine/Config/IOS/IOSEngine.ini index 2afc02a44f8e..8c397a8418aa 100644 --- a/Engine/Config/IOS/IOSEngine.ini +++ b/Engine/Config/IOS/IOSEngine.ini @@ -61,13 +61,13 @@ MaxFlushTimeSeconds=2.0 [BackgroundHttp.iOSSettings] ;While the app is in the forground, time out if we don't get a response in this timeframe -BackgroundHttp.ActiveReceiveTimeout=30 +ActiveReceiveTimeout=30 ;While the app is in the background, time out if we don't get a response in this timeframe -BackgroundHttp.BackgroundReceiveTimeout=120 +BackgroundReceiveTimeout=120 ;Regardless of app state, if the download hasn't finished in this long, time out -BackgroundHttp.BackgroundHttpResourceTimeout=3600 +BackgroundHttpResourceTimeout=3600 ;If retry data exists for a task, how many times we should try using the Retry data before moving onto a different CDN. ( -1 = unlimited ) -BackgroundHttp.RetryResumeDataLimit=3; +RetryResumeDataLimit=3; [Plugins] +EnabledPlugins=Crashlytics diff --git a/Engine/Documentation/Source/Programming/UnrealBuildSystem/Configuration/BuildConfigProperties/BuildConfigProperties.INT.udn b/Engine/Documentation/Source/Programming/UnrealBuildSystem/Configuration/BuildConfigProperties/BuildConfigProperties.INT.udn index 834661b90983..f5acb6f864e9 100644 --- a/Engine/Documentation/Source/Programming/UnrealBuildSystem/Configuration/BuildConfigProperties/BuildConfigProperties.INT.udn +++ b/Engine/Documentation/Source/Programming/UnrealBuildSystem/Configuration/BuildConfigProperties/BuildConfigProperties.INT.udn @@ -6,7 +6,8 @@ Version: 4.26 ### BuildConfiguration -$ bGeneratedSYMFile : Whether to generate dSYM files. Lists Architectures that you want to build. +$ bGeneratedSYMFile : Whether to generate dSYM files. Defaults to true for Shipping builds and false for all other builds. -dsym will force generation of a dsym for other builds, + * NoDsym will force it off for shipping. $ bEnableAddressSanitizer : Enables address sanitizer (ASan) diff --git a/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/framework.ts b/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/framework.ts index 5ceb102a1c4d..96b923cd7d2a 100644 --- a/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/framework.ts +++ b/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/framework.ts @@ -183,6 +183,7 @@ type EdgeOptionFields = { disallowSkip: boolean incognitoMode: boolean + terminal: boolean // changes go along terminal edges but no further excludeAuthors: string[] } @@ -271,9 +272,10 @@ export abstract class FunctionalTest { } protected makeBranchDef(stream: string, to: string[], forceAll?: boolean): RobomergeBranchSpec { + const name = this.fullBranchName(stream) return { streamDepot: this.testName, - name: this.fullBranchName(stream), + name, aliases: [name + '_alias'], streamName: stream, flowsTo: to.map(str => this.fullBranchName(str)), forceAll: !!forceAll @@ -725,7 +727,7 @@ export abstract class FunctionalTest { const status = branchState.getStatusMessage() if (status) { if (dump) { - this.verbose(`${node} status: ${status} (active: ${branchState.isActive()})`) + this.verbose(`${node} status: ${status} (active: ${branchState.isActive()} - ${branchState.getStatusMessage()})`) } return false } diff --git a/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/tests.ts b/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/tests.ts index 9c8d68ce7bac..5fd598f08fc0 100644 --- a/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/tests.ts +++ b/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/tests.ts @@ -37,6 +37,7 @@ import { TestGate } from './tests/test-gate' import { EdgeInitialCl } from './tests/edge-initial-cl' import { TestReconsider } from './tests/test-reconsider' import { TestEdgeReconsider } from './tests/test-edge-reconsider' +import { TestTerminal } from './tests/test-terminal' import { CrossBotTest, CrossBotTest2 } from './tests/cross-bot' @@ -146,6 +147,7 @@ async function go() { new TestEdgeReconsider(p4), // 30 new BlockAssets(p4), + new TestTerminal(p4), // these two must be consecutive new CrossBotTest(p4), @@ -178,7 +180,7 @@ async function go() { /////////////////////// // TESTS TO RUN - const tests = /*/[availableTests[31]]/*/availableTests/**/ + const tests = /*/[availableTests[10]]/*/availableTests/**/ // /////////////////////// diff --git a/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/tests/test-terminal.ts b/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/tests/test-terminal.ts new file mode 100644 index 000000000000..3a29b0f92786 --- /dev/null +++ b/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/tests/test-terminal.ts @@ -0,0 +1,37 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +import { EdgeProperties, P4Util } from '../framework' +import { MultipleDevAndReleaseTestBase } from '../MultipleDevAndReleaseTestBase' + +export class TestTerminal extends MultipleDevAndReleaseTestBase { + async setup() { + await this.p4.depot('stream', this.depotSpec()) + await this.createStreamsAndWorkspaces() + + await P4Util.addFileAndSubmit(this.getClient('Main'), 'test.txt', 'Initial content') + + await this.initialPopulate() + } + + run() { + const r1client = this.getClient('Release-1.0') + return r1client.sync() + .then(() => P4Util.editFileAndSubmit(r1client, 'test.txt', 'Initial content\nSome more!')) + } + + verify() { + return Promise.all([ + this.checkHeadRevision('Main', 'test.txt', 2), + this.checkHeadRevision('Release-2.0', 'test.txt', 2), + this.checkHeadRevision('Dev-Perkin', 'test.txt', 1), + this.checkHeadRevision('Dev-Pootle', 'test.txt', 1) + ]) + } + + getEdges(): EdgeProperties[] { + return [ + { from: this.fullBranchName('Release-2.0'), to: this.fullBranchName('Main') + , terminal: true + } + ] + } +} diff --git a/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/ztag.ts b/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/ztag.ts index 0d3450aec97c..111e05ffb47d 100644 --- a/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/ztag.ts +++ b/Engine/Extras/RoboMerge/v3/functional_tests/framework/src/ztag.ts @@ -1,5 +1,5 @@ // Copyright Epic Games, Inc. All Rights Reserved. -const FIELD_RE = /^\.\.\. ([a-z][a-zA-Z]*)(\d*) (.*)/ +const FIELD_RE = /^\.\.\. ([a-zA-Z]*)(\d*) (.*)/ type ZtagProperties = {[key: string]: string | number}[] diff --git a/Engine/Extras/RoboMerge/v3/public/index.html b/Engine/Extras/RoboMerge/v3/public/index.html index 87bf0522bd12..5f1101590000 100644 --- a/Engine/Extras/RoboMerge/v3/public/index.html +++ b/Engine/Extras/RoboMerge/v3/public/index.html @@ -3,6 +3,13 @@ ROBOMERGE + + + + + + + diff --git a/Engine/Extras/RoboMerge/v3/public/login.html b/Engine/Extras/RoboMerge/v3/public/login.html index 10f6872c1c62..a9a89ec83d3c 100644 --- a/Engine/Extras/RoboMerge/v3/public/login.html +++ b/Engine/Extras/RoboMerge/v3/public/login.html @@ -2,6 +2,14 @@ ROBOMERGE - sign in + + + + + + + + diff --git a/Engine/Extras/RoboMerge/v3/src/common/perforce.ts b/Engine/Extras/RoboMerge/v3/src/common/perforce.ts index c9af454d380d..6f2edc41b937 100644 --- a/Engine/Extras/RoboMerge/v3/src/common/perforce.ts +++ b/Engine/Extras/RoboMerge/v3/src/common/perforce.ts @@ -185,88 +185,6 @@ class CommandRecord { } } -//////////// -// new -ztag parsing - use with care. Initially using for p4.changes -const FIELD_RE = /^\.\.\. ([a-z]+)\d* (.*)/ - -export function parseZtagOutput(ztagOutput: string, expected?: string[], opt?: string[]) { - const result: any[] = [] - - const fields = new Set() - let first = true - let currentField = '' - let multiline: string[] = [] - let entry: any = {} - - const storeCurrentField = (newObject: boolean) => { - if (currentField) { - if (newObject) { - if (multiline[multiline.length - 1]) { - console.log(lines.join('\n'), [...fields], entry) - throw new Error('Expected line break between objects') - } - multiline.pop() - } - - entry[currentField] = multiline.join('\n') - multiline = [] - } - } - - const lines = ztagOutput.trim().split('\n') - for (const line of lines) { - const fieldMatch = line.match(FIELD_RE) - if (fieldMatch) { - const nextField = fieldMatch[1] - - // is this a new object? - storeCurrentField(first ? fields.has(nextField) : nextField in entry) - - currentField = nextField - multiline.push(fieldMatch[2]) - - if (first) { - if (fields.has(currentField)) { - first = false - result.push(entry) - entry = {} - } - else { - fields.add(currentField) - } - } - else if (!fields.has(currentField)) { - if (opt && opt.indexOf(currentField) >= 0) { - continue - } - console.log(entry, line) - throw new Error('unknown field ' + currentField) - } - else if (currentField in entry) { - result.push(entry) - entry = {} - } - } - else { - multiline.push(line) - } - } - - storeCurrentField(false) - if (Object.keys(entry).length > 0) { - result.push(entry) - - if (expected) { - for (const expectedField of expected) { - if (!fields.has(expectedField)) { - console.log(result) - throw new Error(`Expected field '${expectedField}' missing in output (${[...fields]}`) - } - } - } - } - return result -} export interface Change { // from Perforce (ztag) @@ -555,7 +473,7 @@ export class PerforceContext { return this.execAndParse(null, args, {quiet: true}, { expected: {change: 'integer', client: 'string', user: 'string', desc: 'string'}, - optional: {shelved: 'integer', oldChange: 'integer'} + optional: {shelved: 'integer', oldChange: 'integer', IsPromoted: 'integer'} }) as unknown as Promise } diff --git a/Engine/Extras/RoboMerge/v3/src/common/ztag.ts b/Engine/Extras/RoboMerge/v3/src/common/ztag.ts index 5edf975bfd35..0b0147790be1 100644 --- a/Engine/Extras/RoboMerge/v3/src/common/ztag.ts +++ b/Engine/Extras/RoboMerge/v3/src/common/ztag.ts @@ -6,7 +6,7 @@ const colors = require('colors') colors.enable() colors.setTheme(require('colors/themes/generic-logging.js')) -const FIELD_RE = /^\.\.\. ([a-z][a-zA-Z]*)(\d*) (.*)/ +const FIELD_RE = /^\.\.\. ([a-zA-Z]*)(\d*) (.*)/ type ZtagProperties = {[key: string]: string | number}[] diff --git a/Engine/Extras/RoboMerge/v3/src/robo/branchdefs.ts b/Engine/Extras/RoboMerge/v3/src/robo/branchdefs.ts index b5628b9179b4..4b0e3b718393 100644 --- a/Engine/Extras/RoboMerge/v3/src/robo/branchdefs.ts +++ b/Engine/Extras/RoboMerge/v3/src/robo/branchdefs.ts @@ -96,6 +96,7 @@ type EdgeOptionFields = { disallowSkip: boolean incognitoMode: boolean + terminal: boolean // changes go along terminal edges but no further doHackyOkForGithubThing: boolean excludeAuthors: string[] // if present, completely overrides bot and node configs diff --git a/Engine/Extras/RoboMerge/v3/src/robo/edgebot.ts b/Engine/Extras/RoboMerge/v3/src/robo/edgebot.ts index 3fefc05ac6e6..d7b095b77993 100644 --- a/Engine/Extras/RoboMerge/v3/src/robo/edgebot.ts +++ b/Engine/Extras/RoboMerge/v3/src/robo/edgebot.ts @@ -107,6 +107,9 @@ class EdgeBotImpl extends PerforceStatefulBot { get doHackyOkForGithubThing() { return !!this.options.doHackyOkForGithubThing } + get isTerminal() { + return !!this.options.terminal + } get excludedAuthors() { return this.options.excludeAuthors || [] } @@ -341,6 +344,10 @@ class EdgeBotImpl extends PerforceStatefulBot { description += '#okforgithub ignore\n' } + if (this.isTerminal) { + description += '#robomerge #ignore' + } + if (info.overriddenCommand) { // probably no need to remove #s, but feels safer description += `#ROBOMERGE-COMMAND: ${info.overriddenCommand.replace(/#/g, '_')}\n` diff --git a/Engine/Extras/Windows/cl-filter/cl-filter.cpp b/Engine/Extras/Windows/cl-filter/cl-filter.cpp index 9c7edd4c59e9..151cb24dc1f6 100644 --- a/Engine/Extras/Windows/cl-filter/cl-filter.cpp +++ b/Engine/Extras/Windows/cl-filter/cl-filter.cpp @@ -21,6 +21,7 @@ void PrintUsage() wprintf(L"Usage: cl-filter.exe -dependencies= -compiler= [Optional Args] -- \n"); wprintf(L"Optional Args:\n"); wprintf(L"\t-timing=\tThe file to write any timing data to.\n"); + wprintf(L"\t-stderronly\tProcess output from StdErr only and use the default pipe for StdOut.\n"); } bool ParseArgumentValue(const wchar_t* Argument, const wchar_t* Prefix, size_t PrefixLen, const wchar_t** OutValue) @@ -46,6 +47,7 @@ int wmain(int ArgC, const wchar_t* ArgV[]) const wchar_t* TimingFileName = nullptr; const wchar_t* CompilerFileName = nullptr; bool bShowIncludes = false; + bool bUseStdErrOnly = false; for (int Idx = 1; Idx < ArgC; Idx++) { @@ -78,6 +80,11 @@ int wmain(int ArgC, const wchar_t* ArgV[]) bShowIncludes = true; continue; } + if (_wcsicmp(Arg, L"-stderronly") == 0) + { + bUseStdErrOnly = true; + continue; + } // Output a warning if it doesn't match wprintf(L"WARNING: Unknown argument '%s'\n", Arg); @@ -120,19 +127,22 @@ int wmain(int ArgC, const wchar_t* ArgV[]) ZeroMemory(&SecurityAttributes, sizeof(SecurityAttributes)); SecurityAttributes.bInheritHandle = TRUE; - HANDLE StdOutReadHandle; - HANDLE StdOutWriteHandle; - if (CreatePipe(&StdOutReadHandle, &StdOutWriteHandle, &SecurityAttributes, 0) == 0) + HANDLE ReadPipeHandle; + HANDLE WritePipeHandle; + if (CreatePipe(&ReadPipeHandle, &WritePipeHandle, &SecurityAttributes, 0) == 0) { wprintf(L"ERROR: Unable to create output pipe for child process\n"); return EXIT_CODE_FAILURE; } - HANDLE StdErrWriteHandle; - if (DuplicateHandle(GetCurrentProcess(), StdOutWriteHandle, GetCurrentProcess(), &StdErrWriteHandle, 0, true, DUPLICATE_SAME_ACCESS) == 0) + HANDLE AdditionalWritePipeHandle = INVALID_HANDLE_VALUE; + if (!bUseStdErrOnly) { - wprintf(L"ERROR: Unable to create stderr pipe handle for child process\n"); - return EXIT_CODE_FAILURE; + if (DuplicateHandle(GetCurrentProcess(), WritePipeHandle, GetCurrentProcess(), &AdditionalWritePipeHandle, 0, true, DUPLICATE_SAME_ACCESS) == 0) + { + wprintf(L"ERROR: Unable to create stderr pipe handle for child process\n"); + return EXIT_CODE_FAILURE; + } } // Create the new process as suspended, so we can modify it before it starts executing (and potentially preempting us) @@ -140,8 +150,8 @@ int wmain(int ArgC, const wchar_t* ArgV[]) ZeroMemory(&StartupInfo, sizeof(StartupInfo)); StartupInfo.cb = sizeof(StartupInfo); StartupInfo.hStdInput = NULL; - StartupInfo.hStdOutput = StdOutWriteHandle; - StartupInfo.hStdError = StdErrWriteHandle; + StartupInfo.hStdOutput = bUseStdErrOnly ? GetStdHandle(STD_OUTPUT_HANDLE) : WritePipeHandle; + StartupInfo.hStdError = bUseStdErrOnly ? WritePipeHandle : AdditionalWritePipeHandle; StartupInfo.dwFlags = STARTF_USESTDHANDLES; DWORD ProcessCreationFlags = GetPriorityClass(GetCurrentProcess()); @@ -155,8 +165,11 @@ int wmain(int ArgC, const wchar_t* ArgV[]) CloseHandle(ProcessInfo.hThread); // Close the write ends of the handle. We don't want any other process to be able to inherit these. - CloseHandle(StdOutWriteHandle); - CloseHandle(StdErrWriteHandle); + CloseHandle(WritePipeHandle); + if (AdditionalWritePipeHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(AdditionalWritePipeHandle); + } // Delete the output file. DeleteFileW(OutputFileName); @@ -207,7 +220,7 @@ int wmain(int ArgC, const wchar_t* ArgV[]) DWORD BytesRead = 0; if (BufferSize < sizeof(Buffer)) { - if (ReadFile(StdOutReadHandle, Buffer + BufferSize, (DWORD)(sizeof(Buffer) - BufferSize), &BytesRead, NULL)) + if (ReadFile(ReadPipeHandle, Buffer + BufferSize, (DWORD)(sizeof(Buffer) - BufferSize), &BytesRead, NULL)) { BufferSize += BytesRead; } @@ -260,7 +273,7 @@ int wmain(int ArgC, const wchar_t* ArgV[]) if (bShowIncludes) { - WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), Buffer + LineStart, (DWORD)(LineEnd - LineStart), &BytesWritten, NULL); + WriteFile(GetStdHandle(bUseStdErrOnly ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE), Buffer + LineStart, (DWORD)(LineEnd - LineStart), &BytesWritten, NULL); } LineStart = LineEnd; @@ -293,7 +306,7 @@ int wmain(int ArgC, const wchar_t* ArgV[]) if(LineStart < LineEnd) { DWORD BytesWritten; - WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), Buffer + LineStart, (DWORD)(LineEnd - LineStart), &BytesWritten, NULL); + WriteFile(GetStdHandle(bUseStdErrOnly ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE), Buffer + LineStart, (DWORD)(LineEnd - LineStart), &BytesWritten, NULL); } // Move to the next line diff --git a/Engine/Plugins/2D/Paper2D/Source/Paper2D/Private/PaperTileMapComponent.cpp b/Engine/Plugins/2D/Paper2D/Source/Paper2D/Private/PaperTileMapComponent.cpp index 2fb054880fc0..52d08afbefbc 100644 --- a/Engine/Plugins/2D/Paper2D/Source/Paper2D/Private/PaperTileMapComponent.cpp +++ b/Engine/Plugins/2D/Paper2D/Source/Paper2D/Private/PaperTileMapComponent.cpp @@ -69,12 +69,14 @@ FPrimitiveSceneProxy* UPaperTileMapComponent::CreateSceneProxy() void UPaperTileMapComponent::PostInitProperties() { - TileMap = NewObject(this); + EObjectFlags Flags = HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject) ? + GetMaskedFlags(RF_PropagateToSubObjects) : RF_NoFlags; + // In a post GFastPathUniqueNameGeneration world we have to provide a stable name + // for all archetypes, here I'm using PaperTileMap_0 to match old content: + TileMap = NewObject(this, TEXT("PaperTileMap_0"), Flags); + TileMap->SetFlags(RF_Transactional); - if (HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject)) - { - TileMap->SetFlags(GetMaskedFlags(RF_PropagateToSubObjects)); - } + Super::PostInitProperties(); } diff --git a/Engine/Plugins/Animation/GameplayInsights/Source/GameplayInsights/Private/AnimNodesTrack.cpp b/Engine/Plugins/Animation/GameplayInsights/Source/GameplayInsights/Private/AnimNodesTrack.cpp index 9195a8de66ad..f5461a4ad4b8 100644 --- a/Engine/Plugins/Animation/GameplayInsights/Source/GameplayInsights/Private/AnimNodesTrack.cpp +++ b/Engine/Plugins/Animation/GameplayInsights/Source/GameplayInsights/Private/AnimNodesTrack.cpp @@ -323,7 +323,7 @@ void FAnimNodesTrack::UpdateDebugData(const Trace::FFrame& InFrame) { // Basic verification - check node count is the same // @TODO: could add some form of node hash/CRC to the class to improve this - if(InMessage.NodeCount == InstanceClass.Get()->AnimNodeProperties.Num()) + if(InMessage.NodeCount == InstanceClass.Get()->GetAnimNodeProperties().Num()) { AnimationProvider->ReadAnimNodesTimeline(GetGameplayTrack().GetObjectId(), [InGraphStartTime, InGraphEndTime, &DebugData](const FAnimationProvider::AnimNodesTimeline& InNodesTimeline) { diff --git a/Engine/Plugins/Animation/GameplayInsights/Source/GameplayInsights/Private/AnimationTickRecordsTrack.cpp b/Engine/Plugins/Animation/GameplayInsights/Source/GameplayInsights/Private/AnimationTickRecordsTrack.cpp index 1d1a66d70b61..d10b39b3a930 100644 --- a/Engine/Plugins/Animation/GameplayInsights/Source/GameplayInsights/Private/AnimationTickRecordsTrack.cpp +++ b/Engine/Plugins/Animation/GameplayInsights/Source/GameplayInsights/Private/AnimationTickRecordsTrack.cpp @@ -417,7 +417,7 @@ void FAnimationTickRecordsTrack::BuildContextMenu(FMenuBuilder& MenuBuilder) if(IAnimationBlueprintEditor* AnimBlueprintEditor = static_cast(GEditor->GetEditorSubsystem()->FindEditorForAsset(AnimBlueprint, true))) { - int32 AnimNodeIndex = InstanceClass.Get()->AnimNodeProperties.Num() - NodeId - 1; + int32 AnimNodeIndex = InstanceClass.Get()->GetAnimNodeProperties().Num() - NodeId - 1; TWeakObjectPtr* GraphNode = InstanceClass.Get()->AnimBlueprintDebugData.NodePropertyIndexToNodeMap.Find(AnimNodeIndex); if(GraphNode != nullptr && GraphNode->Get()) { diff --git a/Engine/Plugins/Animation/GameplayInsights/Source/GameplayInsights/Private/GameplayInsightsModule.cpp b/Engine/Plugins/Animation/GameplayInsights/Source/GameplayInsights/Private/GameplayInsightsModule.cpp index c6319681fa37..9f95ae56960d 100644 --- a/Engine/Plugins/Animation/GameplayInsights/Source/GameplayInsights/Private/GameplayInsightsModule.cpp +++ b/Engine/Plugins/Animation/GameplayInsights/Source/GameplayInsights/Private/GameplayInsightsModule.cpp @@ -15,6 +15,7 @@ #include "Widgets/Docking/SDockTab.h" #include "Trace/StoreService.h" #include "Trace/StoreClient.h" +#include "Stats/Stats.h" #if WITH_EDITOR #include "IAnimationBlueprintEditorModule.h" @@ -35,6 +36,8 @@ void FGameplayInsightsModule::StartupModule() TickerHandle = FTicker::GetCoreTicker().AddTicker(TEXT("GameplayInsights"), 0.0f, [this](float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FGameplayInsightsModule_TickVisualizers); + GameplayTimingViewExtender.TickVisualizers(DeltaTime); return true; }); diff --git a/Engine/Plugins/Animation/LiveLink/Source/LiveLink/Private/LiveLinkPreset.cpp b/Engine/Plugins/Animation/LiveLink/Source/LiveLink/Private/LiveLinkPreset.cpp index e767e18387e4..f170073d91a4 100644 --- a/Engine/Plugins/Animation/LiveLink/Source/LiveLink/Private/LiveLinkPreset.cpp +++ b/Engine/Plugins/Animation/LiveLink/Source/LiveLink/Private/LiveLinkPreset.cpp @@ -29,6 +29,27 @@ namespace } }) ); + + static FAutoConsoleCommand LiveLinkPresetAddCmd( + TEXT("LiveLink.Preset.Add"), + TEXT("Add a LiveLinkPreset. Use: LiveLink.Preset.Add Preset=/Game/Folder/MyLiveLinkPreset.MyLiveLinkPreset"), + FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray& Args) + { + for (const FString& Element : Args) + { + const TCHAR* PresetStr = TEXT("Preset="); + int32 FoundElement = Element.Find(PresetStr); + if (FoundElement != INDEX_NONE) + { + UObject* Object = StaticLoadObject(ULiveLinkPreset::StaticClass(), nullptr, *Element + FoundElement + FCString::Strlen(PresetStr)); + if (Object) + { + CastChecked(Object)->AddToClient(); + } + } + } + }) + ); } bool ULiveLinkPreset::ApplyToClient() const @@ -44,12 +65,12 @@ bool ULiveLinkPreset::ApplyToClient() const bResult = true; for (const FLiveLinkSourcePreset& SourcePreset : Sources) { - bResult |= LiveLinkClient.CreateSource(SourcePreset); + bResult &= LiveLinkClient.CreateSource(SourcePreset); } for (const FLiveLinkSubjectPreset& SubjectPreset : Subjects) { - bResult |= LiveLinkClient.CreateSubject(SubjectPreset); + bResult &= LiveLinkClient.CreateSubject(SubjectPreset); } } @@ -65,6 +86,77 @@ bool ULiveLinkPreset::ApplyToClient() const return bResult; } +bool ULiveLinkPreset::AddToClient(const bool bRecreatePresets) const +{ + bool bResult = false; + if (IModularFeatures::Get().IsModularFeatureAvailable(ILiveLinkClient::ModularFeatureName)) + { + FLiveLinkClient& LiveLinkClient = IModularFeatures::Get().GetModularFeature(ILiveLinkClient::ModularFeatureName); + + TArray AllSources; + AllSources.Append(LiveLinkClient.GetSources()); + AllSources.Append(LiveLinkClient.GetVirtualSources()); + TArray AllSubjects = LiveLinkClient.GetSubjects(true, true); + + TSet FoundSources; + TSet FoundSubjects; + + for (const FLiveLinkSourcePreset& SourcePreset : Sources) + { + if (AllSources.Contains(SourcePreset.Guid)) + { + if (bRecreatePresets) + { + LiveLinkClient.RemoveSource(SourcePreset.Guid); + } + else + { + FoundSources.Add(SourcePreset.Guid); + } + } + } + + for (const FLiveLinkSubjectPreset& SubjectPreset : Subjects) + { + if (AllSubjects.Contains(SubjectPreset.Key)) + { + if (bRecreatePresets) + { + LiveLinkClient.RemoveSubject_AnyThread(SubjectPreset.Key); + } + else + { + FoundSubjects.Add(SubjectPreset.Key); + } + } + } + + LiveLinkClient.Tick(); + + bResult = true; + for (const FLiveLinkSourcePreset& SourcePreset : Sources) + { + bResult &= (FoundSources.Contains(SourcePreset.Guid) || LiveLinkClient.CreateSource(SourcePreset)); + } + + for (const FLiveLinkSubjectPreset& SubjectPreset : Subjects) + { + bResult &= (FoundSubjects.Contains(SubjectPreset.Key) || LiveLinkClient.CreateSubject(SubjectPreset)); + } + } + + if (bResult) + { + FLiveLinkLog::Info(TEXT("Added '%s'"), *GetFullName()); + } + else + { + FLiveLinkLog::Error(TEXT("Could not add '%s'"), *GetFullName()); + } + + return bResult; +} + void ULiveLinkPreset::BuildFromClient() { Sources.Reset(); diff --git a/Engine/Plugins/Animation/LiveLink/Source/LiveLink/Public/LiveLinkPreset.h b/Engine/Plugins/Animation/LiveLink/Source/LiveLink/Public/LiveLinkPreset.h index 71ff1aca9bb7..364d96d33179 100644 --- a/Engine/Plugins/Animation/LiveLink/Source/LiveLink/Public/LiveLinkPreset.h +++ b/Engine/Plugins/Animation/LiveLink/Source/LiveLink/Public/LiveLinkPreset.h @@ -15,10 +15,10 @@ class LIVELINK_API ULiveLinkPreset : public UObject GENERATED_BODY() private: - UPROPERTY() + UPROPERTY(VisibleAnywhere, Category = "LiveLinkSourcePresets") TArray Sources; - UPROPERTY() + UPROPERTY(VisibleAnywhere, Category = "LiveLinkSubjectPresets") TArray Subjects; public: @@ -35,6 +35,16 @@ public: UFUNCTION(BlueprintCallable, BlueprintPure = false, Category="LiveLink") bool ApplyToClient() const; + /** + * Add the sources and subjects from this preset, but leave any existing sources and subjects connected. + * + * @param bRecreatePresets When true, if subjects and sources from this preset already exist, we will recreate them. + * + * @return True is all sources and subjects from this preset could be created and added. + */ + UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = "LiveLink") + bool AddToClient(const bool bRecreatePresets = true) const; + /** Reset this preset and build the list of sources and subjects from the client. */ UFUNCTION(BlueprintCallable, Category="LiveLink") void BuildFromClient(); diff --git a/Engine/Plugins/Animation/LiveLink/Source/LiveLinkSequencer/Private/SequencerRecorderSections/MovieSceneLiveLinkTrackRecorder.cpp b/Engine/Plugins/Animation/LiveLink/Source/LiveLinkSequencer/Private/SequencerRecorderSections/MovieSceneLiveLinkTrackRecorder.cpp index 1143e1bd8163..aed80129db34 100644 --- a/Engine/Plugins/Animation/LiveLink/Source/LiveLinkSequencer/Private/SequencerRecorderSections/MovieSceneLiveLinkTrackRecorder.cpp +++ b/Engine/Plugins/Animation/LiveLink/Source/LiveLinkSequencer/Private/SequencerRecorderSections/MovieSceneLiveLinkTrackRecorder.cpp @@ -192,8 +192,12 @@ void UMovieSceneLiveLinkTrackRecorder::FinalizeTrackImpl() { if (MovieSceneSection.IsValid()) { + FTakeRecorderParameters Parameters; + Parameters.User = GetDefault()->Settings; + FKeyDataOptimizationParams Params; Params.bAutoSetInterpolation = true; + Params.Tolerance = Parameters.User.ReduceKeysTolerance; MovieSceneSection->FinalizeSection(bReduceKeys, Params); TOptional > DefaultSectionLength = MovieSceneSection->GetAutoSizeRange(); diff --git a/Engine/Plugins/Compositing/OpenColorIO/Source/OpenColorIO/Private/OpenColorIOShaderMap.cpp b/Engine/Plugins/Compositing/OpenColorIO/Source/OpenColorIO/Private/OpenColorIOShaderMap.cpp index 6eaf8cf10137..1f4e3590a2f4 100644 --- a/Engine/Plugins/Compositing/OpenColorIO/Source/OpenColorIO/Private/OpenColorIOShaderMap.cpp +++ b/Engine/Plugins/Compositing/OpenColorIO/Source/OpenColorIO/Private/OpenColorIOShaderMap.cpp @@ -370,7 +370,7 @@ void FOpenColorIOShaderMap::Compile(FOpenColorIOTransformResource* InColorTransf { // Make sure we are operating on a referenced shader map or the below Find will cause this shader map to be deleted, // Since it creates a temporary ref counted pointer. - check(GetNumRefs() > 0); + check(NumRefs > 0); // Add this shader map and to OpenColorIOShaderMapsBeingCompiled TArray* CorrespondingTransform = OpenColorIOShaderMapsBeingCompiled.Find(this); @@ -528,7 +528,7 @@ bool FOpenColorIOShaderMap::ProcessCompilationResults(const TArray 0); + check(NumRefs > 0); TArray* CorrespondingColorTransforms = FOpenColorIOShaderMap::OpenColorIOShaderMapsBeingCompiled.Find(this); if (CorrespondingColorTransforms) @@ -565,7 +565,7 @@ bool FOpenColorIOShaderMap::IsComplete(const FOpenColorIOTransformResource* InCo { // Make sure we are operating on a referenced shader map or the below Find will cause this shader map to be deleted, // Since it creates a temporary ref counted pointer. - check(GetNumRefs() > 0); + check(NumRefs > 0); const TArray* CorrespondingColorTransforms = FOpenColorIOShaderMap::OpenColorIOShaderMapsBeingCompiled.Find(this); if (CorrespondingColorTransforms) @@ -642,23 +642,34 @@ void FOpenColorIOShaderMap::Register(EShaderPlatform InShaderPlatform) bRegistered = true; } -void FOpenColorIOShaderMap::OnReleased() +void FOpenColorIOShaderMap::AddRef() { - if (bRegistered) - { - DEC_DWORD_STAT(STAT_Shaders_NumShaderMaps); - - GIdToOpenColorIOShaderMap[GetShaderPlatform()].Remove(GetContent()->ShaderMapId); - bRegistered = false; - } - check(!bDeletedThroughDeferredCleanup); - bDeletedThroughDeferredCleanup = true; - BeginCleanup(this); + ++NumRefs; +} + +void FOpenColorIOShaderMap::Release() +{ + check(NumRefs > 0); + if(--NumRefs == 0) + { + if (bRegistered) + { + DEC_DWORD_STAT(STAT_Shaders_NumShaderMaps); + + GIdToOpenColorIOShaderMap[GetShaderPlatform()].Remove(GetContent()->ShaderMapId); + bRegistered = false; + } + + check(!bDeletedThroughDeferredCleanup); + bDeletedThroughDeferredCleanup = true; + BeginCleanup(this); + } } FOpenColorIOShaderMap::FOpenColorIOShaderMap() : CompilingId(1), + NumRefs(0), bDeletedThroughDeferredCleanup(false), bRegistered(false), bCompilationFinalized(true), diff --git a/Engine/Plugins/Compositing/OpenColorIO/Source/OpenColorIO/Public/OpenColorIOShared.h b/Engine/Plugins/Compositing/OpenColorIO/Source/OpenColorIO/Public/OpenColorIOShared.h index afb41e72841a..c38e81d0db10 100644 --- a/Engine/Plugins/Compositing/OpenColorIO/Source/OpenColorIO/Public/OpenColorIOShared.h +++ b/Engine/Plugins/Compositing/OpenColorIO/Source/OpenColorIO/Public/OpenColorIOShared.h @@ -151,8 +151,7 @@ public: FOpenColorIOShaderMap(); // Destructor. - virtual ~FOpenColorIOShaderMap(); - virtual void OnReleased() override; + ~FOpenColorIOShaderMap(); /** * Compiles the shaders for a color transform and caches them in this shader map. @@ -197,6 +196,10 @@ public: /** Registers a OpenColorIO shader map in the global map so it can be used by OpenColorIO ColorTransform. */ void Register(EShaderPlatform InShaderPlatform); + // Reference counting. + OPENCOLORIO_API void AddRef(); + OPENCOLORIO_API void Release(); + /** * Removes all entries in the cache with exceptions based on a shader type * @param ShaderType - The shader type to flush @@ -232,6 +235,7 @@ public: return bCompilationFinalized && bCompiledSuccessfully && !bDeletedThroughDeferredCleanup; } + int32 GetNumRefs() const { return NumRefs; } uint32 GetCompilingId() { return CompilingId; } static TMap, TArray > &GetInFlightShaderMaps() { return OpenColorIOShaderMapsBeingCompiled; } @@ -260,6 +264,8 @@ private: /** Uniquely identifies this shader map during compilation, needed for deferred compilation where shaders from multiple shader maps are compiled together. */ uint32 CompilingId; + mutable int32 NumRefs; + /** Used to catch errors where the shader map is deleted directly. */ bool bDeletedThroughDeferredCleanup; diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingModule.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingModule.cpp index ccee7a02aa7a..e98ecb314363 100644 --- a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingModule.cpp +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingModule.cpp @@ -9,6 +9,8 @@ IMPLEMENT_MODULE( FAnimSharingModule, AnimationSharing); TMap FAnimSharingModule::WorldAnimSharingManagers; +FOnAnimationSharingManagerCreated FAnimSharingModule::OnAnimationSharingManagerCreated; + void FAnimSharingModule::StartupModule() { FWorldDelegates::OnPostWorldCleanup.AddStatic(&FAnimSharingModule::OnWorldCleanup); @@ -36,6 +38,8 @@ bool FAnimSharingModule::CreateAnimationSharingManager(UWorld* InWorld, const UA UAnimationSharingManager* Manager = NewObject(InWorld); Manager->Initialise(Setup); WorldAnimSharingManagers.Add(InWorld, Manager); + + OnAnimationSharingManagerCreated.Broadcast(Manager, InWorld); return true; } diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingModule.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingModule.h index b9c5075c88de..faec48da593d 100644 --- a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingModule.h +++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingModule.h @@ -12,6 +12,8 @@ class UAnimationSharingManager; class UAnimationSharingSetup; +DECLARE_MULTICAST_DELEGATE_TwoParams(FOnAnimationSharingManagerCreated, UAnimationSharingManager*, const UWorld*); + class ANIMATIONSHARING_API FAnimSharingModule : public FDefaultModuleImpl, public FGCObject { public: @@ -28,9 +30,15 @@ public: return WorldAnimSharingManagers.FindRef(World); } + FORCEINLINE static FOnAnimationSharingManagerCreated& GetOnAnimationSharingManagerCreated() + { + return OnAnimationSharingManagerCreated; + } + /** Creates an animation sharing manager for the given UWorld (must be a Game World) */ static bool CreateAnimationSharingManager(UWorld* InWorld, const UAnimationSharingSetup* Setup); private: static void OnWorldCleanup(UWorld* World, bool bSessionEnded, bool bCleanupResources); static TMap WorldAnimSharingManagers; + static FOnAnimationSharingManagerCreated OnAnimationSharingManagerCreated; }; diff --git a/Engine/Plugins/Developer/Concert/ConcertMain/Source/Concert/Private/ConcertClient.cpp b/Engine/Plugins/Developer/Concert/ConcertMain/Source/Concert/Private/ConcertClient.cpp index 72fbb6c9407b..b1d95f2a3737 100644 --- a/Engine/Plugins/Developer/Concert/ConcertMain/Source/Concert/Private/ConcertClient.cpp +++ b/Engine/Plugins/Developer/Concert/ConcertMain/Source/Concert/Private/ConcertClient.cpp @@ -13,6 +13,7 @@ #include "Misc/CoreDelegates.h" #include "Misc/AsyncTaskNotification.h" #include "HAL/FileManager.h" +#include "Stats/Stats.h" #include "Runtime/Launch/Resources/Version.h" @@ -51,7 +52,9 @@ public: AutoConnectionNotification = MakeAutoConnectNotification(); - AutoConnectionTickHandle = FTicker::GetCoreTicker().AddTicker(TEXT("ConcertAutoConnect"), 1, [this](float) { + AutoConnectionTickHandle = FTicker::GetCoreTicker().AddTicker(TEXT("ConcertAutoConnect"), 1, [this](float) + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FConcertAutoConnection_Tick); Tick(); return true; }); @@ -345,7 +348,9 @@ public: ConnectionTasks[0]->Execute(); - ConnectionTick = FTicker::GetCoreTicker().AddTicker(TEXT("ConcertPendingConnection"), 0.1f, [this](float) { + ConnectionTick = FTicker::GetCoreTicker().AddTicker(TEXT("ConcertPendingConnection"), 0.1f, [this](float) + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FConcertPendingConnection_Tick); Tick(); return true; }); @@ -750,6 +755,7 @@ void FConcertClient::StartDiscovery() ClientAdminEndpoint->RegisterEventHandler(this, &FConcertClient::HandleServerDiscoveryEvent); DiscoveryTick = FTicker::GetCoreTicker().AddTicker(TEXT("Discovery"), 1, [this](float DeltaSeconds) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FConcertClient_Discovery_Tick); const FDateTime UtcNow = FDateTime::UtcNow(); SendDiscoverServersEvent(); TimeoutDiscovery(UtcNow); diff --git a/Engine/Plugins/Developer/Concert/ConcertMain/Source/Concert/Private/ConcertClientSession.cpp b/Engine/Plugins/Developer/Concert/ConcertMain/Source/Concert/Private/ConcertClientSession.cpp index e122686f8d47..e95c1973ca96 100644 --- a/Engine/Plugins/Developer/Concert/ConcertMain/Source/Concert/Private/ConcertClientSession.cpp +++ b/Engine/Plugins/Developer/Concert/ConcertMain/Source/Concert/Private/ConcertClientSession.cpp @@ -7,6 +7,7 @@ #include "Containers/Ticker.h" #include "Misc/Paths.h" +#include "Stats/Stats.h" #include "UObject/StructOnScope.h" const FName ConcertClientMessageIdName("ConcertMessageId"); @@ -50,7 +51,9 @@ void FConcertClientSession::Startup() ClientSessionEndpoint->RegisterRequestHandler(this, &FConcertClientSession::CommonHandleCustomRequest); // Setup the session tick - SessionTick = FTicker::GetCoreTicker().AddTicker(TEXT("ClientSession"), 0, [this](float DeltaSeconds) { + SessionTick = FTicker::GetCoreTicker().AddTicker(TEXT("ClientSession"), 0, [this](float DeltaSeconds) + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FConcertClientSession_Tick); const FDateTime UtcNow = FDateTime::UtcNow(); TickConnection(DeltaSeconds, UtcNow); return true; diff --git a/Engine/Plugins/Developer/Concert/ConcertMain/Source/Concert/Private/ConcertServerSession.cpp b/Engine/Plugins/Developer/Concert/ConcertMain/Source/Concert/Private/ConcertServerSession.cpp index edface8776d8..6e9986153441 100644 --- a/Engine/Plugins/Developer/Concert/ConcertMain/Source/Concert/Private/ConcertServerSession.cpp +++ b/Engine/Plugins/Developer/Concert/ConcertMain/Source/Concert/Private/ConcertServerSession.cpp @@ -6,6 +6,7 @@ #include "Scratchpad/ConcertScratchpad.h" #include "Containers/Ticker.h" +#include "Stats/Stats.h" #include "UObject/StructOnScope.h" const FName ConcertServerMessageIdName("ConcertMessageId"); @@ -45,7 +46,9 @@ void FConcertServerSession::Startup() ServerSessionEndpoint->RegisterRequestHandler(this, &FConcertServerSession::HandleCustomRequest); // Setup the session tick - SessionTick = FTicker::GetCoreTicker().AddTicker(TEXT("ServerSession"), 0, [this](float DeltaSeconds) { + SessionTick = FTicker::GetCoreTicker().AddTicker(TEXT("ServerSession"), 0, [this](float DeltaSeconds) + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FConcertServerSession_Tick); TickConnections(DeltaSeconds); return true; }); diff --git a/Engine/Plugins/Developer/Concert/ConcertMain/Source/ConcertTransport/Private/ConcertLocalEndpoint.cpp b/Engine/Plugins/Developer/Concert/ConcertMain/Source/ConcertTransport/Private/ConcertLocalEndpoint.cpp index 023ffaf0e120..cc6666ead0d4 100644 --- a/Engine/Plugins/Developer/Concert/ConcertMain/Source/ConcertTransport/Private/ConcertLocalEndpoint.cpp +++ b/Engine/Plugins/Developer/Concert/ConcertMain/Source/ConcertTransport/Private/ConcertLocalEndpoint.cpp @@ -9,6 +9,7 @@ #include "HAL/Runnable.h" #include "HAL/RunnableThread.h" #include "Containers/Ticker.h" +#include "Stats/Stats.h" class FConcertLocalEndpointKeepAliveRunnable : public FRunnable { @@ -289,6 +290,8 @@ FConcertRemoteEndpointPtr FConcertLocalEndpoint::FindRemoteEndpoint(const FMessa bool FConcertLocalEndpoint::HandleTick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FConcertLocalEndpoint_HandleTick2); + const FDateTime UtcNow = FDateTime::UtcNow(); // Flush the task graph to grab any pending messages diff --git a/Engine/Plugins/Developer/Concert/DisasterRecoveryClient/Source/DisasterRecoveryClient/Private/DisasterRecoveryTasks.cpp b/Engine/Plugins/Developer/Concert/DisasterRecoveryClient/Source/DisasterRecoveryClient/Private/DisasterRecoveryTasks.cpp index c27bb03db9f5..aa688c163aef 100644 --- a/Engine/Plugins/Developer/Concert/DisasterRecoveryClient/Source/DisasterRecoveryClient/Private/DisasterRecoveryTasks.cpp +++ b/Engine/Plugins/Developer/Concert/DisasterRecoveryClient/Source/DisasterRecoveryClient/Private/DisasterRecoveryTasks.cpp @@ -7,6 +7,7 @@ #include "IConcertSyncClient.h" #include "IDisasterRecoveryClientModule.h" #include "Containers/Ticker.h" +#include "Stats/Stats.h" #include "ConcertActivityStream.h" #include "DisasterRecoverySessionInfo.h" @@ -46,6 +47,8 @@ void FDisasterRecoveryTaskExecutor::Enqueue(TSharedPtr Ta bool FDisasterRecoveryTaskExecutor::Tick(float) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FDisasterRecoveryTaskExecutor_Tick); + if (Tasks.Num()) { if (!Tasks[0].Value) // Not started? diff --git a/Engine/Plugins/Developer/PropertyAccessNode/PropertyAccessNode.uplugin b/Engine/Plugins/Developer/PropertyAccessNode/PropertyAccessNode.uplugin new file mode 100644 index 000000000000..77b9902d377d --- /dev/null +++ b/Engine/Plugins/Developer/PropertyAccessNode/PropertyAccessNode.uplugin @@ -0,0 +1,33 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Property Access Node", + "Description": "Blueprint node that allows access to properties via a property path", + "Category": "Developer", + "CreatedBy": "Epic Games, Inc.", + "CreatedByURL": "http://epicgames.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "EnabledByDefault": true, + "CanContainContent": false, + "IsBetaVersion": false, + "Installed": false, + + "Modules": + [ + { + "Name": "PropertyAccessNode", + "Type": "UncookedOnly", + "LoadingPhase": "PreDefault" + } + ], + "Plugins": + [ + { + "Name": "PropertyAccess", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/K2Node_PropertyAccess.cpp b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/K2Node_PropertyAccess.cpp new file mode 100644 index 000000000000..34d7fd4e91b2 --- /dev/null +++ b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/K2Node_PropertyAccess.cpp @@ -0,0 +1,251 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "K2Node_PropertyAccess.h" +#include "Algo/Accumulate.h" +#include "EdGraphSchema_K2.h" +#include "PropertyAccess.h" +#include "Engine/Blueprint.h" +#include "FindInBlueprintManager.h" +#include "BlueprintActionDatabaseRegistrar.h" +#include "EditorCategoryUtils.h" +#include "BlueprintNodeSpawner.h" +#include "KismetCompilerMisc.h" +#include "EdGraphUtilities.h" +#include "KismetCompiler.h" +#include "K2Node_VariableGet.h" +#include "AnimationGraph.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "IPropertyAccessCompilerSubsystem.h" +#include "AnimBlueprintCompilerSubsystem.h" +#include "Modules/ModuleManager.h" +#include "IPropertyAccessEditor.h" +#include "IPropertyAccessCompiler.h" +#include "Features/IModularFeatures.h" + +#define LOCTEXT_NAMESPACE "K2Node_PropertyAccess" + +void UK2Node_PropertyAccess::CreateClassVariablesFromBlueprint(FKismetCompilerContext& InCompilerContext) +{ + GeneratedPropertyName = NAME_None; + + if(ResolvedPinType != FEdGraphPinType() && ResolvedPinType.PinCategory != UEdGraphSchema_K2::PC_Wildcard) + { + // Create internal generated destination property + if(FProperty* DestProperty = FKismetCompilerUtilities::CreatePropertyOnScope(InCompilerContext.NewClass, GetFName(), ResolvedPinType, InCompilerContext.NewClass, CPF_None, CastChecked(GetSchema()), InCompilerContext.MessageLog)) + { + GeneratedPropertyName = DestProperty->GetFName(); + + FKismetCompilerUtilities::LinkAddedProperty(InCompilerContext.NewClass, DestProperty); + } + } +} + +void UK2Node_PropertyAccess::ExpandNode(FKismetCompilerContext& InCompilerContext, UEdGraph* InSourceGraph) +{ + ResolveLeafProperty(); + + if(GeneratedPropertyName != NAME_None) + { + TArray DestPropertyPath; + DestPropertyPath.Add(GeneratedPropertyName.ToString()); + + // Create a copy event in the complied generated class + IPropertyAccessCompilerSubsystem* PropertyAccessSubsystem = UAnimBlueprintCompilerSubsystem::FindSubsystemWithInterface(InCompilerContext); + check(PropertyAccessSubsystem); + PropertyAccessSubsystem->AddCopy(Path, DestPropertyPath, EPropertyAccessBatchType::Batched, this); + + // Replace us with a get node + UK2Node_VariableGet* VariableGetNode = InCompilerContext.SpawnIntermediateNode(this, InSourceGraph); + VariableGetNode->VariableReference.SetSelfMember(GeneratedPropertyName); + VariableGetNode->AllocateDefaultPins(); + InCompilerContext.MessageLog.NotifyIntermediateObjectCreation(VariableGetNode, this); + + // Move pin links from Get node we are expanding, to the new pure one we've created + UEdGraphPin* VariableValuePin = VariableGetNode->GetValuePin(); + check(VariableValuePin); + InCompilerContext.MovePinLinksToIntermediate(*GetOutputPin(), *VariableValuePin); + } + else + { + InCompilerContext.MessageLog.Error(*LOCTEXT("IntermediateProperty_Error", "Intermediate property could not be created on @@").ToString(), this); + } +} + +void UK2Node_PropertyAccess::ResolveLeafProperty() +{ + if(UBlueprint* Blueprint = GetBlueprint()) + { + ResolvedProperty = nullptr; + ResolvedArrayIndex = INDEX_NONE; + FProperty* PropertyToResolve = nullptr; + if(IModularFeatures::Get().IsModularFeatureAvailable("PropertyAccessEditor")) + { + IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); + PropertyAccessEditor.ResolveLeafProperty(Blueprint->SkeletonGeneratedClass, Path, PropertyToResolve, ResolvedArrayIndex); + ResolvedProperty = PropertyToResolve; + } + } + else + { + ResolvedProperty = nullptr; + ResolvedArrayIndex = INDEX_NONE; + } +} + +static FText MakeTextPath(const TArray& InPath) +{ + return FText::FromString(Algo::Accumulate(InPath, FString(), [](const FString& InResult, const FString& InSegment) + { + return InResult.IsEmpty() ? InSegment : (InResult + TEXT(".") + InSegment); + })); +} + +void UK2Node_PropertyAccess::SetPath(const TArray& InPath) +{ + Path = InPath; + TextPath = MakeTextPath(Path); + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint()); + ResolveLeafProperty(); + ReconstructNode(); +} + +void UK2Node_PropertyAccess::SetPath(TArray&& InPath) +{ + Path = MoveTemp(InPath); + TextPath = MakeTextPath(Path); + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint()); + ResolveLeafProperty(); + ReconstructNode(); +} + +void UK2Node_PropertyAccess::ClearPath() +{ + Path.Empty(); + TextPath = FText(); + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint()); + ResolvedProperty = nullptr; + ResolvedArrayIndex = INDEX_NONE; + ReconstructNode(); +} + +void UK2Node_PropertyAccess::AllocatePins(UEdGraphPin* InOldOutputPin) +{ + // Resolve leaf to try to get a valid property type for an output pin + ResolveLeafProperty(); + + if(UBlueprint* Blueprint = GetBlueprint()) + { + UEdGraphPin* OutputPin = nullptr; + + if(InOldOutputPin != nullptr && InOldOutputPin->LinkedTo.Num() > 0) + { + // Use old output pin if we have one and it is connected + OutputPin = CreatePin(EGPD_Output, InOldOutputPin->PinType, TEXT("Value")); + ResolvedPinType = InOldOutputPin->PinType; + } + + if(OutputPin == nullptr && ResolvedProperty.Get() != nullptr) + { + // Otherwise use the resolved property + FProperty* PropertyToUse = ResolvedProperty.Get(); + if(FArrayProperty* ArrayProperty = CastField(PropertyToUse)) + { + if(ResolvedArrayIndex != INDEX_NONE) + { + PropertyToUse = ArrayProperty->Inner; + } + } + + // Try to create a pin for the property + const UEdGraphSchema_K2* K2Schema = GetDefault(); + if(K2Schema->ConvertPropertyToPinType(PropertyToUse, ResolvedPinType)) + { + OutputPin = CreatePin(EGPD_Output, ResolvedPinType, TEXT("Value")); + } + } + + if(OutputPin == nullptr) + { + // Cant resolve a type from the path, make a wildcard pin to begin with + OutputPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, TEXT("Value")); + ResolvedPinType = OutputPin->PinType; + } + } +} + +void UK2Node_PropertyAccess::AllocateDefaultPins() +{ + AllocatePins(); +} + +void UK2Node_PropertyAccess::ReallocatePinsDuringReconstruction(TArray& OldPins) +{ + // First find the old output pin, if any + UEdGraphPin* OldOutputPin = nullptr; + if(UEdGraphPin** OldOutputPinPtr = OldPins.FindByPredicate([](UEdGraphPin* InPin){ return InPin->PinName == TEXT("Value"); })) + { + OldOutputPin = *OldOutputPinPtr; + } + + AllocatePins(OldOutputPin); + + RestoreSplitPins(OldPins); +} + +FText UK2Node_PropertyAccess::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + return LOCTEXT("PropertyAccess", "Property Access"); +} + +void UK2Node_PropertyAccess::AddSearchMetaDataInfo(TArray& OutTaggedMetaData) const +{ + auto MakeTextPath = [this]() + { + if(Path.Num()) + { + return FText::FromString(Algo::Accumulate(Path, FString(), [](const FString& InResult, const FString& InSegment) + { + return InResult.IsEmpty() ? InSegment : (InResult + TEXT(".") + InSegment); + })); + } + else + { + return LOCTEXT("None", "None"); + } + }; + + OutTaggedMetaData.Emplace(LOCTEXT("PropertyAccess", "Property Access"), MakeTextPath()); +} + +void UK2Node_PropertyAccess::PinConnectionListChanged(UEdGraphPin* Pin) +{ + if(Pin && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard && Pin->LinkedTo.Num() > 0) + { + Pin->PinType = ResolvedPinType = Pin->LinkedTo[0]->PinType; + } +} + +void UK2Node_PropertyAccess::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const +{ + UClass* NodeClass = GetClass(); + if (ActionRegistrar.IsOpenForRegistration(NodeClass)) + { + UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(NodeClass); + check(NodeSpawner); + ActionRegistrar.AddBlueprintAction(NodeClass, NodeSpawner); + } +} + +FText UK2Node_PropertyAccess::GetMenuCategory() const +{ + return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Variables); +} + +bool UK2Node_PropertyAccess::IsCompatibleWithGraph(UEdGraph const* TargetGraph) const +{ + // Only allow placement in anim graphs, for now. If this changes then we need to address the + // dependency on the anim BP compiler's subsystems + return TargetGraph->IsA(); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/K2Node_PropertyAccess.h b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/K2Node_PropertyAccess.h new file mode 100644 index 000000000000..3f69b94dad24 --- /dev/null +++ b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/K2Node_PropertyAccess.h @@ -0,0 +1,88 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "K2Node.h" +#include "IClassVariableCreator.h" +#include "EdGraph/EdGraphPin.h" + +#include "K2Node_PropertyAccess.generated.h" + +UCLASS(MinimalAPI) +class UK2Node_PropertyAccess : public UK2Node, public IClassVariableCreator +{ +public: + GENERATED_BODY() + + /** Set the path and attempt to resolve the leaf property. */ + void SetPath(const TArray& InPath); + void SetPath(TArray&& InPath); + + /** Clear the path */ + void ClearPath(); + + /** Get the path */ + const TArray& GetPath() const { return Path; } + + /** Get the path as text */ + const FText& GetTextPath() const { return TextPath; } + + /** Get the resolved leaf property, if any. */ + const FProperty* GetResolvedProperty() const { return ResolvedProperty.Get(); } + + /** Get the resolved leaf property's array index, if any. */ + int32 GetResolvedArrayIndex() const { return ResolvedArrayIndex; } + + /** Get the resolved pin type, if any. */ + const FEdGraphPinType& GetResolvedPinType() const { return ResolvedPinType; } + + /** Get the output pin */ + UEdGraphPin* GetOutputPin() const { return FindPinChecked(TEXT("Value"), EGPD_Output); } + +private: + /** Path that this access exposes */ + UPROPERTY() + TArray Path; + + /** Path as text, for display */ + UPROPERTY() + FText TextPath; + + /** Resolved pin type */ + UPROPERTY() + FEdGraphPinType ResolvedPinType; + + /** Generated property created during compilation */ + UPROPERTY() + FName GeneratedPropertyName = NAME_None; + + /** Resolved leaf property for the path, NULL if path cant be resolved or is empty. */ + TFieldPath ResolvedProperty; + + /** Resolved array index, if the leaf property is an array. If this is INDEX_NONE then the property refers to the entire array */ + int32 ResolvedArrayIndex = INDEX_NONE; + + // IClassVariableCreator interface + virtual void CreateClassVariablesFromBlueprint(FKismetCompilerContext& InCompilerContext) override; + + // UEdGraphNode interface + virtual void AllocateDefaultPins() override; + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + virtual void AddSearchMetaDataInfo(TArray& OutTaggedMetaData) const override; + virtual void PinConnectionListChanged(UEdGraphPin* Pin) override; + virtual bool IsCompatibleWithGraph(UEdGraph const* TargetGraph) const override; + + // UK2Node interface + virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override; + virtual void ReallocatePinsDuringReconstruction(TArray& OldPins) override; + virtual bool IsNodePure() const override { return true; } + virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; + virtual FText GetMenuCategory() const override; + + /** Helper function for pin allocation */ + void AllocatePins(UEdGraphPin* InOldOutputPin = nullptr); + + /** Attempt to resolve the path to a leaf property */ + void ResolveLeafProperty(); +}; \ No newline at end of file diff --git a/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/PropertyAccessNodeFactory.cpp b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/PropertyAccessNodeFactory.cpp new file mode 100644 index 000000000000..14e803fccc97 --- /dev/null +++ b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/PropertyAccessNodeFactory.cpp @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "PropertyAccessNodeFactory.h" +#include "SPropertyAccessNode.h" +#include "K2Node_PropertyAccess.h" + +TSharedPtr FPropertyAccessNodeFactory::CreateNode(UEdGraphNode* InNode) const +{ + if(UK2Node_PropertyAccess* K2Node_PropertyAccess = Cast(InNode)) + { + return SNew(SPropertyAccessNode, K2Node_PropertyAccess); + } + + return nullptr; +} \ No newline at end of file diff --git a/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/PropertyAccessNodeFactory.h b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/PropertyAccessNodeFactory.h new file mode 100644 index 000000000000..cd928d31feb1 --- /dev/null +++ b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/PropertyAccessNodeFactory.h @@ -0,0 +1,12 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EdGraphUtilities.h" + +class FPropertyAccessNodeFactory : public FGraphPanelNodeFactory +{ + // FGraphPanelNodeFactory interface + virtual TSharedPtr CreateNode(UEdGraphNode* InNode) const override; +}; \ No newline at end of file diff --git a/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/PropertyAccessNodeModule.cpp b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/PropertyAccessNodeModule.cpp new file mode 100644 index 000000000000..20e038f56e32 --- /dev/null +++ b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/PropertyAccessNodeModule.cpp @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" +#include "EdGraphUtilities.h" +#include "PropertyAccessNodeFactory.h" + +class FPropertyAccessNodeModule : public IModuleInterface +{ +public: + // IModuleInterface interface + virtual void StartupModule() + { + PropertyAccessNodeFactory = MakeShared(); + FEdGraphUtilities::RegisterVisualNodeFactory(PropertyAccessNodeFactory); + } + + virtual void ShutdownModule() + { + FEdGraphUtilities::UnregisterVisualNodeFactory(PropertyAccessNodeFactory); + } + + TSharedPtr PropertyAccessNodeFactory; +}; + +IMPLEMENT_MODULE(FPropertyAccessNodeModule, PropertyAccessNode) \ No newline at end of file diff --git a/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/SPropertyAccessNode.cpp b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/SPropertyAccessNode.cpp new file mode 100644 index 000000000000..08787f99a2e6 --- /dev/null +++ b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/SPropertyAccessNode.cpp @@ -0,0 +1,199 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SPropertyAccessNode.h" +#include "SLevelOfDetailBranchNode.h" +#include "PropertyAccess.h" +#include "EditorStyleSet.h" +#include "EdGraphSchema_K2.h" +#include "IPropertyAccessEditor.h" +#include "Widgets/Layout/SSpacer.h" +#include "K2Node_PropertyAccess.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "Features/IModularFeatures.h" +#include "Widgets/SBoxPanel.h" + +#define LOCTEXT_NAMESPACE "SPropertyAccessNode" + +void SPropertyAccessNode::Construct(const FArguments& InArgs, UK2Node_PropertyAccess* InNode) +{ + GraphNode = InNode; + UpdateGraphNode(); +} + +bool SPropertyAccessNode::CanBindProperty(FProperty* InProperty) const +{ + UK2Node_PropertyAccess* K2Node_PropertyAccess = CastChecked(GraphNode); + + if(InProperty == nullptr) + { + return true; + } + + if(IModularFeatures::Get().IsModularFeatureAvailable("PropertyAccessEditor")) + { + IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); + + const UEdGraphSchema_K2* Schema = GetDefault(); + + FEdGraphPinType ResolvedPinType = K2Node_PropertyAccess->GetResolvedPinType(); + FEdGraphPinType PropertyPinType; + if(ResolvedPinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) + { + // If a property is not already resolved, we allow any type... + return true; + } + else if(Schema->ConvertPropertyToPinType(InProperty, PropertyPinType)) + { + // ...unless we have a valid pin type already + return PropertyAccessEditor.GetPinTypeCompatibility(PropertyPinType, ResolvedPinType) != EPropertyAccessCompatibility::Incompatible; + } + else + { + // Otherwise fall back on a resolved property + const FProperty* ResolvedProperty = K2Node_PropertyAccess->GetResolvedProperty(); + if(ResolvedProperty != nullptr) + { + const FProperty* PropertyToUse = ResolvedProperty; + if(const FArrayProperty* ArrayProperty = CastField(ResolvedProperty)) + { + if(K2Node_PropertyAccess->GetResolvedArrayIndex() != INDEX_NONE) + { + PropertyToUse = ArrayProperty->Inner; + } + } + + // Note: We support type promotion here + return PropertyAccessEditor.GetPropertyCompatibility(InProperty, PropertyToUse) != EPropertyAccessCompatibility::Incompatible; + } + } + } + + return false; +} + +void SPropertyAccessNode::CreateBelowPinControls(TSharedPtr InMainBox) +{ + UK2Node_PropertyAccess* K2Node_PropertyAccess = CastChecked(GraphNode); + + FPropertyBindingWidgetArgs Args; + + Args.OnCanBindProperty = FOnCanBindProperty::CreateSP(this, &SPropertyAccessNode::CanBindProperty); + + Args.OnCanBindFunction = FOnCanBindFunction::CreateLambda([this](UFunction* InFunction) + { + if(InFunction->NumParms != 1 || InFunction->GetReturnProperty() == nullptr || !InFunction->HasAnyFunctionFlags(FUNC_BlueprintPure)) + { + return false; + } + + // check the return property directly + return CanBindProperty(InFunction->GetReturnProperty()); + }); + + Args.OnCanBindToClass = FOnCanBindToClass::CreateLambda([](UClass* InClass) + { + return true; + }); + + Args.OnAddBinding = FOnAddBinding::CreateLambda([K2Node_PropertyAccess](FName InPropertyName, const TArray& InBindingChain) + { + if(IModularFeatures::Get().IsModularFeatureAvailable("PropertyAccessEditor")) + { + IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); + + TArray StringPath; + PropertyAccessEditor.MakeStringPath(InBindingChain, StringPath); + K2Node_PropertyAccess->SetPath(MoveTemp(StringPath)); + } + }); + + Args.OnRemoveBinding = FOnRemoveBinding::CreateLambda([K2Node_PropertyAccess](FName InPropertyName) + { + return K2Node_PropertyAccess->ClearPath(); + }); + + Args.OnCanRemoveBinding = FOnCanRemoveBinding::CreateLambda([K2Node_PropertyAccess](FName InPropertyName) + { + return K2Node_PropertyAccess->GetPath().Num() > 0; + }); + + Args.CurrentBindingText = MakeAttributeLambda([K2Node_PropertyAccess]() + { + const FText& TextPath = K2Node_PropertyAccess->GetTextPath(); + return TextPath.IsEmpty() ? LOCTEXT("Bind", "Bind") : TextPath; + }); + + Args.CurrentBindingImage = MakeAttributeLambda([K2Node_PropertyAccess]() + { + if(const FProperty* Property = K2Node_PropertyAccess->GetResolvedProperty()) + { + if(Cast(Property->GetOwnerUField()) != nullptr) + { + static FName FunctionIcon(TEXT("GraphEditor.Function_16x")); + return FEditorStyle::GetBrush(FunctionIcon); + } + else + { + const UEdGraphSchema_K2* Schema = GetDefault(); + + FEdGraphPinType PinType; + Schema->ConvertPropertyToPinType(K2Node_PropertyAccess->GetResolvedProperty(), PinType); + return FBlueprintEditorUtils::GetIconFromPin(PinType, true); + } + } + + static FName PropertyIcon(TEXT("Kismet.Tabs.Variables")); + return FEditorStyle::GetBrush(PropertyIcon); + }); + + Args.CurrentBindingColor = MakeAttributeLambda([K2Node_PropertyAccess]() + { + if(K2Node_PropertyAccess->GetResolvedProperty()) + { + const UEdGraphSchema_K2* Schema = GetDefault(); + + FEdGraphPinType PinType; + Schema->ConvertPropertyToPinType(K2Node_PropertyAccess->GetResolvedProperty(), PinType); + + return Schema->GetPinTypeColor(PinType); + } + else + { + return FLinearColor(0.5f, 0.5f, 0.5f); + } + }); + + Args.bAllowArrayElementBindings = true; + Args.bAllowNewBindings = false; + Args.bAllowUObjectFunctions = true; + + TSharedPtr PropertyBindingWidget; + + if(IModularFeatures::Get().IsModularFeatureAvailable("PropertyAccessEditor")) + { + IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); + PropertyBindingWidget = PropertyAccessEditor.MakePropertyBindingWidget(K2Node_PropertyAccess->GetBlueprint(), Args); + } + else + { + PropertyBindingWidget = SNullWidget::NullWidget; + } + + InMainBox->AddSlot() + .AutoHeight() + .Padding(5.0f) + [ + SNew(SLevelOfDetailBranchNode) + .UseLowDetailSlot(this, &SPropertyAccessNode::UseLowDetailNodeTitles) + .LowDetail() + [ + SNew(SSpacer) + ] + .HighDetail() + [ + PropertyBindingWidget.ToSharedRef() + ] + ]; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/SPropertyAccessNode.h b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/SPropertyAccessNode.h new file mode 100644 index 000000000000..c78cd41d4ade --- /dev/null +++ b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/Private/SPropertyAccessNode.h @@ -0,0 +1,25 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "KismetNodes/SGraphNodeK2Base.h" + +class UK2Node_PropertyAccess; + +class SPropertyAccessNode : public SGraphNodeK2Base +{ +public: + SLATE_BEGIN_ARGS(SPropertyAccessNode) {} + SLATE_END_ARGS() + +public: + void Construct(const FArguments& InArgs, UK2Node_PropertyAccess* InNode); + + // SGraphNode interface + virtual void CreateBelowPinControls(TSharedPtr InMainBox) override; + +private: + // Helper for property/function binding + bool CanBindProperty(FProperty* InProperty) const; +}; \ No newline at end of file diff --git a/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/PropertyAccessNode.Build.cs b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/PropertyAccessNode.Build.cs new file mode 100644 index 000000000000..3c79f121bfae --- /dev/null +++ b/Engine/Plugins/Developer/PropertyAccessNode/Source/PropertyAccessNode/PropertyAccessNode.Build.cs @@ -0,0 +1,30 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class PropertyAccessNode : ModuleRules +{ + public PropertyAccessNode(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Slate", + "PropertyAccess", + "GraphEditor", + "BlueprintGraph", + "EditorStyle", + "Engine", + "UnrealEd", + "SlateCore", + "InputCore", + "KismetWidgets", + "KismetCompiler", + "PropertyAccessEditor", + "AnimGraph", + } + ); + } +} diff --git a/Engine/Plugins/Developer/TraceSourceFiltering/Source/SourceFilteringTrace/Private/SourceFilterSetup.cpp b/Engine/Plugins/Developer/TraceSourceFiltering/Source/SourceFilteringTrace/Private/SourceFilterSetup.cpp index 97b29f6b0074..916cc9bc964d 100644 --- a/Engine/Plugins/Developer/TraceSourceFiltering/Source/SourceFilteringTrace/Private/SourceFilterSetup.cpp +++ b/Engine/Plugins/Developer/TraceSourceFiltering/Source/SourceFilteringTrace/Private/SourceFilterSetup.cpp @@ -14,6 +14,7 @@ #include "TraceSourceFilteringSettings.h" #include "SourceFilterCollection.h" +#include "Misc/CoreDelegates.h" DEFINE_LOG_CATEGORY_STATIC(SourceFilterSetup, Display, Display); diff --git a/Engine/Plugins/Editor/AssetSearch/Source/Private/AssetSearchManager.cpp b/Engine/Plugins/Editor/AssetSearch/Source/Private/AssetSearchManager.cpp index c09aec52fda9..ab4c84ab76ad 100644 --- a/Engine/Plugins/Editor/AssetSearch/Source/Private/AssetSearchManager.cpp +++ b/Engine/Plugins/Editor/AssetSearch/Source/Private/AssetSearchManager.cpp @@ -33,9 +33,14 @@ #include "FileHelpers.h" #include "Misc/MessageDialog.h" #include "Engine/CurveTable.h" +#include "Materials/Material.h" +#include "Materials/MaterialFunction.h" +#include "Materials/MaterialParameterCollection.h" +#include "Materials/MaterialInstance.h" #include "ISearchProvider.h" +#include "Stats/Stats.h" -#include "Indexers/DataAssetIndexer.h" +#include "Indexers/GenericObjectIndexer.h" #include "Indexers/DataTableIndexer.h" #include "Indexers/BlueprintIndexer.h" #include "Indexers/WidgetBlueprintIndexer.h" @@ -44,6 +49,7 @@ #include "Indexers/LevelIndexer.h" #include "Indexers/ActorIndexer.h" #include "Indexers/SoundCueIndexer.h" +#include "Indexers/MaterialExpressionIndexer.h" #include "Providers/AssetRegistrySearchProvider.h" #define LOCTEXT_NAMESPACE "FAssetSearchManager" @@ -203,7 +209,7 @@ FAssetSearchManager::~FAssetSearchManager() void FAssetSearchManager::Start() { - RegisterAssetIndexer(UDataAsset::StaticClass(), MakeUnique()); + RegisterAssetIndexer(UDataAsset::StaticClass(), MakeUnique("DataAsset")); RegisterAssetIndexer(UDataTable::StaticClass(), MakeUnique()); RegisterAssetIndexer(UCurveTable::StaticClass(), MakeUnique()); RegisterAssetIndexer(UBlueprint::StaticClass(), MakeUnique()); @@ -212,6 +218,10 @@ void FAssetSearchManager::Start() RegisterAssetIndexer(UWorld::StaticClass(), MakeUnique()); RegisterAssetIndexer(AActor::StaticClass(), MakeUnique()); RegisterAssetIndexer(USoundCue::StaticClass(), MakeUnique()); + RegisterAssetIndexer(UMaterial::StaticClass(), MakeUnique("Material")); + RegisterAssetIndexer(UMaterialFunction::StaticClass(), MakeUnique("MaterialFunction")); + RegisterAssetIndexer(UMaterialParameterCollection::StaticClass(), MakeUnique("MaterialParameterCollection")); + RegisterAssetIndexer(UMaterialInstance::StaticClass(), MakeUnique("MaterialInstance")); RegisterSearchProvider(TEXT("AssetRegistry"), MakeUnique()); @@ -685,6 +695,8 @@ void FAssetSearchManager::AddOrUpdateAsset(const FAssetData& InAssetData, const bool FAssetSearchManager::Tick_GameThread(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FAssetSearchManager_Tick); + check(IsInGameThread()); UpdateScanningAssets(); diff --git a/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/BlueprintIndexer.cpp b/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/BlueprintIndexer.cpp index ce4ba0f058d3..8147a0baf61b 100644 --- a/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/BlueprintIndexer.cpp +++ b/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/BlueprintIndexer.cpp @@ -18,8 +18,6 @@ #define LOCTEXT_NAMESPACE "FBlueprintIndexer" -PRAGMA_DISABLE_OPTIMIZATION - enum class EBlueprintIndexerVersion { Empty, @@ -181,6 +179,4 @@ void FBlueprintIndexer::IndexMemberReference(FSearchSerializer& Serializer, cons } } -#undef LOCTEXT_NAMESPACE - -PRAGMA_ENABLE_OPTIMIZATION \ No newline at end of file +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/DataAssetIndexer.cpp b/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/GenericObjectIndexer.cpp similarity index 66% rename from Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/DataAssetIndexer.cpp rename to Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/GenericObjectIndexer.cpp index 2797d939cfcf..44d5d2a85c06 100644 --- a/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/DataAssetIndexer.cpp +++ b/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/GenericObjectIndexer.cpp @@ -1,11 +1,10 @@ // Copyright Epic Games, Inc. All Rights Reserved. -#include "DataAssetIndexer.h" +#include "GenericObjectIndexer.h" #include "Utility/IndexerUtilities.h" -#include "Engine/DataAsset.h" #include "SearchSerializer.h" -enum class EDataAssetIndexerVersion +enum class EGenericObjectIndexerVersion { Empty, Initial, @@ -15,12 +14,12 @@ enum class EDataAssetIndexerVersion LatestVersion = VersionPlusOne - 1 }; -int32 FDataAssetIndexer::GetVersion() const +int32 FGenericObjectIndexer::GetVersion() const { - return (int32)EDataAssetIndexerVersion::LatestVersion; + return (int32)EGenericObjectIndexerVersion::LatestVersion; } -void FDataAssetIndexer::IndexAsset(const UObject* InAssetObject, FSearchSerializer& Serializer) const +void FGenericObjectIndexer::IndexAsset(const UObject* InAssetObject, FSearchSerializer& Serializer) const { Serializer.BeginIndexingObject(InAssetObject, TEXT("$self")); diff --git a/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/DataAssetIndexer.h b/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/GenericObjectIndexer.h similarity index 54% rename from Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/DataAssetIndexer.h rename to Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/GenericObjectIndexer.h index b3c00e8143d2..27cbe37f94e7 100644 --- a/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/DataAssetIndexer.h +++ b/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/GenericObjectIndexer.h @@ -5,9 +5,18 @@ #include "CoreMinimal.h" #include "IAssetIndexer.h" -class FDataAssetIndexer : public IAssetIndexer +class FGenericObjectIndexer : public IAssetIndexer { - virtual FString GetName() const override { return TEXT("DataAsset"); } +public: + FGenericObjectIndexer(const FString& InName) + : Name(InName) + { + } + + virtual FString GetName() const override { return Name; } virtual int32 GetVersion() const override; virtual void IndexAsset(const UObject* InAssetObject, FSearchSerializer& Serializer) const override; + +protected: + const FString Name; }; \ No newline at end of file diff --git a/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/MaterialExpressionIndexer.cpp b/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/MaterialExpressionIndexer.cpp new file mode 100644 index 000000000000..2703b1e7e05b --- /dev/null +++ b/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/MaterialExpressionIndexer.cpp @@ -0,0 +1,72 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MaterialExpressionIndexer.h" +#include "Utility/IndexerUtilities.h" +#include "Internationalization/Text.h" +#include "Materials/Material.h" +#include "Materials/MaterialFunction.h" +#include "Materials/MaterialExpression.h" +#include "Materials/MaterialExpressionFunctionOutput.h" +#include "SearchSerializer.h" + +#include "Materials/MaterialExpressionParameter.h" +#include "Materials/MaterialExpressionFontSampleParameter.h" +#include "Materials/MaterialExpressionScalarParameter.h" +#include "Materials/MaterialExpressionStaticBoolParameter.h" +#include "Materials/MaterialExpressionStaticComponentMaskParameter.h" +#include "Materials/MaterialExpressionTextureSampleParameter.h" +#include "Materials/MaterialExpressionVectorParameter.h" + +#define LOCTEXT_NAMESPACE "FMaterialExpressionIndexer" + +enum class EMaterialExpressionIndexerVersion +{ + Empty, + Initial, + + // ------------------------------------------------------ + VersionPlusOne, + LatestVersion = VersionPlusOne - 1 +}; + +int32 FMaterialExpressionIndexer::GetVersion() const +{ + return (int32)EMaterialExpressionIndexerVersion::LatestVersion; +} + +void FMaterialExpressionIndexer::IndexAsset(const UObject* InAssetObject, FSearchSerializer& Serializer) const +{ + Serializer.BeginIndexingObject(InAssetObject, TEXT("$self")); + FIndexerUtilities::IterateIndexableProperties(InAssetObject, [&Serializer](const FProperty* Property, const FString& Value) { + Serializer.IndexProperty(Property, Value); + }); + Serializer.EndIndexingObject(); + + IndexParameters(InAssetObject, Serializer); +} + +void FMaterialExpressionIndexer::IndexParameters(const UObject* InAssetObject, FSearchSerializer& Serializer) const +{ + const UMaterialFunction* MaterialFunction = Cast(InAssetObject); + const UMaterial* Material = Cast(InAssetObject); + + TArray Expressions; + Expressions = MaterialFunction ? MaterialFunction->FunctionExpressions : Expressions; + Expressions = Material ? Material->Expressions : Expressions; + + for (const UMaterialExpression* Expression : Expressions) + { + const UMaterialExpressionFunctionOutput* ExpressionFunctionOutput = Cast(Expression); + + // Only index parameter and output nodes as those are unique, material nodes are typically non-unique + if (Expression->bIsParameterExpression || ExpressionFunctionOutput) + { + const FText ExpressionText = FText::FromName(ExpressionFunctionOutput ? ExpressionFunctionOutput->OutputName : Expression->GetParameterName()); + Serializer.BeginIndexingObject(Expression, ExpressionText); + Serializer.IndexProperty(Expression->GetClass()->GetFName().ToString(), ExpressionText); + Serializer.EndIndexingObject(); + } + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/MaterialExpressionIndexer.h b/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/MaterialExpressionIndexer.h new file mode 100644 index 000000000000..1c2e8bd815cb --- /dev/null +++ b/Engine/Plugins/Editor/AssetSearch/Source/Private/Indexers/MaterialExpressionIndexer.h @@ -0,0 +1,25 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IAssetIndexer.h" + +struct FMemberReference; + +class FMaterialExpressionIndexer : public IAssetIndexer +{ +public: + FMaterialExpressionIndexer(const FString& InName) + : Name(InName) + { + } + + virtual FString GetName() const override { return Name; } + virtual int32 GetVersion() const override; + virtual void IndexAsset(const UObject* InAssetObject, FSearchSerializer& Serializer) const override; + +private: + void IndexParameters(const UObject* InAssetObject, FSearchSerializer& Serializer) const; + const FString Name; +}; \ No newline at end of file diff --git a/Engine/Plugins/Editor/AssetSearch/Source/Private/Widgets/SSearchTreeRow.cpp b/Engine/Plugins/Editor/AssetSearch/Source/Private/Widgets/SSearchTreeRow.cpp index 456194f47608..a5a481055bc2 100644 --- a/Engine/Plugins/Editor/AssetSearch/Source/Private/Widgets/SSearchTreeRow.cpp +++ b/Engine/Plugins/Editor/AssetSearch/Source/Private/Widgets/SSearchTreeRow.cpp @@ -11,6 +11,10 @@ #include "IAssetRegistry.h" #include "AssetToolsModule.h" #include "Kismet2/KismetEditorUtilities.h" +#include "Editor/MaterialEditor/Public/IMaterialEditor.h" + +#include "Materials/Material.h" +#include "Materials/MaterialFunction.h" #define LOCTEXT_NAMESPACE "SObjectBrowserTableRow" @@ -165,11 +169,35 @@ FReply SSearchTreeRow::OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, c } else if (BrowserObject->GetType() == ESearchNodeType::Object || BrowserObject->GetType() == ESearchNodeType::Property) { - FSoftObjectPath ReferencePath(BrowserObject->GetObjectPath()); + FSoftObjectPath ReferencePath(BrowserObject->GetObjectPath()); UObject* Object = ReferencePath.TryLoad(); - if (Object->GetTypedOuter()) + if (Object->GetTypedOuter()) { - FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(Object, false); + FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(Object, false); + } + if (Object->GetTypedOuter() || Object->GetTypedOuter()) + { + const FString& AssetPathName = ReferencePath.GetAssetPathString(); + UPackage* Package = LoadPackage(NULL, *AssetPathName, LOAD_NoRedirects); + + if (Package) + { + Package->FullyLoad(); + + FString AssetName = FPaths::GetBaseFilename(AssetPathName); + UObject* MaterialObject = FindObject(Package, *AssetName); + + if (MaterialObject != NULL) + { + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + AssetEditorSubsystem->OpenEditorForAsset(MaterialObject); + + if (IMaterialEditor* MaterialEditor = (IMaterialEditor*)(AssetEditorSubsystem->FindEditorForAsset(MaterialObject, true))) + { + MaterialEditor->JumpToExpression(Cast(Object)); + } + } + } } else { diff --git a/Engine/Plugins/Editor/ContentBrowser/ContentBrowserAssetDataSource/Source/ContentBrowserAssetDataSource/Private/ContentBrowserAssetDataCore.cpp b/Engine/Plugins/Editor/ContentBrowser/ContentBrowserAssetDataSource/Source/ContentBrowserAssetDataSource/Private/ContentBrowserAssetDataCore.cpp index ad7126ee8c07..3fcfc367bb42 100644 --- a/Engine/Plugins/Editor/ContentBrowser/ContentBrowserAssetDataSource/Source/ContentBrowserAssetDataSource/Private/ContentBrowserAssetDataCore.cpp +++ b/Engine/Plugins/Editor/ContentBrowser/ContentBrowserAssetDataSource/Source/ContentBrowserAssetDataSource/Private/ContentBrowserAssetDataCore.cpp @@ -250,19 +250,16 @@ bool EditOrPreviewAssetFileItems(TArrayView AssetTypeActions = AssetPayload->GetAssetTypeActions(); + bool bShouldLoadAsset = AssetTypeActions.IsValid() ? AssetTypeActions->CanLoadAssetForPreviewOrEdit(AssetData) : true; + if (bShouldLoadAsset) { - if (TSharedPtr AssetTypeActions = AssetPayload->GetAssetTypeActions()) + if (UObject* Asset = AssetData.GetAsset()) { - // Add this asset to the list associated with the asset type action object - TArray& ObjList = TypeActionsToObjects.FindOrAdd(AssetTypeActions.ToSharedRef()); + TArray& ObjList = AssetTypeActions.IsValid() ? TypeActionsToObjects.FindOrAdd(AssetTypeActions.ToSharedRef()) : ObjectsWithoutTypeActions; ObjList.AddUnique(Asset); } - else - { - ObjectsWithoutTypeActions.AddUnique(Asset); - } } } @@ -655,7 +652,7 @@ bool CanRenameItem(IAssetTools* InAssetTools, const UContentBrowserDataSource* I if (TSharedPtr AssetPayload = GetAssetFileItemPayload(InOwnerDataSource, InItem)) { - return CanRenameAssetFileItem(InAssetTools, *AssetPayload, InNewName, OutErrorMsg); + return CanRenameAssetFileItem(InAssetTools, *AssetPayload, InNewName, InItem.IsTemporary(), OutErrorMsg); } return false; @@ -689,7 +686,7 @@ bool CanRenameAssetFolderItem(IAssetTools* InAssetTools, const FContentBrowserAs return true; } -bool CanRenameAssetFileItem(IAssetTools* InAssetTools, const FContentBrowserAssetFileItemDataPayload& InAssetPayload, const FString* InNewName, FText* OutErrorMsg) +bool CanRenameAssetFileItem(IAssetTools* InAssetTools, const FContentBrowserAssetFileItemDataPayload& InAssetPayload, const FString* InNewName, const bool InIsTempoarary, FText* OutErrorMsg) { if (!CanModifyAssetFileItem(InAssetTools, InAssetPayload, OutErrorMsg)) { @@ -708,7 +705,7 @@ bool CanRenameAssetFileItem(IAssetTools* InAssetTools, const FContentBrowserAsse return false; } - if (InNewName) + if (InNewName && (InIsTempoarary || InAssetPayload.GetAssetData().AssetName != FName(**InNewName))) // Deliberately ignore case here to allow case-only renames of existing assets { const FString PackageName = InAssetPayload.GetAssetData().PackagePath.ToString() / *InNewName; const FString ObjectPath = FString::Printf(TEXT("%s.%s"), *PackageName, **InNewName); @@ -736,7 +733,7 @@ bool RenameItem(IAssetTools* InAssetTools, IAssetRegistry* InAssetRegistry, cons if (TSharedPtr AssetPayload = GetAssetFileItemPayload(InOwnerDataSource, InItem)) { - if (CanRenameAssetFileItem(InAssetTools, *AssetPayload, &InNewName, nullptr)) + if (CanRenameAssetFileItem(InAssetTools, *AssetPayload, &InNewName, InItem.IsTemporary(), nullptr)) { return RenameAssetFileItem(InAssetTools, *AssetPayload, InNewName); } diff --git a/Engine/Plugins/Editor/ContentBrowser/ContentBrowserAssetDataSource/Source/ContentBrowserAssetDataSource/Private/ContentBrowserAssetDataPayload.cpp b/Engine/Plugins/Editor/ContentBrowser/ContentBrowserAssetDataSource/Source/ContentBrowserAssetDataSource/Private/ContentBrowserAssetDataPayload.cpp index 0043f3cd6db1..f12ac5d8dc92 100644 --- a/Engine/Plugins/Editor/ContentBrowser/ContentBrowserAssetDataSource/Source/ContentBrowserAssetDataSource/Private/ContentBrowserAssetDataPayload.cpp +++ b/Engine/Plugins/Editor/ContentBrowser/ContentBrowserAssetDataSource/Source/ContentBrowserAssetDataSource/Private/ContentBrowserAssetDataPayload.cpp @@ -11,7 +11,7 @@ const FString& FContentBrowserAssetFolderItemDataPayload::GetFilename() const { if (!bHasCachedFilename) { - FPackageName::TryConvertLongPackageNameToFilename(InternalPath.ToString(), CachedFilename); + FPackageName::TryConvertLongPackageNameToFilename(InternalPath.ToString() / FString(), CachedFilename); bHasCachedFilename = true; } return CachedFilename; diff --git a/Engine/Plugins/Editor/ContentBrowser/ContentBrowserAssetDataSource/Source/ContentBrowserAssetDataSource/Public/ContentBrowserAssetDataCore.h b/Engine/Plugins/Editor/ContentBrowser/ContentBrowserAssetDataSource/Source/ContentBrowserAssetDataSource/Public/ContentBrowserAssetDataCore.h index c45305d96d86..d1b9f4d37088 100644 --- a/Engine/Plugins/Editor/ContentBrowser/ContentBrowserAssetDataSource/Source/ContentBrowserAssetDataSource/Public/ContentBrowserAssetDataCore.h +++ b/Engine/Plugins/Editor/ContentBrowser/ContentBrowserAssetDataSource/Source/ContentBrowserAssetDataSource/Public/ContentBrowserAssetDataCore.h @@ -94,7 +94,7 @@ namespace ContentBrowserAssetData CONTENTBROWSERASSETDATASOURCE_API bool CanRenameAssetFolderItem(IAssetTools* InAssetTools, const FContentBrowserAssetFolderItemDataPayload& InFolderPayload, const FString* InNewName, FText* OutErrorMsg); - CONTENTBROWSERASSETDATASOURCE_API bool CanRenameAssetFileItem(IAssetTools* InAssetTools, const FContentBrowserAssetFileItemDataPayload& InAssetPayload, const FString* InNewName, FText* OutErrorMsg); + CONTENTBROWSERASSETDATASOURCE_API bool CanRenameAssetFileItem(IAssetTools* InAssetTools, const FContentBrowserAssetFileItemDataPayload& InAssetPayload, const FString* InNewName, const bool InIsTempoarary, FText* OutErrorMsg); CONTENTBROWSERASSETDATASOURCE_API bool RenameItem(IAssetTools* InAssetTools, IAssetRegistry* InAssetRegistry, const UContentBrowserDataSource* InOwnerDataSource, const FContentBrowserItemData& InItem, const FString& InNewName); diff --git a/Engine/Plugins/Editor/ContentBrowser/ContentBrowserClassDataSource/Source/ContentBrowserClassDataSource/Private/ContentBrowserClassDataSource.cpp b/Engine/Plugins/Editor/ContentBrowser/ContentBrowserClassDataSource/Source/ContentBrowserClassDataSource/Private/ContentBrowserClassDataSource.cpp index e940c050d01a..5fc462b3b885 100644 --- a/Engine/Plugins/Editor/ContentBrowser/ContentBrowserClassDataSource/Source/ContentBrowserClassDataSource/Private/ContentBrowserClassDataSource.cpp +++ b/Engine/Plugins/Editor/ContentBrowser/ContentBrowserClassDataSource/Source/ContentBrowserClassDataSource/Private/ContentBrowserClassDataSource.cpp @@ -29,9 +29,6 @@ void UContentBrowserClassDataSource::Initialize(const FName InMountRoot, const b CollectionManager = &FCollectionManagerModule::GetModule().Get(); - NativeClassHierarchy = MakeShared(); - NativeClassHierarchy->OnClassHierarchyUpdated().AddUObject(this, &UContentBrowserClassDataSource::NotifyItemDataRefreshed); - // Bind the class specific menu extensions { if (UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("ContentBrowser.AddNewContextMenu")) @@ -84,6 +81,8 @@ void UContentBrowserClassDataSource::CompileFilter(const FName InPath, const FCo return; } + ConditionalCreateNativeClassHierarchy(); + FNativeClassHierarchyFilter ClassHierarchyFilter; ClassHierarchyFilter.ClassPaths.Add(InternalPath); ClassHierarchyFilter.bRecursivePaths = InFilter.bRecursivePaths; @@ -212,6 +211,8 @@ void UContentBrowserClassDataSource::EnumerateItemsAtPath(const FName InPath, co return; } + ConditionalCreateNativeClassHierarchy(); + if (EnumHasAnyFlags(InItemTypeFilter, EContentBrowserItemTypeFilter::IncludeFolders)) { if (TSharedPtr FolderNode = NativeClassHierarchy->FindNode(InternalPath, ENativeClassHierarchyNodeType::Folder)) @@ -242,6 +243,8 @@ bool UContentBrowserClassDataSource::IsFolderVisibleIfHidingEmpty(const FName In return false; } + ConditionalCreateNativeClassHierarchy(); + return ContentBrowserClassData::IsTopLevelFolder(InternalPath) || NativeClassHierarchy->HasClasses(InternalPath, /*bRecursive*/true); } @@ -406,6 +409,8 @@ FContentBrowserItemData UContentBrowserClassDataSource::CreateClassFolderItem(co FContentBrowserItemData UContentBrowserClassDataSource::CreateClassFileItem(UClass* InClass) { + ConditionalCreateNativeClassHierarchy(); + FName ClassPath; { FString ClassPathStr; @@ -462,6 +467,8 @@ void UContentBrowserClassDataSource::PopulateAddNewContextMenu(UToolMenu* InMenu void UContentBrowserClassDataSource::OnNewClassRequested(const FName InSelectedPath) { + ConditionalCreateNativeClassHierarchy(); + // Parse out the on disk location for the currently selected path, this will then be used as the default location for the new class (if a valid project module location) FString ExistingFolderPath; if (!InSelectedPath.IsNone()) @@ -476,4 +483,13 @@ void UContentBrowserClassDataSource::OnNewClassRequested(const FName InSelectedP ); } +void UContentBrowserClassDataSource::ConditionalCreateNativeClassHierarchy() +{ + if (!NativeClassHierarchy) + { + NativeClassHierarchy = MakeShared(); + NativeClassHierarchy->OnClassHierarchyUpdated().AddUObject(this, &UContentBrowserClassDataSource::NotifyItemDataRefreshed); + } +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Editor/ContentBrowser/ContentBrowserClassDataSource/Source/ContentBrowserClassDataSource/Private/NativeClassHierarchy.cpp b/Engine/Plugins/Editor/ContentBrowser/ContentBrowserClassDataSource/Source/ContentBrowserClassDataSource/Private/NativeClassHierarchy.cpp index cd3cc5022ece..9b17ff55d860 100644 --- a/Engine/Plugins/Editor/ContentBrowser/ContentBrowserClassDataSource/Source/ContentBrowserClassDataSource/Private/NativeClassHierarchy.cpp +++ b/Engine/Plugins/Editor/ContentBrowser/ContentBrowserClassDataSource/Source/ContentBrowserClassDataSource/Private/NativeClassHierarchy.cpp @@ -13,6 +13,8 @@ #include "Misc/HotReloadInterface.h" #include "SourceCodeNavigation.h" #include "Interfaces/IPluginManager.h" +#include "Interfaces/IProjectManager.h" +#include "ProjectDescriptor.h" DEFINE_LOG_CATEGORY_STATIC(LogNativeClassHierarchy, Verbose, All); @@ -588,15 +590,11 @@ TSet FNativeClassHierarchy::GetGameModules() if (FApp::HasProjectName()) { - FGameProjectGenerationModule& GameProjectModule = FGameProjectGenerationModule::Get(); - - // Build up a set of known game modules - used to work out which modules populate Classes_Game - if (GameProjectModule.ProjectHasCodeFiles()) + if (const FProjectDescriptor* const CurrentProject = IProjectManager::Get().GetCurrentProject()) { - TArray GameModulesInfo = GameProjectModule.GetCurrentProjectModules(); - for (const auto& GameModuleInfo : GameModulesInfo) + for (const FModuleDescriptor& Module : CurrentProject->Modules) { - GameModules.Add(FName(*GameModuleInfo.ModuleName)); + GameModules.Add(Module.Name); } } } diff --git a/Engine/Plugins/Editor/ContentBrowser/ContentBrowserClassDataSource/Source/ContentBrowserClassDataSource/Public/ContentBrowserClassDataSource.h b/Engine/Plugins/Editor/ContentBrowser/ContentBrowserClassDataSource/Source/ContentBrowserClassDataSource/Public/ContentBrowserClassDataSource.h index 47a1a697f3f2..e8049b762093 100644 --- a/Engine/Plugins/Editor/ContentBrowser/ContentBrowserClassDataSource/Source/ContentBrowserClassDataSource/Public/ContentBrowserClassDataSource.h +++ b/Engine/Plugins/Editor/ContentBrowser/ContentBrowserClassDataSource/Source/ContentBrowserClassDataSource/Public/ContentBrowserClassDataSource.h @@ -90,6 +90,8 @@ private: void PopulateAddNewContextMenu(UToolMenu* InMenu); + void ConditionalCreateNativeClassHierarchy(); + TSharedPtr NativeClassHierarchy; TSharedPtr ClassTypeActions; diff --git a/Engine/Plugins/Editor/EditorScriptingUtilities/Source/EditorScriptingUtilities/Private/EditorLevelLibrary.cpp b/Engine/Plugins/Editor/EditorScriptingUtilities/Source/EditorScriptingUtilities/Private/EditorLevelLibrary.cpp index fff06f0a8126..f44a392667a6 100644 --- a/Engine/Plugins/Editor/EditorScriptingUtilities/Source/EditorScriptingUtilities/Private/EditorLevelLibrary.cpp +++ b/Engine/Plugins/Editor/EditorScriptingUtilities/Source/EditorScriptingUtilities/Private/EditorLevelLibrary.cpp @@ -235,6 +235,7 @@ AActor* UEditorLevelLibrary::SpawnActorFromClass(TSubclassOf Actor return EditorActorSubsystem ? EditorActorSubsystem->SpawnActorFromClass(ActorClass, Location, Rotation, bTransient) : nullptr; } + bool UEditorLevelLibrary::DestroyActor(class AActor* ToDestroyActor) { UEditorActorSubsystem* EditorActorSubsystem = GEditor->GetEditorSubsystem(); @@ -257,6 +258,29 @@ UWorld* UEditorLevelLibrary::GetGameWorld() return UnrealEditorSubsystem ? UnrealEditorSubsystem->GetEditorWorld() : nullptr; } +TArray UEditorLevelLibrary::GetPIEWorlds(bool bIncludeDedicatedServer) +{ + TGuardValue UnattendedScriptGuard(GIsRunningUnattendedScript, true); + + TArray PIEWorlds; + + if (GEditor) + { + for (const FWorldContext& WorldContext : GEngine->GetWorldContexts()) + { + if (WorldContext.WorldType == EWorldType::PIE) + { + if (bIncludeDedicatedServer || !WorldContext.RunAsDedicated) + { + PIEWorlds.Add(WorldContext.World()); + } + } + } + } + + return PIEWorlds; +} + bool UEditorLevelLibrary::NewLevel(const FString& AssetPath) { ULevelEditorSubsystem* LevelEditorSubsystem = GEditor->GetEditorSubsystem(); diff --git a/Engine/Plugins/Editor/EditorScriptingUtilities/Source/EditorScriptingUtilities/Public/EditorLevelLibrary.h b/Engine/Plugins/Editor/EditorScriptingUtilities/Source/EditorScriptingUtilities/Public/EditorLevelLibrary.h index 6c35751091fb..2e8fc0fddb11 100644 --- a/Engine/Plugins/Editor/EditorScriptingUtilities/Source/EditorScriptingUtilities/Public/EditorLevelLibrary.h +++ b/Engine/Plugins/Editor/EditorScriptingUtilities/Source/EditorScriptingUtilities/Public/EditorLevelLibrary.h @@ -228,6 +228,9 @@ public: UE_DEPRECATED(5.0, "The Editor Scripting Utilities Plugin is deprecated - Use the function in Unreal Editor Subsystem") UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Level Utility", meta = (DeprecatedFunction, DeprecatedMessage = "The Editor Scripting Utilities Plugin is deprecated - Use the function in Unreal Editor Subsystem")) static UWorld* GetGameWorld(); + + UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Level Utility") + static TArray GetPIEWorlds(bool bIncludeDedicatedServer); public: /** diff --git a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagGraphPin.cpp b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagGraphPin.cpp index ba3aa2017c10..f2e853f3fee3 100644 --- a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagGraphPin.cpp +++ b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagGraphPin.cpp @@ -138,7 +138,7 @@ void SGameplayTagGraphPin::RefreshTagList() TagString += TEXT(")"); } FString CurrentDefaultValue = GraphPinObj->GetDefaultAsString(); - if (CurrentDefaultValue.IsEmpty()) + if (CurrentDefaultValue.IsEmpty() || CurrentDefaultValue == TEXT("(TagName=\"\")")) { CurrentDefaultValue = FString(TEXT("")); } diff --git a/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/CADLibrary/Private/CoreTechHelper.cpp b/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/CADLibrary/Private/CoreTechHelper.cpp index a020d0104bf5..cc3fce70a848 100644 --- a/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/CADLibrary/Private/CoreTechHelper.cpp +++ b/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/CADLibrary/Private/CoreTechHelper.cpp @@ -503,7 +503,7 @@ TSharedPtr CreateUEPbrMaterialFromColor(const FC TSharedRef MaterialElement = FDatasmithSceneFactory::CreateUEPbrMaterial(*Name); MaterialElement->SetLabel(*Label); - FLinearColor LinearColor = FLinearColor::FromPow22Color(InColor); + FLinearColor LinearColor = FLinearColor::FromSRGBColor(InColor); IDatasmithMaterialExpressionColor* ColorExpression = MaterialElement->AddMaterialExpression(); ColorExpression->SetName(TEXT("Base Color")); diff --git a/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/DatasmithCADTranslator/Private/DatasmithCADTranslator.cpp b/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/DatasmithCADTranslator/Private/DatasmithCADTranslator.cpp index a0130b8b6d6d..4df19219d3cf 100644 --- a/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/DatasmithCADTranslator/Private/DatasmithCADTranslator.cpp +++ b/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/DatasmithCADTranslator/Private/DatasmithCADTranslator.cpp @@ -86,7 +86,13 @@ bool FDatasmithCADTranslator::LoadScene(TSharedRef DatasmithSce } ImportParameters.ModelCoordSys = CADLibrary::EModelCoordSystem::ZUp_RightHanded; - if (FileDescription.Extension == TEXT("sldprt") || FileDescription.Extension == TEXT("sldasm") || // Solidworks + if (FileDescription.Extension == TEXT("prt")) // NX + { + ImportParameters.ModelCoordSys = CADLibrary::EModelCoordSystem::ZUp_RightHanded; + ImportParameters.DisplayPreference = CADLibrary::EDisplayPreference::ColorOnly; + ImportParameters.Propagation = CADLibrary::EDisplayDataPropagationMode::BodyOnly; + } + else if (FileDescription.Extension == TEXT("sldprt") || FileDescription.Extension == TEXT("sldasm") || // Solidworks FileDescription.Extension == TEXT("iam") || FileDescription.Extension == TEXT("ipt") || // Inventor FileDescription.Extension.StartsWith(TEXT("asm")) || FileDescription.Extension.StartsWith(TEXT("creo")) || FileDescription.Extension.StartsWith(TEXT("prt")) // Creo ) diff --git a/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/DatasmithOpenNurbsTranslator/Private/DatasmithOpenNurbsTranslator.cpp b/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/DatasmithOpenNurbsTranslator/Private/DatasmithOpenNurbsTranslator.cpp index c5ce55c05040..f7c994bb1c5e 100644 --- a/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/DatasmithOpenNurbsTranslator/Private/DatasmithOpenNurbsTranslator.cpp +++ b/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/DatasmithOpenNurbsTranslator/Private/DatasmithOpenNurbsTranslator.cpp @@ -868,7 +868,7 @@ void FOpenNurbsTranslatorImpl::TranslateMaterialTable(const ON_ObjectArray FOpenNurbsTranslatorImpl::GetDefaultMa Material->SetLabel(TEXT("Default")); FColor Color(250, 250, 250, 255); - FLinearColor LinearColor = FLinearColor::FromPow22Color(Color); + FLinearColor LinearColor = FLinearColor::FromSRGBColor(Color); IDatasmithMaterialExpressionColor* ColorExpression = Material->AddMaterialExpression(); ColorExpression->SetName(TEXT("Diffuse Color")); diff --git a/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/DatasmithWireTranslator/Private/DatasmithWireTranslator.cpp b/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/DatasmithWireTranslator/Private/DatasmithWireTranslator.cpp index 4706ebf5cfa8..8421b4a8ac26 100644 --- a/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/DatasmithWireTranslator/Private/DatasmithWireTranslator.cpp +++ b/Engine/Plugins/Enterprise/DatasmithCADImporter/Source/DatasmithWireTranslator/Private/DatasmithWireTranslator.cpp @@ -81,12 +81,12 @@ public: // Generates BodyData's unique id from AlDagNode objects FString GetUUID(const FString& ParentUUID) { - if(ShellSet.Num() == 0) + if (ShellSet.Num() == 0) { return ParentUUID; } - auto GetLongPersistentID = []( AlDagNode& DagNode ) -> int64 + auto GetLongPersistentID = [](AlDagNode& DagNode) -> int64 { union { int a[2]; @@ -96,32 +96,32 @@ public: Value.b = -1; AlPersistentID* PersistentID = nullptr; - DagNode.persistentID( PersistentID ); + DagNode.persistentID(PersistentID); - if( PersistentID != nullptr ) + if (PersistentID != nullptr) { int Dummy; - PersistentID->id( Value.a[0], Value.a[1], Dummy, Dummy ); + PersistentID->id(Value.a[0], Value.a[1], Dummy, Dummy); } return Value.b; }; - if(ShellSet.Num() > 1) + if (ShellSet.Num() > 1) { ShellSet.Sort([&](AlDagNode& A, AlDagNode& B) - { - return GetLongPersistentID( A ) < GetLongPersistentID( B ); - }); + { + return GetLongPersistentID(A) < GetLongPersistentID(B); + }); } FString Buffer; - for(AlDagNode* DagNode : ShellSet) + for (AlDagNode* DagNode : ShellSet) { - Buffer += FString::Printf(TEXT("%016lx"), GetLongPersistentID( *DagNode ) ); + Buffer += FString::Printf(TEXT("%016lx"), GetLongPersistentID(*DagNode)); } - return GetUEUUIDFromAIPersistentID( ParentUUID, Buffer ); + return GetUEUUIDFromAIPersistentID(ParentUUID, Buffer); } }; @@ -222,12 +222,57 @@ private: TOptional MeshDagNodeWithExternalMesher(TSharedRef DagNode, TSharedRef MeshElement, CADLibrary::FMeshParameters& MeshParameters); #endif - TOptional< FMeshDescription > ImportMesh(AlMesh& Mesh, CADLibrary::FMeshParameters& MeshParameters); + TOptional< FMeshDescription > ImportMesh(AlMesh& Mesh, CADLibrary::FMeshParameters& MeshParameters); - void AddAlBlinnParameters(AlShader *Shader, TSharedRef MaterialElement); - void AddAlLambertParameters(AlShader *Shader, TSharedRef MaterialElement); - void AddAlLightSourceParameters(AlShader *Shader, TSharedRef MaterialElement); - void AddAlPhongParameters(AlShader *Shader, TSharedRef MaterialElement); + FORCEINLINE bool IsTransparent(FColor& TransparencyColor) + { + float Opacity = 1.0f - ((float)(TransparencyColor.R + TransparencyColor.G + TransparencyColor.B)) / 765.0f; + return !FMath::IsNearlyEqual(Opacity, 1.0f); + } + + FORCEINLINE bool GetCommonParameters(AlShadingFields Field, double Value, FColor& Color, FColor& TransparencyColor, FColor& IncandescenceColor, double GlowIntensity) + { + switch (Field) + { + case AlShadingFields::kFLD_SHADING_COMMON_COLOR_R: + Color.R = (uint8)Value; + return true; + case AlShadingFields::kFLD_SHADING_COMMON_COLOR_G: + Color.G = (uint8)Value; + return true; + case AlShadingFields::kFLD_SHADING_COMMON_COLOR_B: + Color.B = (uint8)Value; + return true; + case AlShadingFields::kFLD_SHADING_COMMON_INCANDESCENCE_R: + IncandescenceColor.R = (uint8)Value; + return true; + case AlShadingFields::kFLD_SHADING_COMMON_INCANDESCENCE_G: + IncandescenceColor.G = (uint8)Value; + return true; + case AlShadingFields::kFLD_SHADING_COMMON_INCANDESCENCE_B: + IncandescenceColor.B = (uint8)Value; + return true; + case AlShadingFields::kFLD_SHADING_COMMON_TRANSPARENCY_R: + TransparencyColor.R = (uint8)Value; + return true; + case AlShadingFields::kFLD_SHADING_COMMON_TRANSPARENCY_G: + TransparencyColor.G = (uint8)Value; + return true; + case AlShadingFields::kFLD_SHADING_COMMON_TRANSPARENCY_B: + TransparencyColor.B = (uint8)Value; + return true; + case AlShadingFields::kFLD_SHADING_COMMON_GLOW_INTENSITY: + GlowIntensity = Value; + return true; + default : + return false; + } + } + + void AddAlBlinnParameters(AlShader* Shader, TSharedRef MaterialElement); + void AddAlLambertParameters(AlShader* Shader, TSharedRef MaterialElement); + void AddAlLightSourceParameters(AlShader* Shader, TSharedRef MaterialElement); + void AddAlPhongParameters(AlShader* Shader, TSharedRef MaterialElement); private: TSharedRef DatasmithScene; @@ -311,13 +356,13 @@ bool FWireTranslatorImpl::Read() return true; } -void FWireTranslatorImpl::AddAlBlinnParameters(AlShader *Shader, TSharedRef MaterialElement) +void FWireTranslatorImpl::AddAlBlinnParameters(AlShader* Shader, TSharedRef MaterialElement) { // Default values for a Blinn material - double Color[] = { 0.57, 0.58, 0.60 }; - double TransparencyColor[] = { 0.0, 0.0, 0.0 }; - double IncandescenceColor[] = { 0.0, 0.0, 0.0 }; - double SpecularColor[] = { 0.15, 0.15, 0.15 } ; + FColor Color(145, 148, 153); + FColor TransparencyColor(0, 0, 0); + FColor IncandescenceColor(0, 0, 0); + FColor SpecularColor(38, 38, 38); double Diffuse = 1.0; double GlowIntensity = 0.0; double Gloss = 0.8; @@ -327,7 +372,7 @@ void FWireTranslatorImpl::AddAlBlinnParameters(AlShader *Shader, TSharedReffields(); - for (AlShadingFieldItem* Item = static_cast(List->first()); Item; Item = Item->nextField()) + for (AlShadingFieldItem* Item = static_cast(List->first()); Item; Item = Item->nextField()) { double Value = 0.0f; statusCode ErrorCode = Shader->parameter(Item->field(), Value); @@ -336,6 +381,11 @@ void FWireTranslatorImpl::AddAlBlinnParameters(AlShader *Shader, TSharedReffield(), Value, Color, TransparencyColor, IncandescenceColor, GlowIntensity)) + { + continue; + } + switch (Item->field()) { case AlShadingFields::kFLD_SHADING_BLINN_DIFFUSE: @@ -345,13 +395,13 @@ void FWireTranslatorImpl::AddAlBlinnParameters(AlShader *Shader, TSharedRefAddMaterialExpression(); @@ -414,7 +431,7 @@ void FWireTranslatorImpl::AddAlBlinnParameters(AlShader *Shader, TSharedRefAddMaterialExpression(); SpecularColorExpression->SetName(TEXT("SpecularColor")); - SpecularColorExpression->GetColor() = FLinearColor(pow(SpecularColor[0], 2.2), pow(SpecularColor[1], 2.2), pow(SpecularColor[2], 2.2), 1.0f); + SpecularColorExpression->GetColor() = FLinearColor::FromSRGBColor(SpecularColor); IDatasmithMaterialExpressionScalar* SpecularityExpression = MaterialElement->AddMaterialExpression(); SpecularityExpression->GetScalar() = Specularity * 0.3; @@ -434,15 +451,15 @@ void FWireTranslatorImpl::AddAlBlinnParameters(AlShader *Shader, TSharedRefAddMaterialExpression(); ColorExpression->SetName(TEXT("Color")); - ColorExpression->GetColor() = FLinearColor(pow(Color[0] / 255.0, 2.2), pow(Color[1] / 255.0, 2.2), pow(Color[2] / 255.0, 2.2), 255); + ColorExpression->GetColor() = FLinearColor::FromSRGBColor(Color); IDatasmithMaterialExpressionColor* IncandescenceColorExpression = MaterialElement->AddMaterialExpression(); IncandescenceColorExpression->SetName(TEXT("IncandescenceColor")); - IncandescenceColorExpression->GetColor() = FLinearColor(pow(IncandescenceColor[0] / 255.0, 2.2), pow(IncandescenceColor[1] / 255.0, 2.2), pow(IncandescenceColor[2] / 255.0, 2.2), 255); + IncandescenceColorExpression->GetColor() = FLinearColor::FromSRGBColor(IncandescenceColor); IDatasmithMaterialExpressionColor* TransparencyColorExpression = MaterialElement->AddMaterialExpression(); TransparencyColorExpression->SetName(TEXT("TransparencyColor")); - TransparencyColorExpression->GetColor() = FLinearColor(pow(TransparencyColor[0] / 255.0, 2.2), pow(TransparencyColor[1] / 255.0, 2.2), pow(TransparencyColor[2] / 255.0, 2.2), 255); + TransparencyColorExpression->GetColor() = FLinearColor::FromSRGBColor(TransparencyColor); IDatasmithMaterialExpressionScalar* GlowIntensityExpression = MaterialElement->AddMaterialExpression(); GlowIntensityExpression->GetScalar() = GlowIntensity; @@ -616,24 +633,24 @@ void FWireTranslatorImpl::AddAlBlinnParameters(AlShader *Shader, TSharedRefGetOpacity().SetExpression(Divide); MaterialElement->SetParentLabel(TEXT("M_DatasmithAliasBlinnTransparent")); } - else + else { MaterialElement->SetParentLabel(TEXT("M_DatasmithAliasBlinn")); } } -void FWireTranslatorImpl::AddAlLambertParameters(AlShader *Shader, TSharedRef MaterialElement) +void FWireTranslatorImpl::AddAlLambertParameters(AlShader* Shader, TSharedRef MaterialElement) { // Default values for a Lambert material - double Color[] = { 0.57, 0.58, 0.60 }; - double TransparencyColor[] = { 0.0, 0.0, 0.0 }; - double IncandescenceColor[] = { 0.0, 0.0, 0.0 }; + FColor Color(145, 148, 153); + FColor TransparencyColor(0, 0, 0); + FColor IncandescenceColor(0, 0, 0); double Diffuse = 1.0; double GlowIntensity = 0.0; AlList* List = Shader->fields(); - for (AlShadingFieldItem* Item = static_cast(List->first()); Item; Item = Item->nextField()) + for (AlShadingFieldItem* Item = static_cast(List->first()); Item; Item = Item->nextField()) { double Value = 0.0f; statusCode ErrorCode = Shader->parameter(Item->field(), Value); @@ -642,48 +659,20 @@ void FWireTranslatorImpl::AddAlLambertParameters(AlShader *Shader, TSharedReffield(), Value, Color, TransparencyColor, IncandescenceColor, GlowIntensity)) + { + continue; + } + switch (Item->field()) { case AlShadingFields::kFLD_SHADING_LAMBERT_DIFFUSE: Diffuse = Value; break; - case AlShadingFields::kFLD_SHADING_COMMON_COLOR_R: - Color[0] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_COLOR_G: - Color[1] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_COLOR_B: - Color[2] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_INCANDESCENCE_R: - IncandescenceColor[0] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_INCANDESCENCE_G: - IncandescenceColor[1] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_INCANDESCENCE_B: - IncandescenceColor[2] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_TRANSPARENCY_R: - TransparencyColor[0] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_TRANSPARENCY_G: - TransparencyColor[1] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_TRANSPARENCY_B: - TransparencyColor[2] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_GLOW_INTENSITY: - GlowIntensity = Value; - break; - default: - continue; } } - float Opacity = 1.0f - (TransparencyColor[0] + TransparencyColor[1] + TransparencyColor[2]) / 3.0f; - bool bIsTransparent = !FMath::IsNearlyEqual(Opacity, 1.0f); + bool bIsTransparent = IsTransparent(TransparencyColor); // Construct parameter expressions IDatasmithMaterialExpressionScalar* DiffuseExpression = MaterialElement->AddMaterialExpression(); @@ -692,15 +681,15 @@ void FWireTranslatorImpl::AddAlLambertParameters(AlShader *Shader, TSharedRefAddMaterialExpression(); ColorExpression->SetName(TEXT("Color")); - ColorExpression->GetColor() = FLinearColor(pow(Color[0] / 255.0, 2.2), pow(Color[1] / 255.0, 2.2), pow(Color[2] / 255.0, 2.2), 255); + ColorExpression->GetColor() = FLinearColor::FromSRGBColor(Color); IDatasmithMaterialExpressionColor* IncandescenceColorExpression = MaterialElement->AddMaterialExpression(); IncandescenceColorExpression->SetName(TEXT("IncandescenceColor")); - IncandescenceColorExpression->GetColor() = FLinearColor(pow(IncandescenceColor[0] / 255.0, 2.2), pow(IncandescenceColor[1] / 255.0, 2.2), pow(IncandescenceColor[2] / 255.0, 2.2), 255); + IncandescenceColorExpression->GetColor() = FLinearColor::FromSRGBColor(IncandescenceColor); IDatasmithMaterialExpressionColor* TransparencyColorExpression = MaterialElement->AddMaterialExpression(); TransparencyColorExpression->SetName(TEXT("TransparencyColor")); - TransparencyColorExpression->GetColor() = FLinearColor(pow(TransparencyColor[0] / 255.0, 2.2), pow(TransparencyColor[1] / 255.0, 2.2), pow(TransparencyColor[2] / 255.0, 2.2), 255); + TransparencyColorExpression->GetColor() = FLinearColor::FromSRGBColor(TransparencyColor); IDatasmithMaterialExpressionScalar* GlowIntensityExpression = MaterialElement->AddMaterialExpression(); GlowIntensityExpression->GetScalar() = GlowIntensity; @@ -810,16 +799,16 @@ void FWireTranslatorImpl::AddAlLambertParameters(AlShader *Shader, TSharedRef MaterialElement) +void FWireTranslatorImpl::AddAlLightSourceParameters(AlShader* Shader, TSharedRef MaterialElement) { // Default values for a LightSource material - double Color[] = { 0.57, 0.58, 0.60 }; - double TransparencyColor[] = { 0.0, 0.0, 0.0 }; - double IncandescenceColor[] = { 0.0, 0.0, 0.0 }; + FColor Color(145, 148, 153); + FColor TransparencyColor(0, 0, 0); + FColor IncandescenceColor(0, 0, 0); double GlowIntensity = 0.0; AlList* List = Shader->fields(); - for (AlShadingFieldItem* Item = static_cast(List->first()); Item; Item = Item->nextField()) + for (AlShadingFieldItem* Item = static_cast(List->first()); Item; Item = Item->nextField()) { double Value = 0.0f; statusCode ErrorCode = Shader->parameter(Item->field(), Value); @@ -828,58 +817,23 @@ void FWireTranslatorImpl::AddAlLightSourceParameters(AlShader *Shader, TSharedRe continue; } - switch (Item->field()) - { - case AlShadingFields::kFLD_SHADING_COMMON_COLOR_R: - Color[0] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_COLOR_G: - Color[1] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_COLOR_B: - Color[2] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_INCANDESCENCE_R: - IncandescenceColor[0] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_INCANDESCENCE_G: - IncandescenceColor[1] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_INCANDESCENCE_B: - IncandescenceColor[2] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_TRANSPARENCY_R: - TransparencyColor[0] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_TRANSPARENCY_G: - TransparencyColor[1] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_TRANSPARENCY_B: - TransparencyColor[2] = Value; - break; - case AlShadingFields::kFLD_SHADING_COMMON_GLOW_INTENSITY: - GlowIntensity = Value; - break; - default: - continue; - } + GetCommonParameters(Item->field(), Value, Color, TransparencyColor, IncandescenceColor, GlowIntensity); } - float Opacity = 1.0f - (TransparencyColor[0] + TransparencyColor[1] + TransparencyColor[2]) / 3.0f; - bool bIsTransparent = !FMath::IsNearlyEqual(Opacity, 1.0f); + bool bIsTransparent = IsTransparent(TransparencyColor); // Construct parameter expressions IDatasmithMaterialExpressionColor* ColorExpression = MaterialElement->AddMaterialExpression(); ColorExpression->SetName(TEXT("Color")); - ColorExpression->GetColor() = FLinearColor(pow(Color[0] / 255.0, 2.2), pow(Color[1] / 255.0, 2.2), pow(Color[2] / 255.0, 2.2), 255); + ColorExpression->GetColor() = FLinearColor::FromSRGBColor(Color); IDatasmithMaterialExpressionColor* IncandescenceColorExpression = MaterialElement->AddMaterialExpression(); IncandescenceColorExpression->SetName(TEXT("IncandescenceColor")); - IncandescenceColorExpression->GetColor() = FLinearColor(pow(IncandescenceColor[0] / 255.0, 2.2), pow(IncandescenceColor[1] / 255.0, 2.2), pow(IncandescenceColor[2] / 255.0, 2.2), 255); + IncandescenceColorExpression->GetColor() = FLinearColor::FromSRGBColor(IncandescenceColor); IDatasmithMaterialExpressionColor* TransparencyColorExpression = MaterialElement->AddMaterialExpression(); TransparencyColorExpression->SetName(TEXT("TransparencyColor")); - TransparencyColorExpression->GetColor() = FLinearColor(pow(TransparencyColor[0] / 255.0, 2.2), pow(TransparencyColor[1] / 255.0, 2.2), pow(TransparencyColor[2] / 255.0, 2.2), 255); + TransparencyColorExpression->GetColor() = FLinearColor::FromSRGBColor(TransparencyColor); IDatasmithMaterialExpressionScalar* GlowIntensityExpression = MaterialElement->AddMaterialExpression(); GlowIntensityExpression->GetScalar() = GlowIntensity; @@ -971,13 +925,13 @@ void FWireTranslatorImpl::AddAlLightSourceParameters(AlShader *Shader, TSharedRe } -void FWireTranslatorImpl::AddAlPhongParameters(AlShader *Shader, TSharedRef MaterialElement) +void FWireTranslatorImpl::AddAlPhongParameters(AlShader* Shader, TSharedRef MaterialElement) { // Default values for a Phong material - double Color[] = { 0.57, 0.58, 0.60 }; - double TransparencyColor[] = { 0.0, 0.0, 0.0 }; - double IncandescenceColor[] = { 0.0, 0.0, 0.0 }; - double SpecularColor[] = { 0.15, 0.15, 0.15 } ; + FColor Color(145, 148, 153); + FColor TransparencyColor(0, 0, 0); + FColor IncandescenceColor(0, 0, 0); + FColor SpecularColor(38, 38, 38); double Diffuse = 1.0; double GlowIntensity = 0.0; double Gloss = 0.8; @@ -986,7 +940,7 @@ void FWireTranslatorImpl::AddAlPhongParameters(AlShader *Shader, TSharedReffields(); - for (AlShadingFieldItem* Item = static_cast(List->first()); Item; Item = Item->nextField()) + for (AlShadingFieldItem* Item = static_cast(List->first()); Item; Item = Item->nextField()) { double Value = 0.0f; statusCode ErrorCode = Shader->parameter(Item->field(), Value); @@ -995,6 +949,11 @@ void FWireTranslatorImpl::AddAlPhongParameters(AlShader *Shader, TSharedReffield(), Value, Color, TransparencyColor, IncandescenceColor, GlowIntensity)) + { + continue; + } + switch (Item->field()) { case AlShadingFields::kFLD_SHADING_PHONG_DIFFUSE: @@ -1004,13 +963,13 @@ void FWireTranslatorImpl::AddAlPhongParameters(AlShader *Shader, TSharedRefAddMaterialExpression(); @@ -1076,7 +996,7 @@ void FWireTranslatorImpl::AddAlPhongParameters(AlShader *Shader, TSharedRefAddMaterialExpression(); SpecularColorExpression->SetName(TEXT("SpecularColor")); - SpecularColorExpression->GetColor() = FLinearColor(pow(SpecularColor[0], 2.2), pow(SpecularColor[1], 2.2), pow(SpecularColor[2], 2.2), 1.0f); + SpecularColorExpression->GetColor() = FLinearColor::FromSRGBColor(SpecularColor); IDatasmithMaterialExpressionScalar* SpecularityExpression = MaterialElement->AddMaterialExpression(); SpecularityExpression->GetScalar() = Specularity * 0.3; @@ -1092,15 +1012,15 @@ void FWireTranslatorImpl::AddAlPhongParameters(AlShader *Shader, TSharedRefAddMaterialExpression(); ColorExpression->SetName(TEXT("Color")); - ColorExpression->GetColor() = FLinearColor(pow(Color[0] / 255.0, 2.2), pow(Color[1] / 255.0, 2.2), pow(Color[2] / 255.0, 2.2), 255); + ColorExpression->GetColor() = FLinearColor::FromSRGBColor(Color); IDatasmithMaterialExpressionColor* IncandescenceColorExpression = MaterialElement->AddMaterialExpression(); IncandescenceColorExpression->SetName(TEXT("IncandescenceColor")); - IncandescenceColorExpression->GetColor() = FLinearColor(pow(IncandescenceColor[0] / 255.0, 2.2), pow(IncandescenceColor[1] / 255.0, 2.2), pow(IncandescenceColor[2] / 255.0, 2.2), 255); + IncandescenceColorExpression->GetColor() = FLinearColor::FromSRGBColor(IncandescenceColor); IDatasmithMaterialExpressionColor* TransparencyColorExpression = MaterialElement->AddMaterialExpression(); TransparencyColorExpression->SetName(TEXT("TransparencyColor")); - TransparencyColorExpression->GetColor() = FLinearColor(pow(TransparencyColor[0] / 255.0, 2.2), pow(TransparencyColor[1] / 255.0, 2.2), pow(TransparencyColor[2] / 255.0, 2.2), 255); + TransparencyColorExpression->GetColor() = FLinearColor::FromSRGBColor(TransparencyColor); IDatasmithMaterialExpressionScalar* GlowIntensityExpression = MaterialElement->AddMaterialExpression(); GlowIntensityExpression->GetScalar() = GlowIntensity; @@ -1264,7 +1184,7 @@ void FWireTranslatorImpl::AddAlPhongParameters(AlShader *Shader, TSharedRefname(); @@ -1299,7 +1219,7 @@ bool FWireTranslatorImpl::GetShader() TSharedPtr< IDatasmithMaterialIDElement > MaterialIDElement = FDatasmithSceneFactory::CreateMaterialId(MaterialElement->GetName()); ShaderNameToUEMaterialId.Add(*ShaderName, MaterialIDElement); - AlShader *curShader = Shader; + AlShader* curShader = Shader; Shader = AlUniverse::nextShader(Shader); delete curShader; } @@ -1325,7 +1245,7 @@ void FWireTranslatorImpl::GetDagNodeMeta(AlDagNode& CurrentNode, TSharedPtr< IDa ActorElement->SetLayer(*LayerName); } - // TODO import other Meta + // TODO import other Meta } @@ -1337,9 +1257,9 @@ void FWireTranslatorImpl::GetDagNodeInfo(AlDagNode& CurrentNode, const FDagNodeI AlPersistentID* GroupNodeId = nullptr; CurrentNode.persistentID(GroupNodeId); - FString ThisGroupNodeID( GetPersistentIDString(GroupNodeId) ); + FString ThisGroupNodeID(GetPersistentIDString(GroupNodeId)); - // Limit length of UUID by combining hash of parent UUID and container's UUID if ParentUuid is not empty + // Limit length of UUID by combining hash of parent UUID and container's UUID if ParentUuid is not empty CurrentNodeInfo.UEuuid = GetUEUUIDFromAIPersistentID(ParentInfo.UEuuid, ThisGroupNodeID); } @@ -1349,7 +1269,7 @@ void FWireTranslatorImpl::GetDagNodeInfo(TSharedRef CurrentNode, const CurrentNode->Label = CurrentNodeInfo.Label; // Limit length of UUID by combining hash of parent UUID and container's UUID if ParentUuid is not empty - CurrentNodeInfo.UEuuid = GetUEUUIDFromAIPersistentID( ParentInfo.UEuuid, CurrentNode->GetUUID( ParentInfo.UEuuid ) ); + CurrentNodeInfo.UEuuid = GetUEUUIDFromAIPersistentID(ParentInfo.UEuuid, CurrentNode->GetUUID(ParentInfo.UEuuid)); } @@ -1363,7 +1283,7 @@ bool FWireTranslatorImpl::ProcessAlGroupNode(AlGroupNode& GroupNode, const FDagN ThisGroupNodeInfo.ActorElement->SetLabel(*ThisGroupNodeInfo.Label); GetDagNodeMeta(GroupNode, ThisGroupNodeInfo.ActorElement); - AlDagNode * ChildNode = GroupNode.childNode(); + AlDagNode* ChildNode = GroupNode.childNode(); if (AlIsValid(ChildNode) == TRUE) { RecurseDagForLeaves(ChildNode, ThisGroupNodeInfo); @@ -1390,13 +1310,13 @@ bool FWireTranslatorImpl::ProcessAlGroupNode(AlGroupNode& GroupNode, const FDagN TSharedPtr< IDatasmithMeshElement > FWireTranslatorImpl::FindOrAddMeshElement(TSharedRef Body, const FDagNodeInfo& NodeInfo) { - TSharedPtr< IDatasmithMeshElement >* MeshElementPtr = BodyToMeshElementMap.Find( NodeInfo.UEuuid ); + TSharedPtr< IDatasmithMeshElement >* MeshElementPtr = BodyToMeshElementMap.Find(NodeInfo.UEuuid); if (MeshElementPtr != nullptr) { return *MeshElementPtr; } - - TSharedPtr< IDatasmithMeshElement > MeshElement = FDatasmithSceneFactory::CreateMesh( *NodeInfo.UEuuid ); + + TSharedPtr< IDatasmithMeshElement > MeshElement = FDatasmithSceneFactory::CreateMesh(*NodeInfo.UEuuid); MeshElement->SetLabel(*NodeInfo.Label); MeshElement->SetLightmapSourceUV(-1); @@ -1411,7 +1331,7 @@ TSharedPtr< IDatasmithMeshElement > FWireTranslatorImpl::FindOrAddMeshElement(TS ShellUUIDToMeshElementMap.Add(FCString::Atoi(*NodeInfo.UEuuid), MeshElement); MeshElementToBodyMap.Add(MeshElement.Get(), Body); - BodyToMeshElementMap.Add( NodeInfo.UEuuid, MeshElement ); + BodyToMeshElementMap.Add(NodeInfo.UEuuid, MeshElement); return MeshElement; } @@ -1577,10 +1497,10 @@ bool FWireTranslatorImpl::ProcessBodyNode(TSharedRef Body, const FDagN return true; } -AlDagNode * GetNextNode(AlDagNode * DagNode) +AlDagNode* GetNextNode(AlDagNode* DagNode) { // Grab the next sibling before deleting the node. - AlDagNode * SiblingNode = DagNode->nextNode(); + AlDagNode* SiblingNode = DagNode->nextNode(); if (AlIsValid(SiblingNode)) { return SiblingNode; @@ -1591,7 +1511,7 @@ AlDagNode * GetNextNode(AlDagNode * DagNode) } } -bool IsHidden(AlDagNode * DagNode) +bool IsHidden(AlDagNode* DagNode) { /* AlObjectType objectType = DagNode->type(); @@ -1632,7 +1552,7 @@ uint32 getBodySetId(const char* ShaderName, const char* LayerName, bool bCadData uint32 getNumOfPatch(AlShell& Shell) { uint32 NbFace = 0; - AlTrimRegion *TrimRegion = Shell.firstTrimRegion(); + AlTrimRegion* TrimRegion = Shell.firstTrimRegion(); while (TrimRegion) { NbFace++; @@ -1708,8 +1628,8 @@ bool FWireTranslatorImpl::RecurseDagForLeaves(AlDagNode* FirstDagNode, const FDa // Push all leaf nodes into 'leaves' case kShellNodeType: { - AlShellNode *ShellNode = DagNode->asShellNodePtr(); - AlShell *Shell = ShellNode->shell(); + AlShellNode* ShellNode = DagNode->asShellNodePtr(); + AlShell* Shell = ShellNode->shell(); uint32 NbPatch = getNumOfPatch(*Shell); if (AlShader* Shader = Shell->firstShader()) @@ -1729,9 +1649,9 @@ bool FWireTranslatorImpl::RecurseDagForLeaves(AlDagNode* FirstDagNode, const FDa } case kSurfaceNodeType: { - AlSurfaceNode *SurfaceNode = DagNode->asSurfaceNodePtr(); - AlSurface *Surface = SurfaceNode->surface(); - if (AlShader * Shader = Surface->firstShader()) + AlSurfaceNode* SurfaceNode = DagNode->asSurfaceNodePtr(); + AlSurface* Surface = SurfaceNode->surface(); + if (AlShader* Shader = Surface->firstShader()) { ShaderName = Shader->name(); } @@ -1741,9 +1661,9 @@ bool FWireTranslatorImpl::RecurseDagForLeaves(AlDagNode* FirstDagNode, const FDa case kMeshNodeType: { - AlMeshNode *MeshNode = DagNode->asMeshNodePtr(); - AlMesh *Mesh = MeshNode->mesh(); - if (AlShader * Shader = Mesh->firstShader()) + AlMeshNode* MeshNode = DagNode->asMeshNodePtr(); + AlMesh* Mesh = MeshNode->mesh(); + if (AlShader* Shader = Mesh->firstShader()) { ShaderName = Shader->name(); } @@ -1754,7 +1674,7 @@ bool FWireTranslatorImpl::RecurseDagForLeaves(AlDagNode* FirstDagNode, const FDa // Traverse down through groups case kGroupNodeType: { - AlGroupNode * GroupNode = DagNode->asGroupNodePtr(); + AlGroupNode* GroupNode = DagNode->asGroupNodePtr(); if (AlIsValid(GroupNode)) { ProcessAlGroupNode(*GroupNode, ParentInfo); @@ -1806,9 +1726,9 @@ bool FWireTranslatorImpl::RecurseDagForLeavesNoMerge(AlDagNode* FirstDagNode, co // Push all leaf nodes into 'leaves' case kShellNodeType: { - AlShellNode *ShellNode = DagNode->asShellNodePtr(); - AlShell *Shell = ShellNode->shell(); - if (AlShader * Shader = Shell->firstShader()) + AlShellNode* ShellNode = DagNode->asShellNodePtr(); + AlShell* Shell = ShellNode->shell(); + if (AlShader* Shader = Shell->firstShader()) { ShaderName = Shader->name(); } @@ -1818,9 +1738,9 @@ bool FWireTranslatorImpl::RecurseDagForLeavesNoMerge(AlDagNode* FirstDagNode, co } case kSurfaceNodeType: { - AlSurfaceNode *SurfaceNode = DagNode->asSurfaceNodePtr(); - AlSurface *Surface = SurfaceNode->surface(); - if (AlShader * Shader = Surface->firstShader()) + AlSurfaceNode* SurfaceNode = DagNode->asSurfaceNodePtr(); + AlSurface* Surface = SurfaceNode->surface(); + if (AlShader* Shader = Surface->firstShader()) { ShaderName = Shader->name(); } @@ -1831,9 +1751,9 @@ bool FWireTranslatorImpl::RecurseDagForLeavesNoMerge(AlDagNode* FirstDagNode, co case kMeshNodeType: { - AlMeshNode *MeshNode = DagNode->asMeshNodePtr(); - AlMesh *Mesh = MeshNode->mesh(); - if (AlShader * Shader = Mesh->firstShader()) + AlMeshNode* MeshNode = DagNode->asMeshNodePtr(); + AlMesh* Mesh = MeshNode->mesh(); + if (AlShader* Shader = Mesh->firstShader()) { ShaderName = Shader->name(); } @@ -1845,7 +1765,7 @@ bool FWireTranslatorImpl::RecurseDagForLeavesNoMerge(AlDagNode* FirstDagNode, co // Traverse down through groups case kGroupNodeType: { - AlGroupNode * GroupNode = DagNode->asGroupNodePtr(); + AlGroupNode* GroupNode = DagNode->asGroupNodePtr(); if (AlIsValid(GroupNode)) { ProcessAlGroupNode(*GroupNode, ParentInfo); @@ -1957,7 +1877,7 @@ TOptional FWireTranslatorImpl::GetMeshOfShellNode(AlDagNode& D AlMatrix4x4 AlMatrix; DagNode.inverseGlobalTransformationMatrix(AlMatrix); // TODO: the best way, should be to don't have to apply inverse global transform to the generated mesh - AlDagNode *TesselatedNode = TesselateDagLeaf(&DagNode, ETesselatorType::Fast, TessellationOptions.ChordTolerance); + AlDagNode* TesselatedNode = TesselateDagLeaf(&DagNode, ETesselatorType::Fast, TessellationOptions.ChordTolerance); if (TesselatedNode != nullptr) { // Get the meshes from the dag nodes. Note that removing the mesh's DAG. @@ -1986,8 +1906,8 @@ TOptional FWireTranslatorImpl::GetMeshOfMeshBody(TSharedRefShellSet) { - AlMeshNode *MeshNode = DagNode->asMeshNodePtr(); - AlMesh *Mesh = MeshNode->mesh(); + AlMeshNode* MeshNode = DagNode->asMeshNodePtr(); + AlMesh* Mesh = MeshNode->mesh(); if (Mesh) { TransferAlMeshToMeshDescription(*Mesh, MeshDescription, MeshParameters, True, true); @@ -1999,7 +1919,7 @@ TOptional FWireTranslatorImpl::GetMeshOfMeshBody(TSharedRef FWireTranslatorImpl::GetMeshOfNodeMesh(AlMeshNode& MeshNode, TSharedRef MeshElement, CADLibrary::FMeshParameters& MeshParameters, AlMatrix4x4* AlMeshInvGlobalMatrix) { - AlMesh * Mesh = MeshNode.mesh(); + AlMesh* Mesh = MeshNode.mesh(); if (AlIsValid(Mesh)) { if (AlMeshInvGlobalMatrix != nullptr) @@ -2014,7 +1934,7 @@ TOptional FWireTranslatorImpl::GetMeshOfNodeMesh(AlMeshNode& M TOptional FWireTranslatorImpl::GetMeshDescription(TSharedRef MeshElement, CADLibrary::FMeshParameters& MeshParameters, TSharedRef Body) { - if(Body->ShellSet.Num() == 0 ) + if (Body->ShellSet.Num() == 0) { return TOptional< FMeshDescription >(); } @@ -2036,7 +1956,7 @@ TOptional FWireTranslatorImpl::GetMeshDescription(TSharedRefbCadData) + if (Body->bCadData) { return GetMeshOfShellBody(Body, MeshElement, MeshParameters); } @@ -2049,7 +1969,7 @@ TOptional FWireTranslatorImpl::GetMeshDescription(TSharedRef FWireTranslatorImpl::GetMeshDescription(TSharedRef MeshElement, CADLibrary::FMeshParameters& MeshParameters) { - AlDagNode ** DagNodeTemp = MeshElementToAlDagNodeMap.Find(&MeshElement.Get()); + AlDagNode** DagNodeTemp = MeshElementToAlDagNodeMap.Find(&MeshElement.Get()); if (DagNodeTemp == nullptr || *DagNodeTemp == nullptr) { TSharedPtr* BodyTemp = MeshElementToBodyMap.Find(&MeshElement.Get()); @@ -2088,18 +2008,18 @@ TOptional FWireTranslatorImpl::GetMeshDescription(TSharedRef(); @@ -2158,7 +2078,7 @@ bool FDatasmithWireTranslator::LoadScene(TSharedRef OutScene) const uint64 LibAlias2021Version = 7599824377020416; uint64 FileVersion = FPlatformMisc::GetFileVersion(TEXT("libalias_api.dll")); - if (FileVersion > LibAlias2020Version && FileVersion < LibAlias2021Version) + if (FileVersion > LibAlias2020Version&& FileVersion < LibAlias2021Version) { #if WITH_EDITOR // Display message and abort import @@ -2166,7 +2086,7 @@ bool FDatasmithWireTranslator::LoadScene(TSharedRef OutScene) TSharedPtr LogListing = MessageLogModule.GetLogListing("DatasmithWireTranslator"); - if(LogListing.IsValid()) + if (LogListing.IsValid()) { LogListing->SetLabel(LOCTEXT("MessageLogging", "Datasmith Wire Translator")); @@ -2236,7 +2156,7 @@ void FDatasmithWireTranslator::SetSceneImportOptions(TArraySetTessellationOptions( GetCommonTessellationOptions() ); + Translator->SetTessellationOptions(GetCommonTessellationOptions()); } #endif } diff --git a/Engine/Plugins/Experimental/ActorPalette/ActorPalette.uplugin b/Engine/Plugins/Experimental/ActorPalette/ActorPalette.uplugin new file mode 100644 index 000000000000..d61a523f8a88 --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/ActorPalette.uplugin @@ -0,0 +1,25 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Actor Palette", + "Description": "Allows creation of Actor Palettes based on existing levels to quickly select actors and drag them into the level editor", + "Category": "Other", + "CreatedBy": "Epic Games, Inc.", + "CreatedByURL": "http://epicgames.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": false, + "IsBetaVersion": false, + "EnabledByDefault": false, + "IsExperimentalVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "ActorPalette", + "Type": "Editor", + "LoadingPhase": "Default" + } + ] +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/ActorPalette.Build.cs b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/ActorPalette.Build.cs new file mode 100644 index 000000000000..694a659073cd --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/ActorPalette.Build.cs @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class ActorPalette : ModuleRules +{ + public ActorPalette(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Projects", + "InputCore", + "UnrealEd", + "ToolMenus", + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "EditorStyle", + "ContentBrowser", + "WorkspaceMenuStructure", + "DeveloperSettings" + } + ); + } +} diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPalette.cpp b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPalette.cpp new file mode 100644 index 000000000000..f5ac07f4a111 --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPalette.cpp @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ActorPalette.h" +#include "ActorPaletteViewport.h" +#include "ActorPaletteViewportClient.h" + +////////////////////////////////////////////////////////////////////// +// SActorPalette + +void SActorPalette::Construct(const FArguments& InArgs, int32 InTabIndex) +{ + TabIndex = InTabIndex; + + ActorPaletteViewportClient = MakeShareable(new FActorPaletteViewportClient(TabIndex)); + ActorPaletteViewport = SNew(SActorPaletteViewport, ActorPaletteViewportClient, TabIndex); + ActorPaletteViewportClient->SetOwnerWidget(ActorPaletteViewport); + + ChildSlot + [ + ActorPaletteViewport.ToSharedRef() + ]; +} diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPalette.h b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPalette.h new file mode 100644 index 000000000000..bc5562fcd08c --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPalette.h @@ -0,0 +1,26 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + +class SActorPaletteViewport; +class FActorPaletteViewportClient; + +////////////////////////////////////////////////////////////////////// + +class SActorPalette : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SActorPalette) {} + SLATE_END_ARGS() + +public: + void Construct(const FArguments& InArgs, int32 InTabIndex); + +private: + TSharedPtr ActorPaletteViewport; + TSharedPtr ActorPaletteViewportClient; + int32 TabIndex; +}; diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteCommands.cpp b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteCommands.cpp new file mode 100644 index 000000000000..c2c654867637 --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteCommands.cpp @@ -0,0 +1,13 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ActorPaletteCommands.h" + +#define LOCTEXT_NAMESPACE "ActorPalette" + +void FActorPaletteCommands::RegisterCommands() +{ + UI_COMMAND(ToggleGameView, "Game View", "Toggles game view. Game view shows the scene as it appears in game", EUserInterfaceActionType::ToggleButton, FInputChord(EKeys::G)); + UI_COMMAND(ResetCameraView, "Reset Camera", "Resets the camera view", EUserInterfaceActionType::Button, FInputChord()); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteModule.cpp b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteModule.cpp new file mode 100644 index 000000000000..2802fab91b98 --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteModule.cpp @@ -0,0 +1,130 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ActorPaletteModule.h" +#include "ActorPaletteStyle.h" +#include "ActorPaletteCommands.h" + +#include "Widgets/Docking/SDockTab.h" +#include "Framework/Commands/UICommandList.h" + +#include "WorkspaceMenuStructureModule.h" +#include "WorkspaceMenuStructure.h" + +#include "ActorPalette.h" + +// Core functionality/bugs: +//@TODO: Clear selection state (& invalidate) when drop finished or aborted +//@TODO: Support factorying using actual actor settings (everything but position / rotation, including scale, different material, etc...) +//@TODO: See if we can proper drag-drop behavior (right now the fake drag doesn't fizzle if the mouse is released, and in fact only renders correctly as a preview into the main frame if you release first) +//@TODO: Better workaround (or real fix) for the RenameForPIE hack + +// Important UX stuff: +//@TODO: Save/restore camera position for LRU/favorites +//@TODO: Try doing a timer or something to refresh the non-realtime viewport a few seconds after map load to compensate for texture streaming blurriness + +// Random ideas: +// - Bookmark support (using existing ones, not setting them IMO) +// - Better bookmarks (setting names for example) then showing them as preset buttons on the left side of the viewport +// - Click-drag to pan on LMB that misses meshes +// - Setting for whether or not 'game mode' is enabled by default / remember game mode setting +// - Auto-reload if map being viewed gets saved in the editor +// - Same but for links to other maps, or just create something visual I can double-click on the map itself (or teleport hyperlinks) +// - Support for materials (e.g., detect if it's a material demo kiosk and drag-drop the material instead of the mesh) +// - Store the source map..actor path in package metadata for placed instances and provide a key bind to focus it back (Shift+Ctrl+B maybe?) +// - Add keybinds to cycle between related objects in a set (using metadata on the placed instance linking it back to template map, along with set/chain metadata in the template map or via an associated collection) +// Should this nuke the existing actor and spawn a new one, only copying transform, or should it do something crazier like try to delta +// serialize against old template and apply diffs to new template (getting into CPFUO land...) +// - Keybind to randomize Z rotation for selected object (totally unrelated to actor palette, just might be a nice level editor feature?) +// - Picker-style shortcut to let it be used without keeping it up all the time +// - Multi-select (Ctrl+click) when in click but not drag mode? (unsure if I want to keep it working like it is, see note above in core functionality; could lean on grouping instead if stuff is meant to be placed together) + +// Might be bridging too far into foliage / placement tools land here: +// - Support for stamp mode (keep placing instances until you press Escape) +// - Support for optional random z rotation? + +#define LOCTEXT_NAMESPACE "ActorPalette" + +////////////////////////////////////////////////////////////////////// +// FActorPaletteModule + +void FActorPaletteModule::StartupModule() +{ + FActorPaletteStyle::Initialize(); + FActorPaletteStyle::ReloadTextures(); + + FActorPaletteCommands::Register(); + + PluginCommands = MakeShareable(new FUICommandList); + + // Register tab spawners for the actor palette tabs + const FSlateIcon ActorPaletteIcon(FActorPaletteStyle::GetStyleSetName(), "ActorPalette.TabIcon"); + const IWorkspaceMenuStructure& MenuStructure = WorkspaceMenu::GetMenuStructure(); + TSharedRef ActorPaletteGroup = MenuStructure.GetToolsCategory()->AddGroup( + LOCTEXT("WorkspaceMenu_ActorPaletteCategory", "Actor Palette"), + LOCTEXT("ActorPaletteMenuTooltipText", "Open an Actor Palette tab."), + ActorPaletteIcon, + true); + + for (int32 TabIndex = 0; TabIndex < UE_ARRAY_COUNT(ActorPaletteTabs); TabIndex++) + { + const FName TabID = FName(*FString::Printf(TEXT("ActorPaletteTab%d"), TabIndex + 1)); + ActorPaletteTabs[TabIndex].TabID = TabID; + + FGlobalTabmanager::Get()->RegisterNomadTabSpawner(TabID, FOnSpawnTab::CreateRaw(this, &FActorPaletteModule::OnSpawnPluginTab, TabIndex)) + .SetDisplayName(GetActorPaletteLabelWithIndex(TabIndex)) + .SetTooltipText(LOCTEXT("ActorPaletteMenuTooltipText", "Open an Actor Palette tab.")) + .SetGroup(ActorPaletteGroup) + .SetIcon(ActorPaletteIcon); + } +} + +void FActorPaletteModule::ShutdownModule() +{ + FActorPaletteStyle::Shutdown(); + + FActorPaletteCommands::Unregister(); + + for (int32 TabIndex = 0; TabIndex < UE_ARRAY_COUNT(ActorPaletteTabs); ++TabIndex) + { + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ActorPaletteTabs[TabIndex].TabID); + } +} + +TSharedRef FActorPaletteModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs, int32 TabIndex) +{ + TSharedRef NewPalette = SNew(SActorPalette, TabIndex); + + check(!ActorPaletteTabs[TabIndex].OpenInstance.IsValid()); + ActorPaletteTabs[TabIndex].OpenInstance = NewPalette; + + TAttribute TabLabel = TAttribute::Create(TAttribute::FGetter::CreateRaw(this, &FActorPaletteModule::GetActorPaletteTabLabel, TabIndex)); + + TSharedRef ResultTab = SNew(SDockTab) + .TabRole(ETabRole::NomadTab) + .Label(TabLabel) + [ + NewPalette + ]; + + return ResultTab; +} + +FText FActorPaletteModule::GetActorPaletteLabelWithIndex(int32 TabIndex) +{ + return FText::Format(LOCTEXT("ActorPaletteTabNameWithIndex", "Actor Palette {0}"), FText::AsNumber(TabIndex + 1)); +} + +FText FActorPaletteModule::GetActorPaletteTabLabel(int32 TabIndex) const +{ + int32 NumOpenPalettes = 0; + for (const FActorPaletteTabInfo& TabInfo : ActorPaletteTabs) + { + NumOpenPalettes += TabInfo.OpenInstance.IsValid() ? 1 : 0; + } + + return (NumOpenPalettes > 1) ? GetActorPaletteLabelWithIndex(TabIndex) : LOCTEXT("ActorPaletteTabNoIndex", "Actor Palette"); +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FActorPaletteModule, ActorPalette) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteSettings.cpp b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteSettings.cpp new file mode 100644 index 000000000000..85b0e13ff009 --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteSettings.cpp @@ -0,0 +1,111 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ActorPaletteSettings.h" +#include "AssetRegistryModule.h" + +FAssetData FActorPaletteMapEntry::GetAsAssetData() const +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::Get().LoadModuleChecked(TEXT("AssetRegistry")); + return AssetRegistryModule.Get().GetAssetByObjectPath(*MapPath); +} + +UActorPaletteSettings::UActorPaletteSettings() +{ + CategoryName = TEXT("Plugins"); +} + +#if WITH_EDITOR +void UActorPaletteSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + TrimRecentList(); +} +#endif + +int32 UActorPaletteSettings::FindMapEntry(const FString& DesiredName) const +{ + return SettingsPerLevel.IndexOfByPredicate([&](const FActorPaletteMapEntry& Entry) { return Entry.MapPath == DesiredName; }); +} + +int32 UActorPaletteSettings::FindLastLevelForTab(int32 TabIndex) const +{ + return MostRecentLevelByTab.IsValidIndex(TabIndex) ? FindMapEntry(MostRecentLevelByTab[TabIndex]) : INDEX_NONE; +} + +void UActorPaletteSettings::MarkAsRecentlyUsed(const FAssetData& MapAsset, int32 TabIndex) +{ + if (MapAsset.IsValid()) + { + const FString MapAssetPath = MapAsset.ObjectPath.ToString(); + + // Remember as the most recent for this tab + if (!MostRecentLevelByTab.IsValidIndex(TabIndex)) + { + MostRecentLevelByTab.AddDefaulted(TabIndex + 1 - MostRecentLevelByTab.Num()); + } + MostRecentLevelByTab[TabIndex] = MapAssetPath; + + // Remember as a recent entry across all tabs, bubbling to the top if is already present + const int32 RecentIndex = RecentlyUsedList.IndexOfByKey(MapAssetPath); + if (RecentIndex != INDEX_NONE) + { + RecentlyUsedList.Swap(0, RecentIndex); + } + else + { + RecentlyUsedList.Insert(MapAssetPath, 0); + } + + // Make sure it's in our list of settings per level + const int32 SettingsIndex = FindMapEntry(MapAssetPath); + if (SettingsIndex == INDEX_NONE) + { + // New entry + FActorPaletteMapEntry& NewEntry = SettingsPerLevel[SettingsPerLevel.AddDefaulted()]; + NewEntry.MapPath = MapAsset.ObjectPath.ToString(); + } + } + + TrimRecentList(); + +#if WITH_EDITOR + PostEditChange(); + SaveConfig(); +#endif +} + +void UActorPaletteSettings::ToggleFavorite(const FAssetData& MapAsset) +{ + if (FavoritesList.Contains(MapAsset.ObjectPath.ToString())) + { + FavoritesList.Remove(MapAsset.ObjectPath.ToString()); + } + else + { + FavoritesList.Add(MapAsset.ObjectPath.ToString()); + } + +#if WITH_EDITOR + PostEditChange(); + SaveConfig(); +#endif +} + +void UActorPaletteSettings::TrimRecentList() +{ + // Trim the end of the recent list + const int32 EffectiveLimit = FMath::Max(NumRecentLevelsToKeep, 0); + while (RecentlyUsedList.Num() > EffectiveLimit) + { + RecentlyUsedList.RemoveAt(RecentlyUsedList.Num() - 1); + } + + // Determine what per-map settings to keep alive + TSet InterestingLevels; + InterestingLevels.Append(RecentlyUsedList); + InterestingLevels.Append(FavoritesList); + InterestingLevels.Append(MostRecentLevelByTab); + + // And remove settings that are no longer in any list + SettingsPerLevel.RemoveAllSwap([&](const FActorPaletteMapEntry& Entry) { return !InterestingLevels.Contains(Entry.MapPath); }); +} diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteSettings.h b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteSettings.h new file mode 100644 index 000000000000..5787f963d853 --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteSettings.h @@ -0,0 +1,86 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "Engine/Blueprint.h" +#include "Engine/DeveloperSettings.h" +#include "AssetData.h" +#include "ActorPaletteSettings.generated.h" + +// Information about a single recent/favorite map +USTRUCT() +struct FActorPaletteMapEntry +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category=ActorPalette) + FString MapPath; + + //@TODO: Store viewpoint + + // Was game mode enabled? +// UPROPERTY() +// uint8 bGameMode : 1; + + FAssetData GetAsAssetData() const; +}; + +// Settings/preferences for Actor Palettes +UCLASS(config=EditorPerProjectUserSettings) +class UActorPaletteSettings : public UDeveloperSettings +{ + GENERATED_BODY() + +public: + UActorPaletteSettings(); + + // Data model note: + // - Every tab remembers the last map they had open + // - There is a shared recent maps list (opening a map in any tab will bubble it to the top of the recent list) + // - There is a shared favorites list + // - While a tab is open it has unique viewport settings, but only the most recent user interaction updates the data in the recent/favorites list + //@TODO: Nothing purges items out of the recent/favorites list if the map is deleted, though recent list is bounded in size + +public: + // Remembered settings for any recent/current/favorite actor palette maps + UPROPERTY(config) + TArray SettingsPerLevel; + + // List of levels that were recently open in any Actor Palette tab + UPROPERTY(config) + TArray RecentlyUsedList; + + // List of levels that were last open in each Actor Palette tab (indexed by tab index) + UPROPERTY(config) + TArray MostRecentLevelByTab; + + // List of levels that were marked as favorite actor palettes + UPROPERTY(config) + TArray FavoritesList; + + // Should the 'game mode' show flag be set by default for newly opened actor palettes? + //UPROPERTY(config, EditAnywhere, Category=ActorPalette) + //bool bEnableGameModeByDefault = true; + + // How many recent levels will be remembered? + UPROPERTY(config, EditAnywhere, Category=ActorPalette, meta=(ClampMin=0, ClampMax=25)) + int32 NumRecentLevelsToKeep = 10; + +#if WITH_EDITOR + //~UObject interface + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; + //~End of UObject interface +#endif + + int32 FindMapEntry(const FString& MapName) const; + int32 FindLastLevelForTab(int32 TabIndex) const; + +public: + void MarkAsRecentlyUsed(const FAssetData& MapAsset, int32 TabIndex); + void ToggleFavorite(const FAssetData& MapAsset); + void TrimRecentList(); +}; + diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteStyle.cpp b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteStyle.cpp new file mode 100644 index 000000000000..84eff3d18dff --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteStyle.cpp @@ -0,0 +1,84 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ActorPaletteStyle.h" +#include "Styling/SlateStyleRegistry.h" +#include "Framework/Application/SlateApplication.h" +#include "Slate/SlateGameResources.h" +#include "Interfaces/IPluginManager.h" +#include "EditorStyleSet.h" + +TSharedPtr< FSlateStyleSet > FActorPaletteStyle::StyleInstance = NULL; + +void FActorPaletteStyle::Initialize() +{ + if (!StyleInstance.IsValid()) + { + StyleInstance = Create(); + FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); + } +} + +void FActorPaletteStyle::Shutdown() +{ + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); + ensure(StyleInstance.IsUnique()); + StyleInstance.Reset(); +} + +FName FActorPaletteStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("ActorPaletteStyle")); + return StyleSetName; +} + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) + +const FVector2D Icon16x16(16.0f, 16.0f); +const FVector2D Icon20x20(20.0f, 20.0f); +const FVector2D Icon40x40(40.0f, 40.0f); + +TSharedRef< FSlateStyleSet > FActorPaletteStyle::Create() +{ + TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("ActorPaletteStyle")); + Style->SetContentRoot(IPluginManager::Get().FindPlugin("ActorPalette")->GetBaseDir() / TEXT("Resources")); + + Style->Set("ActorPalette.OpenPluginWindow", new IMAGE_BRUSH(TEXT("ButtonIcon_40x"), Icon40x40)); + + const FTextBlockStyle& NormalText = FEditorStyle::Get().GetWidgetStyle("NormalText"); + + Style->Set("ActorPalette.ViewportTitleTextStyle", FTextBlockStyle(NormalText) + .SetFont(FCoreStyle::GetDefaultFontStyle("Regular", 18)) + .SetColorAndOpacity(FLinearColor(1.0, 1.0f, 1.0f, 0.5f)) + ); + + Style->Set("ActorPalette.Palette", new IMAGE_BRUSH(TEXT("Palette_40x"), Icon40x40)); + Style->Set("ActorPalette.Palette.Small", new IMAGE_BRUSH(TEXT("Palette_40x"), Icon20x20)); + Style->Set("ActorPalette.TabIcon", new IMAGE_BRUSH(TEXT("Palette_16x"), Icon16x16)); + + Style->Set("ActorPalette.ViewportTitleBackground", new BOX_BRUSH("GraphTitleBackground", FMargin(0))); + + return Style; +} + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef OTF_FONT + +void FActorPaletteStyle::ReloadTextures() +{ + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); + } +} + +const ISlateStyle& FActorPaletteStyle::Get() +{ + return *StyleInstance; +} diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewport.cpp b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewport.cpp new file mode 100644 index 000000000000..7e6ebfd3755a --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewport.cpp @@ -0,0 +1,502 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ActorPaletteViewport.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/SViewport.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SScaleBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Images/SImage.h" +#include "SEditorViewportToolBarMenu.h" +#include "EngineUtils.h" +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" + +#include "ActorPaletteStyle.h" +#include "ActorPaletteCommands.h" +#include "ActorPaletteViewportClient.h" +#include "ActorPaletteSettings.h" + +#define LOCTEXT_NAMESPACE "ActorPalette" + +////////////////////////////////////////////////////////////////////////// +// SActorPaletteFavoriteEntry + +class SActorPaletteFavoriteEntry : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SActorPaletteFavoriteEntry) {} + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs, const FAssetData& InAssetData, TSharedPtr TypedViewportClient) + { + AssetData = InAssetData; + VPC = TypedViewportClient; + + ChildSlot + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ForegroundColor(FSlateColor::UseForeground()) + .OnClicked_Lambda([this]() + { + VPC->OpenWorldAsPalette(AssetData); + FSlateApplication::Get().DismissAllMenus(); + return FReply::Handled(); + }) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .OnClicked_Lambda([this]() + { + GetMutableDefault()->ToggleFavorite(AssetData); + FSlateApplication::Get().DismissAllMenus(); + return FReply::Handled(); + }) + .ToolTipText(LOCTEXT("ToggleFavoriteTooltip", "Mark this level as a favorite or remove it from the favorites list")) + .ContentPadding(0) + [ + SNew(SImage) + .Image_Lambda([this]() + { + return FEditorStyle::GetBrush(GetDefault()->FavoritesList.Contains(AssetData.ObjectPath.ToString()) ? + TEXT("PropertyWindow.Favorites_Enabled") : + TEXT("PropertyWindow.Favorites_Disabled")); + }) + ] + ] + +SHorizontalBox::Slot() + .Padding(6.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "Menu.Label") + .Text(FText::Format(LOCTEXT("OpenFavoriteLevel_Label", "{0}"), FText::FromName(AssetData.AssetName))) + .ToolTipText(FText::Format(LOCTEXT("OpenFavoriteLevel_Tooltip", "Use {0} as an Actor Palette"), FText::FromName(AssetData.PackageName))) + ] + ] + ]; + } + + FAssetData AssetData; + TSharedPtr VPC; +}; + +////////////////////////////////////////////////////////////////////////// +// SActorPaletteViewportToolbar + +// In-viewport toolbar widget used in the actor palette +class SActorPaletteViewportToolbar : public SCommonEditorViewportToolbarBase +{ +public: + SLATE_BEGIN_ARGS(SActorPaletteViewportToolbar) {} + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs, TSharedPtr InInfoProvider, FOnGetContent&& InSelectMapMenu); + + // SCommonEditorViewportToolbarBase interface + virtual TSharedRef GenerateShowMenu() const override; + virtual void ExtendLeftAlignedToolbarSlots(TSharedPtr MainBoxPtr, TSharedPtr ParentToolBarPtr) const override; + // End of SCommonEditorViewportToolbarBase + + SActorPaletteViewport& GetOwnerViewport() const; + FText GetMapMenuLabel() const; + TSharedRef GenerateMapMenu() const; + + FOnGetContent SelectMapMenuCallback; +}; + +SActorPaletteViewport& SActorPaletteViewportToolbar::GetOwnerViewport() const +{ + return StaticCastSharedRef(GetInfoProvider().GetViewportWidget()).Get(); +} + +void SActorPaletteViewportToolbar::Construct(const FArguments& InArgs, TSharedPtr InInfoProvider, FOnGetContent&& InSelectMapMenu) +{ + SelectMapMenuCallback = MoveTemp(InSelectMapMenu); + SCommonEditorViewportToolbarBase::Construct(SCommonEditorViewportToolbarBase::FArguments(), InInfoProvider); +} + +TSharedRef SActorPaletteViewportToolbar::GenerateShowMenu() const +{ + GetInfoProvider().OnFloatingButtonClicked(); + + TSharedRef ViewportRef = GetInfoProvider().GetViewportWidget(); + + const bool bInShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder ShowMenuBuilder(bInShouldCloseWindowAfterMenuSelection, ViewportRef->GetCommandList()); + { + ShowMenuBuilder.AddMenuEntry(FActorPaletteCommands::Get().ToggleGameView); + ShowMenuBuilder.AddMenuEntry(FActorPaletteCommands::Get().ResetCameraView); + } + + return ShowMenuBuilder.MakeWidget(); +} + +void SActorPaletteViewportToolbar::ExtendLeftAlignedToolbarSlots(TSharedPtr MainBoxPtr, TSharedPtr ParentToolBarPtr) const +{ + const FMargin ToolbarSlotPadding(2.0f, 2.0f); + + MainBoxPtr->AddSlot() + .AutoWidth() + .Padding(ToolbarSlotPadding) + [ + SNew(SEditorViewportToolbarMenu) + .Label(LOCTEXT("MapMenu_Label", "Choose Level")) + .OnGetMenuContent(SelectMapMenuCallback) + .Cursor(EMouseCursor::Default) + .ParentToolBar(ParentToolBarPtr) + ]; +} + +////////////////////////////////////////////////////////////////////////// +// SActorPaletteViewport + +SActorPaletteViewport::~SActorPaletteViewport() +{ + TypedViewportClient.Reset(); +} + +void SActorPaletteViewport::Construct(const FArguments& InArgs, TSharedPtr InViewportClient, int32 InTabIndex) +{ + TypedViewportClient = InViewportClient; + TabIndex = InTabIndex; + + SEditorViewport::Construct(SEditorViewport::FArguments()); + + TSharedRef MyOverlay = SNew(SOverlay) + +SOverlay::Slot() + [ + ChildSlot.GetWidget() + ]; + + // Show quick buttons to open / choose whenever there is no map open + MyOverlay->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SHorizontalBox) + .Visibility_Lambda([this]() { return TypedViewportClient->GetCurrentWorldAssetData().IsValid() ? EVisibility::Collapsed : EVisibility::Visible; }) + + +SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 32.0f, 0.0f) + .VAlign(VAlign_Fill) + [ + SNew(SButton) + .Text(LOCTEXT("QuickButtonLabel_ReloadLastMap", "Open Last Level")) + .VAlign(VAlign_Center) + .Visibility_Lambda([=]() + { + return (GetDefault()->FindLastLevelForTab(TabIndex) != INDEX_NONE) ? EVisibility::Visible : EVisibility::Collapsed; + }) + .ToolTipText_Lambda([=]() + { + const UActorPaletteSettings* Settings = GetDefault(); + const int32 LastLevelForTabIndex = Settings->FindLastLevelForTab(TabIndex); + return (LastLevelForTabIndex != INDEX_NONE) ? + FText::Format(LOCTEXT("QuickButtonTooltip_ReloadLastMap", "Use {0} as an Actor Palette"), FText::FromName(Settings->SettingsPerLevel[LastLevelForTabIndex].GetAsAssetData().PackageName)) : + FText::GetEmpty(); + }) + .OnClicked_Lambda([=]() + { + const UActorPaletteSettings* Settings = GetDefault(); + int32 LastLevelIndex = Settings->FindLastLevelForTab(TabIndex); + if (LastLevelIndex != INDEX_NONE) + { + TypedViewportClient->OpenWorldAsPalette(Settings->SettingsPerLevel[LastLevelIndex].GetAsAssetData()); + } + return FReply::Handled(); + }) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Fill) + [ + SNew(SComboButton) + .OnGetMenuContent(this, &SActorPaletteViewport::GenerateMapMenu) + .ButtonContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("QuickButtonLabel_ChooseMap", "Choose Level")) + .ToolTipText(LOCTEXT("QuickButtonTooltip_ChooseMap", "Choose a Level to use as an Actor Palette")) + ] + ] + ]; + + // Show the name of the currently open map + MyOverlay->AddSlot() + .VAlign(VAlign_Bottom) + [ + SNew(SBorder) + .BorderImage(FActorPaletteStyle::Get().GetBrush("ActorPalette.ViewportTitleBackground")) + .HAlign(HAlign_Fill) + .Visibility(EVisibility::HitTestInvisible) + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SScaleBox) + .HAlign(HAlign_Center) + .StretchDirection(EStretchDirection::DownOnly) + .Stretch(EStretch::ScaleToFit) + [ + SNew(STextBlock) + .TextStyle(FActorPaletteStyle::Get(), "ActorPalette.ViewportTitleTextStyle") + .Text(this, &SActorPaletteViewport::GetTitleText) + ] + ] + ] + ]; + + this->ChildSlot + [ + MyOverlay + ]; +} + +TSharedPtr SActorPaletteViewport::MakeViewportToolbar() +{ + return SNew(SActorPaletteViewportToolbar, SharedThis(this), FOnGetContent::CreateSP(this, &SActorPaletteViewport::GenerateMapMenu)); +} + +TSharedRef SActorPaletteViewport::MakeEditorViewportClient() +{ + return TypedViewportClient.ToSharedRef(); +} + +void SActorPaletteViewport::BindCommands() +{ + SEditorViewport::BindCommands(); + + FActorPaletteCommands::Register(); + const FActorPaletteCommands& Commands = FActorPaletteCommands::Get(); + + CommandList->MapAction( + Commands.ToggleGameView, + FExecuteAction::CreateLambda([=]() { TypedViewportClient->SetGameView(!TypedViewportClient->IsInGameView()); }), + FCanExecuteAction(), + FIsActionChecked::CreateLambda([=](){ return TypedViewportClient->IsInGameView(); })); + + CommandList->MapAction( + Commands.ResetCameraView, + FExecuteAction::CreateLambda([=]() { TypedViewportClient->ResetCameraView(); })); +} + +void SActorPaletteViewport::OnFocusViewportToSelection() +{ +//@TODO: +// TypedViewportClient->RequestFocusOnSelection(/*bInstant=*/ false); +} + +TSharedRef SActorPaletteViewport::GetViewportWidget() +{ + return SharedThis(this); +} + +TSharedPtr SActorPaletteViewport::GetExtenders() const +{ + TSharedPtr Result(MakeShareable(new FExtender)); + return Result; +} + +void SActorPaletteViewport::OnFloatingButtonClicked() +{ +} + +FText SActorPaletteViewport::GetTitleText() const +{ + const FName CurrentMapName = TypedViewportClient->GetCurrentWorldAssetData().PackageName; + if (CurrentMapName == NAME_None) + { + return LOCTEXT("ActorPaletteViewportTitle_NoMap", "Choose a level to use as a palette"); + } + else + { + return FText::Format(LOCTEXT("ActorPaletteViewportTitle_MapName", "{0}"), FText::FromName(CurrentMapName)); + } +} + +TSharedRef SActorPaletteViewport::GenerateMapMenu() const +{ + const FActorPaletteCommands& Actions = FActorPaletteCommands::Get(); + UActorPaletteSettings* Settings = GetMutableDefault(); + + TSharedPtr MenuExtender = GetExtenders(); + + const bool bInShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder InMenuBuilder(bInShouldCloseWindowAfterMenuSelection, CommandList, MenuExtender); + + InMenuBuilder.PushCommandList(CommandList.ToSharedRef()); + if (MenuExtender.IsValid()) + { + InMenuBuilder.PushExtender(MenuExtender.ToSharedRef()); + } + + // Add an entry to chose an arbitrary map via an asset picker + { + InMenuBuilder.AddSubMenu( + LOCTEXT("OpenLevelFromPicker_Label", "Open Level..."), + LOCTEXT("OpenLevelFromPicker_Tooltip", "Select an existing level asset to use as an Actor Palette"), + FNewMenuDelegate::CreateLambda([=](FMenuBuilder& SubMenuBuilder) + { + FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); + + // Configure filter for asset picker + FAssetPickerConfig Config; + Config.Filter.ClassNames.Add(UWorld::StaticClass()->GetFName()); + Config.InitialAssetViewType = EAssetViewType::List; + Config.OnAssetSelected = FOnAssetSelected::CreateLambda([=](const FAssetData& AssetData) + { + TypedViewportClient->OpenWorldAsPalette(AssetData); + FSlateApplication::Get().DismissAllMenus(); + }); + Config.bAllowDragging = false; + Config.bAllowNullSelection = true; + Config.bFocusSearchBoxWhenOpened = true; + Config.InitialAssetSelection = TypedViewportClient->GetCurrentWorldAssetData(); + + TSharedRef ChooseMapWidget = + SNew(SBox) + .WidthOverride(300.f) + .HeightOverride(300.f) + [ + ContentBrowserModule.Get().CreateAssetPicker(Config) + ]; + + SubMenuBuilder.BeginSection("Browse", LOCTEXT("BrowseHeader", "Browse")); + SubMenuBuilder.AddWidget(ChooseMapWidget, FText::GetEmpty()); + SubMenuBuilder.EndSection(); + }) + ); + } + + // Add an entry for the first few selected maps from the content browser (ala a 'Use' arrow button) + { + TArray SelectedAssets; + GEditor->GetContentBrowserSelections(/*out*/ SelectedAssets); + + int32 NumLeftAllowedFromContentBrowser = 4; + for (FAssetData& Asset : SelectedAssets) + { + if (Asset.AssetClass == UWorld::StaticClass()->GetFName()) + { + FUIAction Action; + Action.ExecuteAction.BindLambda([=]() + { + TypedViewportClient->OpenWorldAsPalette(Asset); + }); + + InMenuBuilder.AddMenuEntry( + FText::Format(LOCTEXT("OpenSelectedLevelFromCB_Label", "Open {0} (from Content Browser)"), FText::FromName(Asset.AssetName)), + FText::Format(LOCTEXT("OpenSelectedLevelFromCB_Tooltip", "Use {0} as an Actor Palette"), FText::FromName(Asset.PackageName)), + FSlateIcon(), + Action); + + --NumLeftAllowedFromContentBrowser; + if (NumLeftAllowedFromContentBrowser == 0) + { + break; + } + } + } + } + + // Add an entry to chose a recent map + { + InMenuBuilder.AddSubMenu( + LOCTEXT("OpenLevelFromRecentList_Label", "Recent Levels"), + LOCTEXT("OpenLevelFromRecentList_Tooltip", "Select a level recently used as an Actor Palette"), + FNewMenuDelegate::CreateLambda([=](FMenuBuilder& SubMenuBuilder) + { + bool bAddedAnyRecent = false; + for (const FString& RecentEntry : Settings->RecentlyUsedList) + { + const int32 SettingsIndex = Settings->FindMapEntry(RecentEntry); + if (ensure(SettingsIndex != INDEX_NONE)) + { + FAssetData Asset = Settings->SettingsPerLevel[SettingsIndex].GetAsAssetData(); + bAddedAnyRecent = true; + + FUIAction Action; + Action.ExecuteAction.BindLambda([=]() + { + TypedViewportClient->OpenWorldAsPalette(Asset); + }); + + SubMenuBuilder.AddMenuEntry( + FText::Format(LOCTEXT("OpenRecentLevel_Label", "{0}"), FText::FromName(Asset.AssetName)), + FText::Format(LOCTEXT("OpenRecentLevel_Tooltip", "Use {0} as an Actor Palette"), FText::FromName(Asset.PackageName)), + FSlateIcon(), + Action); + } + } + + if (!bAddedAnyRecent) + { + const FText NoRecents = LOCTEXT("NoRecents_Label", "There are no recent levels"); + SubMenuBuilder.AddMenuEntry(NoRecents, NoRecents, FSlateIcon(), FUIAction()); + } + }) + ); + } + + // Add an entry to chose a favorite map + { + InMenuBuilder.AddSubMenu( + LOCTEXT("OpenLevelFromFavoriteList_Label", "Favorite Levels"), + LOCTEXT("OpenLevelFromFavoriteList_Tooltip", "Select a favorite level as an Actor Palette"), + FNewMenuDelegate::CreateLambda([=](FMenuBuilder& SubMenuBuilder) + { + if (TypedViewportClient->GetCurrentWorldAssetData().IsValid()) + { + SubMenuBuilder.BeginSection(NAME_None, LOCTEXT("FavoritesMenu_CurrentMap", "Current Level")); + SubMenuBuilder.AddWidget(SNew(SActorPaletteFavoriteEntry, TypedViewportClient->GetCurrentWorldAssetData(), TypedViewportClient), FText::GetEmpty()); + SubMenuBuilder.EndSection(); + } + + SubMenuBuilder.BeginSection(NAME_None, LOCTEXT("FavoritesMenu_FavoritesList", "Favorites")); + + bool bAddedAnyFavorites = false; + for (const FString& FavoriteEntry : Settings->FavoritesList) + { + const int32 SettingsIndex = Settings->FindMapEntry(FavoriteEntry); + if (ensure(SettingsIndex != INDEX_NONE)) + { + FAssetData Asset = Settings->SettingsPerLevel[SettingsIndex].GetAsAssetData(); + bAddedAnyFavorites = true; + + SubMenuBuilder.AddWidget(SNew(SActorPaletteFavoriteEntry, Asset, TypedViewportClient), FText::GetEmpty()); + } + } + + if (!bAddedAnyFavorites) + { + const FText NoFavorites = LOCTEXT("NoFavorites_Label", "There are no favorite levels"); + SubMenuBuilder.AddMenuEntry(NoFavorites, NoFavorites, FSlateIcon(), FUIAction()); + } + + SubMenuBuilder.EndSection(); + }) + ); + } + + InMenuBuilder.PopCommandList(); + if (MenuExtender.IsValid()) + { + InMenuBuilder.PopExtender(); + } + + return InMenuBuilder.MakeWidget(); +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewport.h b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewport.h new file mode 100644 index 000000000000..61d88a1c99c0 --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewport.h @@ -0,0 +1,50 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "EditorViewportClient.h" +#include "SEditorViewport.h" +#include "Editor/UnrealEd/Public/SCommonEditorViewportToolbarBase.h" + +class FActorPaletteViewportClient; +class SActorPaletteViewportToolbar; + +////////////////////////////////////////////////////////////////////////// +// SActorPaletteViewport + +class SActorPaletteViewport : public SEditorViewport, public ICommonEditorViewportToolbarInfoProvider +{ +public: + SLATE_BEGIN_ARGS(SActorPaletteViewport) {} + SLATE_END_ARGS() + + ~SActorPaletteViewport(); + + void Construct(const FArguments& InArgs, TSharedPtr InViewportClient, int32 InTabIndex); +// virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + // SEditorViewport interface + virtual TSharedPtr MakeViewportToolbar() override; + virtual TSharedRef MakeEditorViewportClient() override; + virtual void BindCommands() override; + virtual void OnFocusViewportToSelection() override; + // End of SEditorViewport interface + + // ICommonEditorViewportToolbarInfoProvider interface + virtual TSharedRef GetViewportWidget() override; + virtual TSharedPtr GetExtenders() const override; + virtual void OnFloatingButtonClicked() override; + // End of ICommonEditorViewportToolbarInfoProvider interface + +protected: + FText GetTitleText() const; + TSharedRef GenerateMapMenu() const; + +private: + TSharedPtr TypedViewportClient; + int32 TabIndex; + + friend SActorPaletteViewportToolbar; +}; diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewportClient.cpp b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewportClient.cpp new file mode 100644 index 000000000000..74503829cdbe --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewportClient.cpp @@ -0,0 +1,204 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ActorPaletteViewportClient.h" +#include "Materials/MaterialInterface.h" +#include "UObject/UObjectHash.h" +#include "UObject/UObjectIterator.h" +#include "CanvasItem.h" +#include "CanvasTypes.h" +#include "AssetEditorModeManager.h" +#include "ScopedTransaction.h" +#include "EngineUtils.h" +#include "Engine/LevelStreamingDynamic.h" +#include "Engine/Selection.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "DragAndDrop/AssetDragDropOp.h" +#include "MouseDeltaTracker.h" +#include "ActorPaletteSettings.h" +#include "Misc/ScopedSlowTask.h" +#include "Widgets/SWindow.h" +#include "SEditorViewport.h" + +#define LOCTEXT_NAMESPACE "ActorPalette" + +////////////////////////////////////////////////////////////////////////// +// FActorPaletteViewportClient + +FActorPaletteViewportClient::FActorPaletteViewportClient(int32 InTabIndex) + : FEditorViewportClient(new FAssetEditorModeManager(), nullptr, nullptr) + , TabIndex(InTabIndex) +{ + SetViewModes(VMI_Lit, VMI_Lit); + + // Get the correct general direction of the perspective mode; the distance doesn't matter much as we've queued up a deferred zoom that will calculate a much better distance + //@TODO: Save/load camera views into settings... (SetInitialViewTransform + SetViewportType) + + MyBGColor = FLinearColor::Black; + + SetRealtime(false); + + PreviewScene = &OwnedPreviewScene; + + EngineShowFlags.Selection = true; + EngineShowFlags.SelectionOutline = true; + EngineShowFlags.Grid = false; +} + +bool FActorPaletteViewportClient::InputKey(FViewport* InViewport, int32 ControllerId, FKey Key, EInputEvent Event, float AmountDepressed, bool bGamepad) +{ + if (Key == EKeys::LeftMouseButton) + { + if (FSlateApplication::Get().IsDragDropping()) + { +// if (Event == IE_Released) +// { +// FSlateApplication::Get().CancelDragDrop(); +// } + + return true; + } + + const int32 HitX = InViewport->GetMouseX(); + const int32 HitY = InViewport->GetMouseY(); + + // Calc the raw delta from the mouse to detect if there was any movement + FVector RawMouseDelta = MouseDeltaTracker->GetRawDelta(); + + // Note: We are using raw mouse movement to double check distance moved in low performance situations. In low performance situations its possible + // that we would get a mouse down and a mouse up before the next tick where GEditor->MouseMovment has not been updated. + // In that situation, legitimate drags are incorrectly considered clicks + bool bNoMouseMovment = RawMouseDelta.SizeSquared() < MOUSE_CLICK_DRAG_DELTA && GEditor->MouseMovement.SizeSquared() < MOUSE_CLICK_DRAG_DELTA; + + if (bNoMouseMovment && !MouseDeltaTracker->WasExternalMovement()) + { + // If the mouse haven't moved too far, treat the button release as a click. + } + else + { + HHitProxy* HitProxy = InViewport->GetHitProxy(HitX, HitY); + + + if (HActor* ActorProxy = HitProxyCast(HitProxy)) + { + if ((ActorProxy->Actor != nullptr) && !ActorProxy->Actor->bLockLocation) + { + TArray Assets; + ActorProxy->Actor->GetReferencedContentObjects(Assets); + + if ((Assets.Num() > 0) && (Assets[0] != nullptr)) + { + USelection* CBSelection = GEditor->GetSelectedActors(); + CBSelection->Select(ActorProxy->Actor, true); + ActorProxy->Actor->MarkComponentsRenderStateDirty(); + Invalidate(); + FSlateApplication::Get().CancelDragDrop(); + + const FVector2D CurrentCursorPosition = FSlateApplication::Get().GetCursorPos(); + const FVector2D LastCursorPosition = FSlateApplication::Get().GetLastCursorPos(); + + TSharedPtr DragDropOperation = FAssetDragDropOp::New(FAssetData(Assets[0], true)); + + TSet PressedMouseButtons; + PressedMouseButtons.Add(Key); + + FModifierKeysState ModifierKeyState; + + FPointerEvent FakePointerEvent( + FSlateApplication::Get().GetUserIndexForMouse(), + FSlateApplicationBase::CursorPointerIndex, + CurrentCursorPosition, + LastCursorPosition, + PressedMouseButtons, + EKeys::Invalid, + 0, + ModifierKeyState); + + // Tell slate to enter drag and drop mode. + // Make a faux mouse event for slate, so we can initiate a drag and drop. + FDragDropEvent DragDropEvent(FakePointerEvent, DragDropOperation); + + TSharedPtr OwnerWindow = FSlateApplication::Get().FindWidgetWindow(GetEditorViewportWidget().ToSharedRef()); + + FSlateApplication::Get().ProcessDragEnterEvent(OwnerWindow.ToSharedRef(), DragDropEvent); + } + } + } + + } + + return true; + } + else + { + return FEditorViewportClient::InputKey(InViewport, ControllerId, (Key == EKeys::RightMouseButton) ? EKeys::LeftMouseButton : Key, Event, AmountDepressed, bGamepad); + } +} + +FLinearColor FActorPaletteViewportClient::GetBackgroundColor() const +{ + return MyBGColor; +} + +void FActorPaletteViewportClient::OpenWorldAsPalette(const FAssetData& InSourceWorldAsset) +{ + FScopedSlowTask SlowTask(0.0f, LOCTEXT("LoadingLevelAsPalette", "Loading Level into Actor Palette...")); + + UWorld* TargetWorld = OwnedPreviewScene.GetWorld(); + + if (CurrentLevelStreaming != nullptr) + { + CurrentLevelStreaming->SetIsRequestingUnloadAndRemoval(true); + TargetWorld->FlushLevelStreaming(EFlushLevelStreamingType::Full); + CurrentLevelStreaming = nullptr; + } + + // Copy if the source world is valid + SourceWorldAsset = InSourceWorldAsset; + if (UWorld* SourceWorld = Cast(SourceWorldAsset.GetAsset())) + { + bool bSucceeded; + ULevelStreamingDynamic* NewLevel = ULevelStreamingDynamic::LoadLevelInstance(TargetWorld, SourceWorld->GetPathName(), FVector::ZeroVector, FRotator::ZeroRotator, /*out*/ bSucceeded); + + if (bSucceeded) + { + //@TODO: This is a squiffy workaround for ULevelStreamingDynamic::LoadLevelInstance doing the wrong thing if the level is already loaded and we're not in PIE + // (this doesn't make it go down the code path that makes it work in PIE, it just guarantees the destination name isn't the same as the asset name anymore...) + NewLevel->RenameForPIE(1); + + CurrentLevelStreaming = NewLevel; + + TargetWorld->FlushLevelStreaming(EFlushLevelStreamingType::Full); + + TargetWorld->EditorViews = SourceWorld->EditorViews; + + ResetCameraView(); + } + } + + // Update the most-recently used list + UActorPaletteSettings* Settings = GetMutableDefault(); + Settings->MarkAsRecentlyUsed(SourceWorldAsset, TabIndex); + + // Redraw the viewport + Invalidate(); +} + +void FActorPaletteViewportClient::ResetCameraView() +{ + UWorld* World = OwnedPreviewScene.GetWorld(); + + if (World->EditorViews.IsValidIndex(GetViewportType())) + { + FLevelViewportInfo& ViewportInfo = World->EditorViews[GetViewportType()]; + SetInitialViewTransform( + GetViewportType(), + ViewportInfo.CamPosition, + ViewportInfo.CamRotation, + ViewportInfo.CamOrthoZoom); + } +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewportClient.h b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewportClient.h new file mode 100644 index 000000000000..b86a4b3ec08e --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Private/ActorPaletteViewportClient.h @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "PreviewScene.h" +#include "EditorViewportClient.h" + +class FCanvas; +class FScopedTransaction; +class FUICommandList; +class ULevelStreamingDynamic; + +////////////////////////////////////////////////////////////////////////// +// FActorPaletteViewportClient + +class FActorPaletteViewportClient : public FEditorViewportClient +{ +public: + FActorPaletteViewportClient(int32 InTabIndex); + + // FViewportClient interface + virtual bool InputKey(FViewport* Viewport, int32 ControllerId, FKey Key, EInputEvent Event, float AmountDepressed, bool bGamepad) override; + // End of FViewportClient interface + + // FEditorViewportClient interface + virtual FLinearColor GetBackgroundColor() const override; + // End of FEditorViewportClient interface + + void OpenWorldAsPalette(const FAssetData& SourceWorldAsset); + FAssetData GetCurrentWorldAssetData() const { return SourceWorldAsset; } + + void SetOwnerWidget(const TWeakPtr OwnerPtr) + { + EditorViewportWidget = OwnerPtr; + } + + void ResetCameraView(); + +private: + int32 TabIndex; + + // The preview scene + FPreviewScene OwnedPreviewScene; + + FAssetData SourceWorldAsset; + + ULevelStreamingDynamic* CurrentLevelStreaming = nullptr; + + FLinearColor MyBGColor; +}; diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Public/ActorPaletteCommands.h b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Public/ActorPaletteCommands.h new file mode 100644 index 000000000000..3302a5c49850 --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Public/ActorPaletteCommands.h @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "ActorPaletteStyle.h" + +class FActorPaletteCommands : public TCommands +{ +public: + + FActorPaletteCommands() + : TCommands(TEXT("ActorPalette"), NSLOCTEXT("Contexts", "ActorPalette", "ActorPalette Plugin"), NAME_None, FActorPaletteStyle::GetStyleSetName()) + { + } + + // TCommands<> interface + virtual void RegisterCommands() override; + +public: + TSharedPtr ToggleGameView; + TSharedPtr ResetCameraView; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Public/ActorPaletteModule.h b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Public/ActorPaletteModule.h new file mode 100644 index 000000000000..40e99ac08dca --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Public/ActorPaletteModule.h @@ -0,0 +1,38 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class SActorPalette; + +#define MAX_ACTOR_PALETTES 4 + +class FActorPaletteModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + /** This function will be bound to Command (by default it will bring up plugin window) */ + void PluginButtonClicked(); + +private: + TSharedRef OnSpawnPluginTab(const class FSpawnTabArgs& SpawnTabArgs, int32 TabIndex); + + static FText GetActorPaletteLabelWithIndex(int32 TabIndex); + FText GetActorPaletteTabLabel(int32 TabIndex) const; + +private: + struct FActorPaletteTabInfo + { + FName TabID; + TWeakPtr OpenInstance; + }; + + FActorPaletteTabInfo ActorPaletteTabs[MAX_ACTOR_PALETTES]; + TSharedPtr PluginCommands; +}; diff --git a/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Public/ActorPaletteStyle.h b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Public/ActorPaletteStyle.h new file mode 100644 index 000000000000..f6646754d2ae --- /dev/null +++ b/Engine/Plugins/Experimental/ActorPalette/Source/ActorPalette/Public/ActorPaletteStyle.h @@ -0,0 +1,32 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/SlateStyle.h" + +/** */ +class FActorPaletteStyle +{ +public: + + static void Initialize(); + + static void Shutdown(); + + /** reloads textures used by slate renderer */ + static void ReloadTextures(); + + /** @return The Slate style set for the Shooter game */ + static const ISlateStyle& Get(); + + static FName GetStyleSetName(); + +private: + + static TSharedRef< class FSlateStyleSet > Create(); + +private: + + static TSharedPtr< class FSlateStyleSet > StyleInstance; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/AutomationUtils/Source/AutomationUtils/Private/AutomationUtilsBlueprintLibrary.cpp b/Engine/Plugins/Experimental/AutomationUtils/Source/AutomationUtils/Private/AutomationUtilsBlueprintLibrary.cpp index 5c92049cfa1a..1b85692adc2c 100644 --- a/Engine/Plugins/Experimental/AutomationUtils/Source/AutomationUtils/Private/AutomationUtilsBlueprintLibrary.cpp +++ b/Engine/Plugins/Experimental/AutomationUtils/Source/AutomationUtils/Private/AutomationUtilsBlueprintLibrary.cpp @@ -22,6 +22,7 @@ #include "Serialization/JsonSerializer.h" #include "Containers/Ticker.h" #include "Engine/GameEngine.h" +#include "Stats/Stats.h" //Private Helper Class Definitions class FAutomationUtilsGameplayViewExtension : public FSceneViewExtensionBase @@ -225,7 +226,9 @@ FAutomationUtilsGameplayAutomationScreenshotInstance::FAutomationUtilsGameplayAu } else { - FTicker::GetCoreTicker().AddTicker(TEXT("FAutomationUtilsGameplayScreenshotInstanceAutoCleanup"), 0.1f, [this](float) { + FTicker::GetCoreTicker().AddTicker(TEXT("FAutomationUtilsGameplayScreenshotInstanceAutoCleanup"), 0.1f, [this](float) + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FAutomationUtilsGameplayScreenshotInstanceAutoCleanup); FAutomationUtilsGameplayAutomationScreenshotFactory::RequestDeleteScreenshotInstance(ScreenshotName); return false; }); diff --git a/Engine/Plugins/Experimental/ChaosCaching/ChaosCaching.uplugin b/Engine/Plugins/Experimental/ChaosCaching/ChaosCaching.uplugin new file mode 100644 index 000000000000..5ccec9306706 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/ChaosCaching.uplugin @@ -0,0 +1,30 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "ChaosCaching", + "Description": "Chaos Cache asset support for recording and playing back physics simulations", + "Category": "Physics", + "CreatedBy": "Epic Games, Inc.", + "CreatedByURL": "http://epicgames.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "EnabledByDefault": false, + "CanContainContent": true, + "IsBetaVersion": true, + "IsExperimentalVersion": true, + "Installed": false, + "Modules": [ + { + "Name": "ChaosCaching", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + }, + { + "Name": "ChaosCachingEditor", + "Type": "Editor", + "LoadingPhase": "PreDefault" + } + ] +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/ChaosCaching.Build.cs b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/ChaosCaching.Build.cs new file mode 100644 index 000000000000..99e444b73383 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/ChaosCaching.Build.cs @@ -0,0 +1,41 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class ChaosCaching : ModuleRules + { + public ChaosCaching(ReadOnlyTargetRules Target) : base(Target) + { + PublicIncludePaths.Add(ModuleDirectory + "/Public"); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "EngineSettings", + "RenderCore", + "RHI", + "ChaosSolvers", + "GeometryCollectionCore", + "GeometryCollectionEngine" + }); + + if(Target.bBuildEditor) + { + // Slate/Editor extensions + PublicDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "SlateCore", + "Slate" + }); + } + + SetupModulePhysicsSupport(Target); + PrivateDefinitions.Add("CHAOS_INCLUDE_LEVEL_1=1"); + } + } +} diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/CacheAdapter.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/CacheAdapter.cpp new file mode 100644 index 000000000000..3e68885e08f3 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/CacheAdapter.cpp @@ -0,0 +1,118 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/Adapters/CacheAdapter.h" +#include "Features/IModularFeatures.h" +#include "Components/PrimitiveComponent.h" + +// Feature registration name for modular features +const FName Chaos::FComponentCacheAdapter::FeatureName("ChaosCacheAdapter"); + +// Engine adapters will always use priorities between EngineAdapterPriotityBegin and UserAdapterPriotityBegin +const uint8 Chaos::FComponentCacheAdapter::EngineAdapterPriotityBegin(0); + +// Any priority of this or above is a user implemented adapter +const uint8 Chaos::FComponentCacheAdapter::UserAdapterPriotityBegin(1 << 3); + +DEFINE_LOG_CATEGORY(LogCacheAdapter); + +namespace Chaos +{ + void RegisterAdapter(FComponentCacheAdapter* InAdapter) + { + check(InAdapter); + + IModularFeatures& ModularFeatures = IModularFeatures::Get(); + + TArray Adapters = + ModularFeatures.GetModularFeatureImplementations( + FComponentCacheAdapter::FeatureName); + + // Multiple adapters can't directly support the same class with the same priority. + // If this happens either there is a collision or an adapter has been registered twice + UClass* NewClass = InAdapter->GetDesiredClass(); + FComponentCacheAdapter::SupportType NewSupportType = InAdapter->SupportsComponentClass(NewClass); + uint8 NewPriority = InAdapter->GetPriority(); + bool bNewAdapterValid = true; + + for(FComponentCacheAdapter* Adapter : Adapters) + { + if(NewClass == Adapter->GetDesiredClass() && NewSupportType == Adapter->SupportsComponentClass(NewClass) + && NewPriority == Adapter->GetPriority()) + { + UE_LOG(LogCacheAdapter, + Error, + TEXT("Attempted to register a cache adapter with GUID %s however the desired class %s is " + "already registered at the requested priority (%u) by a cache adapter with GUID %s"), + *InAdapter->GetGuid().ToString(), + *NewClass->GetName(), + NewPriority, + *Adapter->GetGuid().ToString()); + + bNewAdapterValid = false; + break; + } + } + + if(bNewAdapterValid) + { + ModularFeatures.RegisterModularFeature(FComponentCacheAdapter::FeatureName, InAdapter); + } + } + + void UnregisterAdapter(FComponentCacheAdapter* InAdapter) + { + check(InAdapter); + + IModularFeatures& ModularFeatures = IModularFeatures::Get(); + + // Directly calling unregister on the modular features will trigger an unregister broadcast + // even if the feature wasn't actually registered, so verify here first to avoid over-broadcasting + TArray Adapters = + ModularFeatures.GetModularFeatureImplementations( + FComponentCacheAdapter::FeatureName); + + if(Adapters.Contains(InAdapter)) + { + ModularFeatures.UnregisterModularFeature(FComponentCacheAdapter::FeatureName, InAdapter); + } + } + + FComponentCacheAdapter* FAdapterUtil::GetBestAdapterForClass(TSubclassOf InComponentClass, bool bAllowDerived) + { + // Build list of adapters for our observed components + IModularFeatures& ModularFeatures = IModularFeatures::Get(); + TArray Adapters = ModularFeatures.GetModularFeatureImplementations(FComponentCacheAdapter::FeatureName); + + auto ByPriority = [](const FComponentCacheAdapter* A, const FComponentCacheAdapter* B) + { + return A->GetPriority() < B->GetPriority(); + }; + + UClass* ActualClass = InComponentClass.Get(); + TArray DirectAdapters = Adapters.FilterByPredicate([ActualClass](const FComponentCacheAdapter* InTest) + { + return InTest && InTest->SupportsComponentClass(ActualClass) == Chaos::FComponentCacheAdapter::SupportType::Direct; + }); + + TArray DerivedAdapters = Adapters.FilterByPredicate([ActualClass](const FComponentCacheAdapter* InTest) + { + return InTest && InTest->SupportsComponentClass(ActualClass) == Chaos::FComponentCacheAdapter::SupportType::Derived; + }); + + Algo::Sort(DirectAdapters, ByPriority); + Algo::Sort(DerivedAdapters, ByPriority); + + if(DirectAdapters.Num() > 0) + { + return DirectAdapters[0]; + } + + if(bAllowDerived && DerivedAdapters.Num() > 0) + { + return DerivedAdapters[0]; + } + + return nullptr; + } + +} // namespace Chaos diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/GeometryCollectionComponentCacheAdapter.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/GeometryCollectionComponentCacheAdapter.cpp new file mode 100644 index 000000000000..020f526d9b8e --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/GeometryCollectionComponentCacheAdapter.cpp @@ -0,0 +1,348 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/Adapters/GeometryCollectionComponentCacheAdapter.h" +#include "Chaos/ChaosCache.h" +#include "Chaos/ParticleHandle.h" +#include "GeometryCollection/GeometryCollectionComponent.h" +#include "PBDRigidsSolver.h" +#include "PhysicsProxy/GeometryCollectionPhysicsProxy.h" +#include "PhysicsProxy/SingleParticlePhysicsProxy.h" + +FName FEnableStateEvent::EventName("GC_Enable"); + +namespace Chaos +{ + + FComponentCacheAdapter::SupportType FGeometryCollectionCacheAdapter::SupportsComponentClass(UClass* InComponentClass) const + { + UClass* Desired = GetDesiredClass(); + if(InComponentClass == Desired) + { + return FComponentCacheAdapter::SupportType::Direct; + } + else if(InComponentClass->IsChildOf(Desired)) + { + return FComponentCacheAdapter::SupportType::Derived; + } + + return FComponentCacheAdapter::SupportType::None; + } + + UClass* FGeometryCollectionCacheAdapter::GetDesiredClass() const + { + return UGeometryCollectionComponent::StaticClass(); + } + + uint8 FGeometryCollectionCacheAdapter::GetPriority() const + { + return EngineAdapterPriotityBegin; + } + + void FGeometryCollectionCacheAdapter::Record_PostSolve(UPrimitiveComponent* InComp, const FTransform& InRootTransform, FPendingFrameWrite& OutFrame, Chaos::FReal InTime) const + { + using FClusterParticle = Chaos::TPBDRigidClusteredParticleHandle; + using FRigidParticle = Chaos::TPBDRigidParticleHandle; + + UGeometryCollectionComponent* Comp = CastChecked(InComp); + FGeometryCollectionPhysicsProxy* Proxy = Comp->GetPhysicsProxy(); + + if(!Proxy) + { + return; + } + + const Chaos::FPhysicsSolver* Solver = Proxy->GetSolver(); + const FGeometryCollection* RestCollection = Proxy->GetSimParameters().RestCollection; + + if(!RestCollection || !Solver) + { + return; + } + + FGeometryDynamicCollection& Collection = Proxy->GetPhysicsCollection(); + const TManagedArray& TransformIndices = RestCollection->TransformIndex; + const TManagedArray& Transforms = Collection.Transform; + const TArray>& Breaks = Solver->GetEvolution()->GetRigidClustering().GetAllClusterBreakings(); + + // A transform index exists for each 'real' (i.e. leaf node in the rest collection) + const int32 NumTransforms = Collection.NumElements(FGeometryCollection::TransformGroup); + + // Pre-alloc once for worst case. + OutFrame.PendingParticleData.Reserve(NumTransforms); + + TArray*> RelatedBreaks; + RelatedBreaks.Reserve(Breaks.Num()); + for(const TBreakingData& Break : Breaks) + { + // Accessing the GT particle here to pull the proxy - while unsafe we're recording a proxy currently so it should remain valid. + // No GT data is being read from the particle + Chaos::TGeometryParticle* GTParticleUnsafe = Break.Particle->GTGeometryParticle(); + IPhysicsProxyBase* BaseProxy = GTParticleUnsafe->GetProxy(); + if(BaseProxy->GetType() == EPhysicsProxyType::GeometryCollectionType) + { + FGeometryCollectionPhysicsProxy* ConcreteProxy = static_cast(BaseProxy); + + if(ConcreteProxy == Proxy) + { + // The break particle belongs to our proxy + RelatedBreaks.Add(Break.Particle); + } + } + } + + for(int32 TransformIndex = 0; TransformIndex < NumTransforms; ++TransformIndex) + { + FClusterParticle* Handle = Proxy->GetParticles()[TransformIndex]; + + if(Handle) + { + const FRigidParticle* Parent = Handle ? Handle->ClusterIds().Id : nullptr; + const FClusterParticle* ParentAsCluster = Parent ? Parent->CastToClustered() : nullptr; + const bool bParentIsInternalCluster = ParentAsCluster ? ParentAsCluster->InternalCluster() : false; + const bool bParentIsActiveInternalCluster = bParentIsInternalCluster && !Parent->Disabled(); + + const bool bParticleDisabled = Handle->Disabled(); + if(!bParticleDisabled || bParentIsActiveInternalCluster) + { + OutFrame.PendingParticleData.AddDefaulted(); + FPendingParticleWrite& Pending = OutFrame.PendingParticleData.Last(); + + Pending.ParticleIndex = TransformIndex; + Pending.PendingTransform = FTransform(Handle->R(), Handle->X()).GetRelativeTransform(InRootTransform); + } + + int32 BreakIndex = RelatedBreaks.Find(Handle); + if(BreakIndex != INDEX_NONE) + { + OutFrame.PushEvent(FEnableStateEvent::EventName, InTime, FEnableStateEvent(TransformIndex, true)); + } + } + } + + // Never going to change again till freed after writing to the cache so free up the extra space we reserved + OutFrame.PendingParticleData.Shrink(); + } + + void FGeometryCollectionCacheAdapter::Playback_PreSolve(UPrimitiveComponent* InComponent, + UChaosCache* InCache, + Chaos::FReal InTime, + FPlaybackTickRecord& TickRecord, + TArray*>& OutUpdatedRigids) const + { + using FClusterParticle = Chaos::TPBDRigidClusteredParticleHandle; + using FRigidParticle = Chaos::TPBDRigidParticleHandle; + + UGeometryCollectionComponent* Comp = CastChecked(InComponent); + FGeometryCollectionPhysicsProxy* Proxy = Comp->GetPhysicsProxy(); + + if(!Proxy) + { + return; + } + + const FGeometryCollection* RestCollection = Proxy->GetSimParameters().RestCollection; + Chaos::FPhysicsSolver* Solver = Proxy->GetSolver(); + + if(!RestCollection || !Solver) + { + return; + } + + FGeometryDynamicCollection& Collection = Proxy->GetPhysicsCollection(); + const TManagedArray& TransformIndices = RestCollection->TransformIndex; + const TManagedArray& Transforms = Collection.Transform; + TArray Particles = Proxy->GetParticles(); + + FCacheEvaluationContext Context(TickRecord); + Context.bEvaluateTransform = true; + Context.bEvaluateCurves = false; + Context.bEvaluateEvents = true; + + FCacheEvaluationResult EvaluatedResult = InCache->Evaluate(Context); + + const int32 NumEventTracks = EvaluatedResult.Events.Num(); + const TArray* EnableEvents = EvaluatedResult.Events.Find(FEnableStateEvent::EventName); + + if(EnableEvents) + { + TMap> NewClusters; + for(const FCacheEventHandle& Handle : *EnableEvents) + { + if(FEnableStateEvent* Event = Handle.Get()) + { + if(Particles.IsValidIndex(Event->Index)) + { + Chaos::TPBDRigidClusteredParticleHandle* ChildParticle = Particles[Event->Index]; + + if(ChildParticle->ObjectState() != EObjectStateType::Kinematic) + { + // If a field or other external actor set the particle to static or dynamic we no longer apply the cache + continue; + } + + if(FRigidParticle* ClusterParent = ChildParticle->ClusterIds().Id) + { + if(FClusterParticle* Parent = ClusterParent->CastToClustered()) + { + TArray& Cluster = NewClusters.FindOrAdd(Parent); + Cluster.Add(ChildParticle); + } + } + else + { + // This is a cluster parent + ChildParticle->SetDisabled(!Event->bEnable); + } + + } + } + } + + for(TPair> Cluster : NewClusters) + { + Solver->GetEvolution()->GetRigidClustering().ReleaseClusterParticles(Cluster.Value); + } + } + + const int32 NumTransforms = EvaluatedResult.Transform.Num(); + for(int32 Index = 0; Index < NumTransforms; ++Index) + { + const int32 ParticleIndex = EvaluatedResult.ParticleIndices[Index]; + const FTransform EvaluatedTransform = EvaluatedResult.Transform[Index]; + + if(Particles.IsValidIndex(ParticleIndex)) + { + Chaos::TPBDRigidClusteredParticleHandleFloat3* Handle = Particles[ParticleIndex]; + + if(!Handle || Handle->ObjectState() != EObjectStateType::Kinematic) + { + // If a field or other external actor set the particle to static or dynamic we no longer apply the cache + continue; + } + + Handle->SetP(EvaluatedTransform.GetTranslation()); + Handle->SetQ(EvaluatedTransform.GetRotation()); + Handle->SetX(Handle->P()); + Handle->SetR(Handle->Q()); + + if(FRigidParticle* ClusterParent = Handle->ClusterIds().Id) + { + if(FClusterParticle* Parent = ClusterParent->CastToClustered()) + { + if(Parent->InternalCluster()) + { + // This is an unmanaged particle. Because its children are kinematic it will be also. + // however we need to update its position at least once to place it correctly. + // The child was placed with: + // ChildT = ChildHandle->ChildToParent() * FTransform(ParentHandle->R(), ParentHandle->X()); + // When it was simulated, so we can work backwards to place the parent. + // This will result in multiple transform sets happening to the parent but allows us to mostly ignore + // that it exists, if it doesn't the child still gets set to the correct position. + FTransform ChildTransform = Handle->ChildToParent(); + FTransform Result = ChildTransform.Inverse() * EvaluatedTransform; + Parent->SetP(Result.GetTranslation()); + Parent->SetX(Result.GetTranslation()); + Parent->SetQ(Result.GetRotation()); + Parent->SetR(Result.GetRotation()); + } + } + } + + OutUpdatedRigids.Add(Handle); + } + } + } + + bool FGeometryCollectionCacheAdapter::ValidForPlayback(UPrimitiveComponent* InComponent, UChaosCache* InCache) const + { + UGeometryCollectionComponent* GeomComponent = Cast(InComponent); + + if(!GeomComponent) + { + return false; + } + + const UGeometryCollection* Collection = GeomComponent->RestCollection; + + if(!Collection || !Collection->GetGeometryCollection().IsValid()) + { + return false; + } + + // Really permissive check - as long as we can map all tracks to a particle in the geometry collection we'll allow this to play. + // allows geometry changes without invalidating an entire cache on reimport or modification. + const int32 NumTransforms = Collection->GetGeometryCollection()->Transform.Num(); + for(const int32 ParticleIndex : InCache->TrackToParticle) + { + if(ParticleIndex < 0 || ParticleIndex >= NumTransforms) + { + return false; + } + } + + return true; + } + + FGuid FGeometryCollectionCacheAdapter::GetGuid() const + { + FGuid NewGuid; + checkSlow(FGuid::Parse(TEXT("A3147746B50C47C883B93DBF85CBB589"), NewGuid)); + return NewGuid; + } + + Chaos::FPhysicsSolver* FGeometryCollectionCacheAdapter::GetComponentSolver(UPrimitiveComponent* InComponent) const + { + if(InComponent && InComponent->GetWorld()) + { + UWorld* ComponentWorld = InComponent->GetWorld(); + + if(FPhysScene* WorldScene = ComponentWorld->GetPhysicsScene()) + { + return WorldScene->GetSolver(); + } + } + + return nullptr; + } + + bool FGeometryCollectionCacheAdapter::InitializeForRecord(UPrimitiveComponent* InComponent, UChaosCache* InCache) const + { + UGeometryCollectionComponent* Comp = CastChecked(InComponent); + FGeometryCollectionPhysicsProxy* Proxy = Comp->GetPhysicsProxy(); + + if(!Proxy) + { + return false; + } + + Chaos::FPhysicsSolver* Solver = Proxy->GetSolver(); + + if(!Solver) + { + return false; + } + + // We need breaking data to record cluster breaking information into the cache + Solver->GetEvolution()->GetRigidClustering().SetGenerateClusterBreaking(true); + + return true; + } + + bool FGeometryCollectionCacheAdapter::InitializeForPlayback(UPrimitiveComponent* InComponent, UChaosCache* InCache) const + { + UGeometryCollectionComponent* Comp = CastChecked(InComponent); + FGeometryCollectionPhysicsProxy* Proxy = Comp->GetPhysicsProxy(); + + FGeometryDynamicCollection& Collection = Proxy->GetPhysicsCollection(); + + for(int32& State : Collection.DynamicState) + { + State = (int32)EObjectStateTypeEnum::Chaos_Object_Kinematic; + } + + return true; + } + +} // namespace Chaos + +Chaos::TAutoRegisterCacheAdapter GeometryCollectionAdapter; diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/StaticMeshComponentCacheAdapter.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/StaticMeshComponentCacheAdapter.cpp new file mode 100644 index 000000000000..99eee0a4bba2 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/Adapters/StaticMeshComponentCacheAdapter.cpp @@ -0,0 +1,193 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/Adapters/StaticMeshComponentCacheAdapter.h" +#include "Chaos/ParticleHandle.h" +#include "PhysicsProxy/SingleParticlePhysicsProxy.h" +#include "PBDRigidsSolver.h" +#include "Chaos/ChaosCache.h" +#include "Components/StaticMeshComponent.h" + +namespace Chaos +{ + FComponentCacheAdapter::SupportType FStaticMeshCacheAdapter::SupportsComponentClass(UClass* InComponentClass) const + { + UClass* Desired = GetDesiredClass(); + if(InComponentClass == Desired) + { + return FComponentCacheAdapter::SupportType::Direct; + } + else if(InComponentClass->IsChildOf(Desired)) + { + return FComponentCacheAdapter::SupportType::Derived; + } + + return FComponentCacheAdapter::SupportType::None; + } + + UClass* FStaticMeshCacheAdapter::GetDesiredClass() const + { + return UStaticMeshComponent::StaticClass(); + } + + uint8 FStaticMeshCacheAdapter::GetPriority() const + { + return EngineAdapterPriotityBegin; + } + + template + void RecordToCacheInternal(ProxyType* InProxy, const FTransform& InRootTransform, FPendingFrameWrite& OutFrame, Chaos::FReal InTime) + { + typename ProxyType::FParticleHandle* Handle = InProxy->GetHandle(); + + if(Handle) + { + if(TPBDRigidParticleHandle * AsRigid = Handle->CastToRigidParticle()) + { + FPendingParticleWrite NewData; + + NewData.ParticleIndex = 0; // Only one particle for static caches + NewData.PendingTransform = FTransform(AsRigid->R(), AsRigid->X()).GetRelativeTransform(InRootTransform); + + OutFrame.PendingParticleData.Add(MoveTemp(NewData)); + } + } + } + + void FStaticMeshCacheAdapter::Record_PostSolve(UPrimitiveComponent* InComponent, const FTransform& InRootTransform, FPendingFrameWrite& OutFrame, Chaos::FReal InTime) const + { + UStaticMeshComponent* MeshComp = CastChecked(InComponent); + + IPhysicsProxyBase* PhysProxy = MeshComp->BodyInstance.ActorHandle->GetProxy(); + + switch(PhysProxy->GetType()) + { + case EPhysicsProxyType::SingleRigidParticleType: + { + FRigidParticlePhysicsProxy* Proxy = static_cast(PhysProxy); + RecordToCacheInternal(Proxy, InRootTransform, OutFrame, InTime); + break; + } + case EPhysicsProxyType::SingleKinematicParticleType: + { + FKinematicGeometryParticlePhysicsProxy* Proxy = static_cast(PhysProxy); + RecordToCacheInternal(Proxy, InRootTransform, OutFrame, InTime); + break; + } + case EPhysicsProxyType::SingleGeometryParticleType: + { + FGeometryParticlePhysicsProxy* Proxy = static_cast(PhysProxy); + RecordToCacheInternal(Proxy, InRootTransform, OutFrame, InTime); + break; + } + } + } + + template + void PlayFromCacheInternal(ProxyType* InProxy, UChaosCache* InCache, FPlaybackTickRecord& TickRecord, TArray*>& OutUpdatedRigids) + { + if(!InCache || InCache->GetDuration() == 0.0f) + { + return; + } + + typename ProxyType::FParticleHandle* Handle = InProxy->GetHandle(); + + if(Handle && Handle->ObjectState() == EObjectStateType::Kinematic) + { + if(TPBDRigidParticleHandle* AsRigid = Handle->CastToRigidParticle()) + { + FCacheEvaluationContext Context(TickRecord); + Context.bEvaluateTransform = true; + Context.bEvaluateCurves = false; + Context.bEvaluateEvents = false; + + FCacheEvaluationResult EvaluatedResult = InCache->Evaluate(Context); + + // Either 0 or 1 result, 0 for nothing in the eval track - 1 if there was. + if(EvaluatedResult.Transform.Num() == 1) + { + AsRigid->SetX(EvaluatedResult.Transform[0].GetTranslation()); + AsRigid->SetR(EvaluatedResult.Transform[0].GetRotation()); + } + + OutUpdatedRigids.Add(AsRigid); + } + } + } + + void FStaticMeshCacheAdapter::Playback_PreSolve(UPrimitiveComponent* InComponent, UChaosCache* InCache, Chaos::FReal InTime, FPlaybackTickRecord& TickRecord, TArray*>& OutUpdatedRigids) const + { + UStaticMeshComponent* MeshComp = CastChecked(InComponent); + + IPhysicsProxyBase* PhysProxy = MeshComp->BodyInstance.ActorHandle->GetProxy(); + + switch(PhysProxy->GetType()) + { + case EPhysicsProxyType::SingleRigidParticleType: + { + FRigidParticlePhysicsProxy* Proxy = static_cast(PhysProxy); + PlayFromCacheInternal(Proxy, InCache, TickRecord, OutUpdatedRigids); + break; + } + case EPhysicsProxyType::SingleKinematicParticleType: + { + FKinematicGeometryParticlePhysicsProxy* Proxy = static_cast(PhysProxy); + PlayFromCacheInternal(Proxy, InCache, TickRecord, OutUpdatedRigids); + break; + } + case EPhysicsProxyType::SingleGeometryParticleType: + { + FGeometryParticlePhysicsProxy* Proxy = static_cast(PhysProxy); + PlayFromCacheInternal(Proxy, InCache, TickRecord, OutUpdatedRigids); + break; + } + } + } + + FGuid FStaticMeshCacheAdapter::GetGuid() const + { + FGuid NewGuid; + checkSlow(FGuid::Parse(TEXT("82570E6C014B4D2FA7866A0EC99924C4"), NewGuid)); + return NewGuid; + } + + bool FStaticMeshCacheAdapter::ValidForPlayback(UPrimitiveComponent* InComponent, UChaosCache* InCache) const + { + // If we have a mesh we can play back any cache as long as it has one or more tracks + UStaticMeshComponent* Comp = Cast(InComponent); + return Comp && Comp->GetStaticMesh() && InCache->TrackToParticle.Num() > 0; + } + + Chaos::FPhysicsSolver* FStaticMeshCacheAdapter::GetComponentSolver(UPrimitiveComponent* InComponent) const + { + if(InComponent && InComponent->GetWorld()) + { + UWorld* ComponentWorld = InComponent->GetWorld(); + + if(FPhysScene* WorldScene = ComponentWorld->GetPhysicsScene()) + { + return WorldScene->GetSolver(); + } + } + + return nullptr; + } + + bool FStaticMeshCacheAdapter::InitializeForRecord(UPrimitiveComponent* InComponent, UChaosCache* InCache) const + { + return true; + } + + bool FStaticMeshCacheAdapter::InitializeForPlayback(UPrimitiveComponent* InComponent, UChaosCache* InCache) const + { + if(Cast(InComponent)) + { + FPhysInterface_Chaos::SetIsKinematic_AssumesLocked(InComponent->GetBodyInstance()->ActorHandle, true); + } + + return true; + } + +} // namespace Chaos + +Chaos::TAutoRegisterCacheAdapter StaticMeshAdapter; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/CacheCollection.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/CacheCollection.cpp new file mode 100644 index 000000000000..b8c3d044995a --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/CacheCollection.cpp @@ -0,0 +1,61 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/CacheCollection.h" +#include "Chaos/ChaosCache.h" +#include "Algo/Find.h" +#include "Async/ParallelFor.h" + +UChaosCache* UChaosCacheCollection::FindCache(const FName& CacheName) const +{ + UChaosCache* const* ExistingCache = Algo::FindByPredicate(Caches, [&CacheName](const UChaosCache* Test) + { + return Test->GetFName() == CacheName; + }); + + return ExistingCache ? *ExistingCache : nullptr; +} + +UChaosCache* UChaosCacheCollection::FindOrAddCache(const FName& CacheName) +{ + FName FinalName = CacheName; + UChaosCache* ResultCache = nullptr; + + if(FinalName != NAME_None) + { + UChaosCache** ExistingCache = Algo::FindByPredicate(Caches, [&FinalName](const UChaosCache* Test) + { + return Test->GetFName() == FinalName; + }); + + ResultCache = ExistingCache ? *ExistingCache : nullptr; + } + + if(!ResultCache) + { + // Final check for unique name, or no name - generate one from the base name + // (GetPlainNameString so we don't get increasing strings of appended numbers) + if(StaticFindObject(UChaosCache::StaticClass(), this, *FinalName.ToString()) || FinalName == NAME_None) + { + FinalName = MakeUniqueObjectName(this, UChaosCache::StaticClass(), *FinalName.GetPlainNameString()); + } + + ResultCache = NewObject(this, FinalName, RF_Transactional); + + Caches.Add(ResultCache); + } + + return ResultCache; +} + +void UChaosCacheCollection::FlushAllCacheWrites() +{ + ParallelFor(Caches.Num(), [this](int32 InIndex) + { + Caches[InIndex]->FlushPendingFrames(); + }); +} + +const TArray UChaosCacheCollection::GetCaches() const +{ + return Caches; +} diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/CacheEvents.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/CacheEvents.cpp new file mode 100644 index 000000000000..1796e301ebef --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/CacheEvents.cpp @@ -0,0 +1,182 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/CacheEvents.h" +#include "Logging/MessageLog.h" + +FCacheEventTrack::FCacheEventTrack() + : Name(NAME_None) + , Struct(nullptr) +{ + +} + +FCacheEventTrack::FCacheEventTrack(FName InName, UScriptStruct* InStruct) + : Name(InName) + , Struct(InStruct) +{ + +} + +FCacheEventTrack::~FCacheEventTrack() +{ + DestroyAll(); +} + +FCacheEventTrack::FHandle FCacheEventTrack::GetEventHandle(int32 Index) +{ + FHandle NewHandle; + if(EventData.IsValidIndex(Index)) + { + NewHandle.Track = this; + NewHandle.Index = Index; + NewHandle.Version = TransientVersion; + } + + return NewHandle; +} + +void FCacheEventTrack::DestroyAll() +{ + if(!Struct) + { + // Make sure we haven't lost the struct after adding some items + check(EventData.Num() == 0); + return; + } + + // Invalidate old handles + ++TransientVersion; + + for(uint8* EventPtr : EventData) + { + Struct->DestroyStruct(EventPtr); + FMemory::Free(EventPtr); + } + + EventData.Reset(); + TimeStamps.Reset(); +} + +bool FCacheEventTrack::Serialize(FArchive& Ar) +{ + // Serialize the normal UPROPERTY data + if(Ar.IsLoading() || Ar.IsSaving()) + { + const UScriptStruct* ThisStruct = FCacheEventTrack::StaticStruct(); + ThisStruct->SerializeTaggedProperties(Ar, reinterpret_cast(this), Struct, nullptr); + } + + if(Ar.IsSaving()) + { + SaveEventsToArchive(Ar); + } + else if(Ar.IsLoading()) + { + LoadEventsFromArchive(Ar); + } + + return true; +} + +void FCacheEventTrack::Merge(FCacheEventTrack&& Other) +{ + if(Other.TimeStamps.Num() == 0) + { + return; + } + + // Invalidate old handles + ++Other.TransientVersion; + ++TransientVersion; + + const int32 NumEntries = Other.TimeStamps.Num(); + for(int32 Index = 0; Index < NumEntries; ++Index) + { + int32 InsertIndex = Algo::LowerBound(TimeStamps, Other.TimeStamps[Index]); + + if(TimeStamps.IsValidIndex(InsertIndex)) + { + TimeStamps.Insert(Other.TimeStamps[Index], InsertIndex); + EventData.Insert(Other.EventData[Index], InsertIndex); + } + else + { + TimeStamps.Add(Other.TimeStamps[Index]); + EventData.Add(Other.EventData[Index]); + } + } + + Other.TimeStamps.Reset(); + Other.EventData.Reset(); +} + +int32 FCacheEventTrack::GetTransientVersion() const +{ + return TransientVersion; +} + +void FCacheEventTrack::PushEventInternal(float TimeStep, const void* Event) +{ + // Validation already performed by FCacheEventTrack::PushEvent for the struct + uint8* NewData = reinterpret_cast(FMemory::Malloc(Struct->GetStructureSize())); + + // Construct and then copy struct data + Struct->InitializeStruct(NewData); + Struct->CopyScriptStruct(NewData, Event); + + TimeStamps.Add(TimeStep); + EventData.Add(NewData); + + // Invalidate old handles + ++TransientVersion; +} + +void FCacheEventTrack::LoadEventsFromArchive(FArchive& Ar) +{ + UScriptStruct* StructToLoad = Struct; + if(!StructToLoad) + { + // This struct contains no data so won't load garbage - but lets us continue + StructToLoad = FCacheEventBase::StaticStruct(); + + FMessageLog("LoadErrors").Error() + ->AddToken(FTextToken::Create(NSLOCTEXT("ChaosCache", "StructLoadError", "Failed to load Chaos cache event track as the underlying struct was unavailable."))); + } + + const int32 NumEvents = TimeStamps.Num(); + + for(int32 Index = 0; Index < NumEvents; ++Index) + { + uint8* NewEvent = reinterpret_cast(FMemory::Malloc(StructToLoad->GetStructureSize())); + + StructToLoad->InitializeStruct(NewEvent); + StructToLoad->SerializeItem(Ar, NewEvent, nullptr); + + EventData.Add(NewEvent); + } +} + +void FCacheEventTrack::SaveEventsToArchive(FArchive& Ar) +{ + UScriptStruct* StructToSave = Struct; + if(!StructToSave) + { + // This struct contains no data so won't load garbage - but lets us continue + StructToSave = FCacheEventBase::StaticStruct(); + + FMessageLog("SaveErrors").Error() + ->AddToken(FTextToken::Create(NSLOCTEXT("ChaosCache", "StructSaveError", "Failed to save Chaos cache event track as the underlying struct was unavailable."))); + } + + check(EventData.Num() == TimeStamps.Num()); + + for(uint8* EventPtr : EventData) + { + StructToSave->SerializeItem(Ar, EventPtr, nullptr); + } +} + +bool FCacheEventTrack::FHandle::IsAlive() const +{ + return Index != INDEX_NONE && Track && Track->GetTransientVersion() == Version; +} diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/CacheManagerActor.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/CacheManagerActor.cpp new file mode 100644 index 000000000000..9348de59cff2 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/CacheManagerActor.cpp @@ -0,0 +1,585 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/CacheManagerActor.h" + +#include "Chaos/Adapters/CacheAdapter.h" +#include "Chaos/CacheCollection.h" +#include "Chaos/ChaosCache.h" +#include "Chaos/ChaosCachingPlugin.h" +#include "ChaosSolversModule.h" +#include "Components/BillboardComponent.h" +#include "PBDRigidsSolver.h" + +#if WITH_EDITOR +#include "Editor.h" +#include "Editor/EditorEngine.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Styling/CoreStyle.h" +#include "Styling/ISlateStyle.h" +#include "UObject/ConstructorHelpers.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Kismet2/ComponentEditorUtils.h" +#endif + +#define LOCTEXT_NAMESPACE "ChaosCacheManager" + +FName GetComponentCacheName(UPrimitiveComponent* InComponent) +{ + return FName(InComponent->GetPathName(InComponent->GetWorld())); +} + +void FObservedComponent::ResetRuntimeData() +{ + bTriggered = StartMode == EStartMode::Timed; + AbsoluteTime = 0; + TimeSinceTrigger = 0; + Cache = nullptr; + + TickRecord.Reset(); +} + +UPrimitiveComponent* FObservedComponent::GetComponent() +{ + return Cast(ComponentRef.GetComponent(nullptr)); +} + +UPrimitiveComponent* FObservedComponent::GetComponent() const +{ + return Cast(ComponentRef.GetComponent(nullptr)); +} + +AChaosCacheManager::AChaosCacheManager(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) + : Super(ObjectInitializer) +{ + // This actor will tick, just not normally. There needs to be a tick-like event both before physics simulation + // and after physics simulation, we bind to some physics scene events in BeginPlay to handle this. + PrimaryActorTick.bCanEverTick = true; + + // Add a scene component as our root + RootComponent = ObjectInitializer.CreateDefaultSubobject(this, TEXT("Root")); + RootComponent->SetMobility(EComponentMobility::Static); + + // Add a sprite when in the editor +#if WITH_EDITOR + struct FConstructorStatics + { + ConstructorHelpers::FObjectFinderOptional SpriteTextureObject; + FName ID_CacheManager; + FText NAME_CacheManager; + + FConstructorStatics() + : SpriteTextureObject(TEXT("/Engine/EditorResources/S_Actor")) + , ID_CacheManager(TEXT("Cache Manager")) + , NAME_CacheManager(NSLOCTEXT("SpriteCategory", "CacheManager", "Chaos Cache Manager")) + { + } + }; + static FConstructorStatics ConstructorStatics; + + UBillboardComponent* SpriteComp = ObjectInitializer.CreateEditorOnlyDefaultSubobject(this, TEXT("Editor Icon")); + + if(SpriteComp) + { + SpriteComp->Sprite = ConstructorStatics.SpriteTextureObject.Get(); + SpriteComp->SpriteInfo.Category = ConstructorStatics.ID_CacheManager; + SpriteComp->SpriteInfo.DisplayName = ConstructorStatics.NAME_CacheManager; + SpriteComp->Mobility = EComponentMobility::Static; + SpriteComp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + } +#endif +} + +void AChaosCacheManager::TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction) +{ + Super::TickActor(DeltaTime, TickType, ThisTickFunction); + + if(CacheCollection) + { + CacheCollection->FlushAllCacheWrites(); + } +} + +#if WITH_EDITOR +void AChaosCacheManager::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); +} +#endif + +void AChaosCacheManager::SetAllMode(ECacheMode InMode) +{ + for(FObservedComponent& Observed : ObservedComponents) + { + Observed.CacheMode = InMode; + } +} + +void AChaosCacheManager::ResetAllComponentTransforms() +{ + if(!CacheCollection) + { + return; + } + + for(FObservedComponent& Observed : ObservedComponents) + { + if(UPrimitiveComponent* Comp = Observed.GetComponent()) + { + if(UChaosCache* Cache = CacheCollection->FindCache(Observed.CacheName)) + { + Comp->SetWorldTransform(Cache->Spawnable.InitialTransform); + } + } + } +} + +void AChaosCacheManager::ResetSingleTransform(int32 InIndex) +{ + if(!ObservedComponents.IsValidIndex(InIndex)) + { + return; + } + + FObservedComponent& Observed = ObservedComponents[InIndex]; + + if(UPrimitiveComponent* Comp = Observed.GetComponent()) + { + if(UChaosCache* Cache = CacheCollection->FindCache(Observed.CacheName)) + { + Comp->SetWorldTransform(Cache->Spawnable.InitialTransform); + } + } +} + +#if WITH_EDITOR +void AChaosCacheManager::SelectComponent(int32 InIndex) +{ + if(!ObservedComponents.IsValidIndex(InIndex)) + { + return; + } + + FObservedComponent& Observed = ObservedComponents[InIndex]; + + if(UPrimitiveComponent* Comp = Observed.GetComponent()) + { + GEditor->SelectNone(true, true); + GEditor->SelectActor(Comp->GetOwner(), true, true); + GEditor->SelectComponent(Comp, true, true); + } +} +#endif + +void AChaosCacheManager::BeginPlay() +{ + using namespace Chaos; + + Super::BeginPlay(); + + if(!CacheCollection) + { + // without a collection the cache manager can't do anything, no reason to inialise the observed array + SetActorTickEnabled(false); + return; + } + else + { + SetActorTickEnabled(true); + } + + const TArray Caches = CacheCollection->GetCaches(); + + // Build list of adapters for our observed components + IModularFeatures& ModularFeatures = IModularFeatures::Get(); + TArray Adapters = ModularFeatures.GetModularFeatureImplementations(FComponentCacheAdapter::FeatureName); + + ActiveAdapters.Reset(); + + int32 NumFailedPlaybackEntries = 0; + const int32 NumComponents = ObservedComponents.Num(); + for(int32 Index = 0; Index < NumComponents; ++Index) + { + FObservedComponent& Observed = ObservedComponents[Index]; + + auto ByPriority = [](const FComponentCacheAdapter* A, const FComponentCacheAdapter* B) { + return A->GetPriority() < B->GetPriority(); + }; + + UPrimitiveComponent* Comp = Observed.GetComponent(); + + if(!Comp) + { + ActiveAdapters.Add(nullptr); + continue; + } + + UClass* ActualClass = Comp->GetClass(); + + TArray DirectAdapters = Adapters.FilterByPredicate([ActualClass](const FComponentCacheAdapter* InTest) { + return InTest && InTest->SupportsComponentClass(ActualClass) == Chaos::FComponentCacheAdapter::SupportType::Direct; + }); + + TArray DerivedAdapters = Adapters.FilterByPredicate([ActualClass](const FComponentCacheAdapter* InTest) { + return InTest && InTest->SupportsComponentClass(ActualClass) == Chaos::FComponentCacheAdapter::SupportType::Derived; + }); + + Algo::Sort(DirectAdapters, ByPriority); + Algo::Sort(DerivedAdapters, ByPriority); + + if(DirectAdapters.Num() == 0 && DerivedAdapters.Num() == 0) + { + // No actual adapter for this class type, log and push nullptr for this observed object + ActiveAdapters.Add(nullptr); + } + else + { + if(DirectAdapters.Num() > 0) + { + ActiveAdapters.Add(DirectAdapters[0]); + } + else + { + ActiveAdapters.Add(DerivedAdapters[0]); + } + } + + // Reset timers and last cache + Observed.ResetRuntimeData(); + + bool bRequiresRecord = false; + FComponentCacheAdapter* CurrAdapter = ActiveAdapters[Index]; + check(CurrAdapter); // should definitely have added one above + + if(Chaos::FPhysicsSolver* Solver = CurrAdapter->GetComponentSolver(Comp)) + { + FPerSolverData* SolverData = PerSolverData.Find(Solver); + + if(!SolverData) + { + SolverData = &PerSolverData.Add(Solver); + SolverData->PreSolveHandle = Solver->AddPreAdvanceCallback(FSolverPreAdvance::FDelegate::CreateUObject(this, &AChaosCacheManager::HandlePreSolve, Solver)); + SolverData->PreBufferHandle = Solver->AddPreBufferCallback(FSolverPreAdvance::FDelegate::CreateUObject(this, &AChaosCacheManager::HandlePreBuffer, Solver)); + SolverData->PostSolveHandle = Solver->AddPostAdvanceCallback(FSolverPostAdvance::FDelegate::CreateUObject(this, &AChaosCacheManager::HandlePostSolve, Solver)); + } + + switch(Observed.CacheMode) + { + case ECacheMode::Play: + { + UChaosCache* PlayCache = CacheCollection->FindCache(Observed.CacheName); + + if(PlayCache) + { + FCacheUserToken Token = PlayCache->BeginPlayback(); + + if(Token.IsOpen() && CurrAdapter->ValidForPlayback(Comp, PlayCache)) + { + SolverData->PlaybackIndices.Add(Index); + SolverData->PlaybackTickRecords.AddDefaulted(); + SolverData->PlaybackTickRecords.Last().SetSpaceTransform(Comp->GetComponentToWorld()); + Observed.Cache = PlayCache; + Observed.TickRecord.SetSpaceTransform(Comp->GetComponentToWorld()); + OpenPlaybackCaches.Add(TTuple(MoveTemp(Token), Observed.Cache)); + CurrAdapter->InitializeForPlayback(Comp, Observed.Cache); + } + else + { + if(Token.IsOpen()) + { + UE_LOG(LogChaosCache, + Warning, + TEXT("Failed playback for component %s, Selected cache adapter unable to handle the cache (the cache is incompatible)"), + *Comp->GetPathName()); + + // The cache session was valid so make sure to end it + PlayCache->EndPlayback(Token); + } + else // Already open for record somewhere + { + UE_LOG(LogChaosCache, + Warning, + TEXT("Failed playback for component %s using cache %s, cache already open for record"), + *Comp->GetName(), + *PlayCache->GetPathName()); + } + + ++NumFailedPlaybackEntries; + } + } + else + { + UE_LOG(LogChaosCache, Log, TEXT("Skipping playback for component %s, no available cache."), *Comp->GetName()); + } + + break; + } + case ECacheMode::Record: + { + // Make sure there's a cache available if we're going to record. + UPrimitiveComponent* Component = Observed.GetComponent(); + FName CacheName = Observed.CacheName == NAME_None ? MakeUniqueObjectName(CacheCollection, UChaosCache::StaticClass(), "Cache") : Observed.CacheName; + + UChaosCache* RecordCache = CacheCollection->FindOrAddCache(CacheName); + FCacheUserToken Token = RecordCache->BeginRecord(Observed.GetComponent(), CurrAdapter->GetGuid()); + + if(Token.IsOpen()) + { + SolverData->RecordIndices.Add(Index); + + Observed.Cache = CacheCollection->FindOrAddCache(CacheName); + Observed.TickRecord.SetSpaceTransform(Comp->GetComponentToWorld()); + Observed.Cache->BeginRecord(Observed.GetComponent(), CurrAdapter->GetGuid()); + OpenRecordCaches.Add(TTuple(MoveTemp(Token), Observed.Cache)); + CurrAdapter->InitializeForRecord(Component, Observed.Cache); + + // Ensure we enable the actor tick to flush out the pending record writes + bRequiresRecord = true; + } + break; + } + default: + { + break; + } + } + } + + // If we're recording then the physics thread(s) will be filling queues on each cache of pending writes + // which we consume on the game thread in the manager tick. + SetActorTickEnabled(bRequiresRecord); + } + + if(NumFailedPlaybackEntries > 0) + { + UE_LOG(LogChaosCache, Warning, TEXT("Failed playback for %d components"), NumFailedPlaybackEntries); + +#if WITH_EDITOR + FNotificationInfo Info(FText::Format(LOCTEXT("FailedPlaybackToast", "Failed Chaos cache playback for {0} components."), FText::AsNumber(NumFailedPlaybackEntries))); + Info.ExpireDuration = 5.0f; + Info.bFireAndForget = true; + Info.Image = FCoreStyle::Get().GetBrush(TEXT("MessageLog.Warning")); + FSlateNotificationManager::Get().AddNotification(Info); +#endif + } +} + +void AChaosCacheManager::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + FChaosSolversModule* Module = FChaosSolversModule::GetModule(); + check(Module); + + for(TPair PerSolver : PerSolverData) + { + Chaos::FPhysicsSolver* CurrSolver = PerSolver.Key; + FPerSolverData& CurrData = PerSolver.Value; + if(ensure(CurrSolver)) + { + ensure(CurrSolver->RemovePostAdvanceCallback(CurrData.PostSolveHandle)); + ensure(CurrSolver->RemovePreBufferCallback(CurrData.PreBufferHandle)); + ensure(CurrSolver->RemovePreAdvanceCallback(CurrData.PreSolveHandle)); + CurrData.PostSolveHandle.Reset(); + CurrData.PreSolveHandle.Reset(); + CurrData.PreBufferHandle.Reset(); + } + } + + ActiveAdapters.Reset(); + + // Close any open caches as the session is complete. this will flush pending writes and post-process the cache + for(TTuple& OpenCache : OpenRecordCaches) + { + OpenCache.Get<1>()->EndRecord(OpenCache.Get<0>()); + } + OpenRecordCaches.Reset(); + + for(TTuple& OpenCache : OpenPlaybackCaches) + { + OpenCache.Get<1>()->EndPlayback(OpenCache.Get<0>()); + } + OpenPlaybackCaches.Reset(); +} + +void AChaosCacheManager::HandlePreSolve(Chaos::FReal InDt, Chaos::FPhysicsSolver* InSolver) +{ + if(!CacheCollection) + { + return; + } + + FPerSolverData* Data = PerSolverData.Find(InSolver); + + if(!Data) + { + ensureMsgf(false, TEXT("AChaosCacheManager::HandlePostSolve couldn't find a solver entry - a solver binding has leaked.")); + return; + } + + TickObservedComponents(Data->PlaybackIndices, InDt, [this, Data](UChaosCache* InCache, FObservedComponent& Observed, Chaos::FComponentCacheAdapter* InAdapter) { + UPrimitiveComponent* Comp = Observed.GetComponent(); + if(ensure(Comp)) + { + InAdapter->Playback_PreSolve(Comp, InCache, Observed.TimeSinceTrigger, Observed.TickRecord, Data->PendingKinematicUpdates); + } + }); +} + +void AChaosCacheManager::HandlePreBuffer(Chaos::FReal InDt, Chaos::FPhysicsSolver* InSolver) +{ + if(!CacheCollection) + { + return; + } + + FPerSolverData* Data = PerSolverData.Find(InSolver); + + if(!Data) + { + ensureMsgf(false, TEXT("AChaosCacheManager::HandlePreBuffer couldn't find a solver entry - a solver binding has leaked.")); + return; + } + + for(Chaos::TPBDRigidParticleHandle* PendingKinematic : Data->PendingKinematicUpdates) + { + InSolver->GetParticles().MarkTransientDirtyParticle(PendingKinematic); + } + + Data->PendingKinematicUpdates.Reset(); +} + +void AChaosCacheManager::HandlePostSolve(Chaos::FReal InDt, Chaos::FPhysicsSolver* InSolver) +{ + if(!CacheCollection) + { + return; + } + + FPerSolverData* Data = PerSolverData.Find(InSolver); + + if(!Data) + { + ensureMsgf(false, TEXT("AChaosCacheManager::HandlePostSolve couldn't find a solver entry - a solver binding has leaked.")); + return; + } + + TickObservedComponents(Data->RecordIndices, InDt, [](UChaosCache* InCache, FObservedComponent& Observed, Chaos::FComponentCacheAdapter* InAdapter) { + UPrimitiveComponent* Comp = Observed.GetComponent(); + if(ensure(Comp && InCache)) + { + // If we haven't advanced since the last record, don't push another frame + if(Observed.TimeSinceTrigger > InCache->GetDuration()) + { + FPendingFrameWrite NewFrame; + NewFrame.Time = Observed.TimeSinceTrigger; + InAdapter->Record_PostSolve(Comp, Observed.TickRecord.GetSpaceTransform(), NewFrame, Observed.TimeSinceTrigger); + + InCache->AddFrame_Concurrent(MoveTemp(NewFrame)); + } + } + }); +} + +void AChaosCacheManager::TriggerComponent(UPrimitiveComponent* InComponent) +{ + // #BGTODO Maybe not totally thread-safe, probably safer with an atomic or condition var rather than the bTriggered flag + FObservedComponent* Found = Algo::FindByPredicate(ObservedComponents, [InComponent](const FObservedComponent& Test) { + return Test.GetComponent() == InComponent; + }); + + if(Found && Found->StartMode == EStartMode::Triggered) + { + Found->bTriggered = true; + } +} + +void AChaosCacheManager::TriggerComponentByCache(FName InCacheName) +{ + FObservedComponent* Found = Algo::FindByPredicate(ObservedComponents, [InCacheName](const FObservedComponent& Test) { + return Test.CacheName == InCacheName && Test.GetComponent(); + }); + + if(Found && Found->StartMode == EStartMode::Triggered) + { + Found->bTriggered = true; + } +} + +void AChaosCacheManager::TriggerAll() +{ + for(FObservedComponent& Observed : ObservedComponents) + { + if(Observed.StartMode == EStartMode::Triggered && Observed.GetComponent()) + { + Observed.bTriggered = true; + } + } +} + +FObservedComponent* AChaosCacheManager::FindObservedComponent(UPrimitiveComponent* InComponent) +{ + return Algo::FindByPredicate(ObservedComponents, [ToTest = InComponent](const FObservedComponent& Item) { + return Item.GetComponent() == ToTest; + }); +} + +FObservedComponent& AChaosCacheManager::AddNewObservedComponent(UPrimitiveComponent* InComponent) +{ + check(InComponent->CreationMethod != EComponentCreationMethod::UserConstructionScript); + ObservedComponents.AddDefaulted(); + FObservedComponent& NewEntry = ObservedComponents.Last(); + + NewEntry.ComponentRef.PathToComponent = InComponent->GetPathName(InComponent->GetOwner()); + NewEntry.ComponentRef.OtherActor = InComponent->GetOwner(); + NewEntry.CacheName = MakeUniqueObjectName(CacheCollection, UChaosCache::StaticClass(), "Cache"); + + return NewEntry; +} + +FObservedComponent& AChaosCacheManager::FindOrAddObservedComponent(UPrimitiveComponent* InComponent) +{ + FObservedComponent* Found = FindObservedComponent(InComponent); + return Found ? *Found : AddNewObservedComponent(InComponent); +} + +void AChaosCacheManager::TickObservedComponents(const TArray& InIndices, Chaos::FReal InDt, FTickObservedFunction InCallable) +{ + for(int32 Index : InIndices) + { + check(ObservedComponents.IsValidIndex(Index) && ObservedComponents.Num() == ActiveAdapters.Num()); + + FObservedComponent& Observed = ObservedComponents[Index]; + Chaos::FComponentCacheAdapter* Adapter = ActiveAdapters[Index]; + + if(!Observed.Cache) + { + // Skip if no available cache - this can happen if a component was deleted while being observed - the other components + // can play fine, we just omit any that we cannot find. + continue; + } + + Observed.AbsoluteTime += InDt; + + // Adapters can be null if there isn't support available for a selected component + // (happens if a plugin implemented it but is no longer loaded) + if(Observed.bTriggered && Adapter) + { + if(Observed.CacheMode == ECacheMode::Play) + { + Observed.TickRecord.SetDt(InDt); + } + + if(Observed.TimedDuration == 0.0f) + { + Observed.TimeSinceTrigger += InDt; + InCallable(Observed.Cache, Observed, Adapter); + } + else + { + Observed.TimedDuration = FMath::Max(Observed.TimedDuration - InDt, 0.0f); + } + } + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/ChaosCache.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/ChaosCache.cpp new file mode 100644 index 000000000000..a325f3835bf7 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/ChaosCache.cpp @@ -0,0 +1,511 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/ChaosCache.h" +#include "Chaos/ChaosCachingPlugin.h" +#include "Components/PrimitiveComponent.h" + +void UChaosCache::FlushPendingFrames() +{ + QUICK_SCOPE_CYCLE_COUNTER(QSTAT_CacheFlushPendingFrames); + bool bWroteParticleData = false; + FPendingFrameWrite NewData; + while(PendingWrites.Dequeue(NewData)) + { + QUICK_SCOPE_CYCLE_COUNTER(QSTAT_CacheFlushSingleFrame); + const int32 ParticleCount = NewData.PendingParticleData.Num(); + + bWroteParticleData |= ParticleCount > 0; + + for(int32 Index = 0; Index < ParticleCount; ++Index) + { + const FPendingParticleWrite& ParticleData = NewData.PendingParticleData[Index]; + const int32 ParticleIndex = ParticleData.ParticleIndex; + + int32 TrackIndex = INDEX_NONE; + if(!TrackToParticle.Find(ParticleIndex, TrackIndex)) + { + TrackToParticle.Add(ParticleIndex); + TrackIndex = ParticleTracks.AddDefaulted(); + } + + FPerParticleCacheData& TargetCacheData = ParticleTracks[TrackIndex]; + FParticleTransformTrack& PTrack = TargetCacheData.TransformData; + + if(PTrack.GetNumKeys() == 0) + { + // Initial write to this particle + PTrack.BeginOffset = NewData.Time; + } + + // Make sure we're actually appending to the track - shouldn't be adding data from the past + if(ensure(PTrack.GetNumKeys() == 0 || NewData.Time > PTrack.KeyTimestamps.Last())) + { + PTrack.KeyTimestamps.Add(NewData.Time); + + // Append the transform (ignoring scale) + FRawAnimSequenceTrack& RawTrack = PTrack.RawTransformTrack; + RawTrack.ScaleKeys.Add(FVector(1.0f)); + RawTrack.PosKeys.Add(ParticleData.PendingTransform.GetTranslation()); + RawTrack.RotKeys.Add(ParticleData.PendingTransform.GetRotation()); + + for(TPair CurveKeyPair : ParticleData.PendingCurveData) + { + FRichCurve& TargetCurve = TargetCacheData.CurveData.FindOrAdd(CurveKeyPair.Key); + TargetCurve.AddKey(NewData.Time, CurveKeyPair.Value); + } + } + } + + for(TTuple& PendingTrack : NewData.PendingEvents) + { + FCacheEventTrack* CacheTrack = EventTracks.Find(PendingTrack.Key); + + if(!CacheTrack) + { + CacheTrack = &EventTracks.Add(PendingTrack.Key, FCacheEventTrack(PendingTrack.Key, PendingTrack.Value.Struct)); + } + + CacheTrack->Merge(MoveTemp(PendingTrack.Value)); + } + + ++NumRecordedFrames; + } + + if(bWroteParticleData) + { + QUICK_SCOPE_CYCLE_COUNTER(QSTAT_CacheCalcDuration); + float Min = TNumericLimits::Max(); + float Max = -Min; + for(const FPerParticleCacheData ParticleData : ParticleTracks) + { + Min = FMath::Min(Min, ParticleData.TransformData.GetBeginTime()); + Max = FMath::Max(Max, ParticleData.TransformData.GetEndTime()); + } + + RecordedDuration = Max - Min; + } +} + +FCacheUserToken UChaosCache::BeginRecord(UPrimitiveComponent* InComponent, FGuid InAdapterId) +{ + // First make sure we're valid to record + int32 OtherRecordersCount = CurrentRecordCount.fetch_add(1); + if(OtherRecordersCount == 0) + { + // We're the only recorder + if(CurrentPlaybackCount.load() == 0) + { + // And there's no playbacks, we can proceed + // Setup the cache to begin recording + RecordedDuration = 0.0f; + NumRecordedFrames = 0; + ParticleTracks.Reset(); + TrackToParticle.Reset(); + CurveData.Reset(); + EventTracks.Reset(); + + PendingWrites.Empty(); + + // Initialise the spawnable template to handle the provided component + BuildSpawnableFromComponent(InComponent); + + // Build a compatibility hash for the component state. + AdapterGuid = InAdapterId; + + return FCacheUserToken(true, true, this); + } + else + { + UE_LOG(LogChaosCache, Warning, TEXT("Failed to open cache %s for record, it was the only recorder but the cache was open for playback."), *GetPathName()); + CurrentRecordCount--; + } + } + else + { + UE_LOG(LogChaosCache, Warning, TEXT("Failed to open cache %s for record, the cache was already open for record."), *GetPathName()); + CurrentRecordCount--; + } + + return FCacheUserToken(false, true, this); +} + +void UChaosCache::EndRecord(FCacheUserToken& InOutToken) +{ + if(InOutToken.IsOpen() && InOutToken.Owner == this) + { + FlushPendingFrames(); + // Cache now complete, process data + + // Invalidate the token + InOutToken.bIsOpen = false; + InOutToken.Owner = nullptr; + CurrentRecordCount--; + } + else + { + if(InOutToken.Owner) + { + UE_LOG(LogChaosCache, Warning, TEXT("Attempted to close a recording session with a token from an external cache.")); + } + else + { + UE_LOG(LogChaosCache, Warning, TEXT("Attempted to close a recording session with an invalid token")); + } + } +} + +FCacheUserToken UChaosCache::BeginPlayback() +{ + CurrentPlaybackCount++; + if(CurrentRecordCount.load() == 0) + { + // We can playback from this cache as it isn't open for record + return FCacheUserToken(true, false, this); + } + else + { + CurrentPlaybackCount--; + } + + return FCacheUserToken(false, false, this); +} + +void UChaosCache::EndPlayback(FCacheUserToken& InOutToken) +{ + if(InOutToken.IsOpen() && InOutToken.Owner == this) + { + // Invalidate the token + InOutToken.bIsOpen = false; + InOutToken.Owner = nullptr; + CurrentPlaybackCount--; + } + else + { + if(InOutToken.Owner) + { + UE_LOG(LogChaosCache, Warning, TEXT("Attempted to close a playback session with a token from an external cache.")); + } + else + { + UE_LOG(LogChaosCache, Warning, TEXT("Attempted to close a playback session with an invalid token")); + } + } +} + +void UChaosCache::AddFrame_Concurrent(FPendingFrameWrite&& InFrame) +{ + PendingWrites.Enqueue(MoveTemp(InFrame)); +} + +float UChaosCache::GetDuration() const +{ + return RecordedDuration; +} + +FCacheEvaluationResult UChaosCache::Evaluate(const FCacheEvaluationContext& InContext) +{ + QUICK_SCOPE_CYCLE_COUNTER(QSTAT_CacheEval); + + FCacheEvaluationResult Result; + + if(CurrentPlaybackCount.load() == 0) + { + // No valid playback session + UE_LOG(LogChaosCache, Warning, TEXT("Attempted to evaluate a cache that wasn't opened for playback")); + return Result; + } + + Result.EvaluatedTime = InContext.TickRecord.GetTime(); + + if(!InContext.bEvaluateTransform && !InContext.bEvaluateCurves && !InContext.bEvaluateEvents) + { + // no evaluation requested + return Result; + } + + const int32 NumProvidedIndices = InContext.EvaluationIndices.Num(); + + if(NumProvidedIndices > 0 && NumProvidedIndices < ParticleTracks.Num()) + { + if(InContext.bEvaluateTransform) + { + Result.Transform.SetNum(NumProvidedIndices); + } + + if(InContext.bEvaluateCurves) + { + Result.Curves.SetNum(NumProvidedIndices); + } + + for(int32 EvalIndex = 0; EvalIndex < NumProvidedIndices; ++EvalIndex) + { + int32 CacheIndex = InContext.EvaluationIndices[EvalIndex]; + if(ensure(ParticleTracks.IsValidIndex(CacheIndex))) + { + FTransform* EvalTransform = nullptr; + TMap* EvalCurves = nullptr; + + if(InContext.bEvaluateTransform) + { + EvalTransform = &Result.Transform[EvalIndex]; + } + + if(InContext.bEvaluateCurves) + { + EvalCurves = &Result.Curves[EvalIndex]; + } + + if(TrackToParticle.IsValidIndex(CacheIndex)) + { + Result.ParticleIndices.Add(TrackToParticle[CacheIndex]); + } + else + { + Result.ParticleIndices.Add(INDEX_NONE); + } + + EvaluateSingle(CacheIndex, InContext.TickRecord, EvalTransform, EvalCurves); + } + } + } + else + { + const int32 NumParticles = ParticleTracks.Num(); + + if(InContext.bEvaluateTransform) + { + Result.Transform.Reserve(NumParticles); + } + + if(InContext.bEvaluateCurves) + { + Result.Curves.Reserve(NumParticles); + } + + for(int32 Index = 0; Index < NumParticles; ++Index) + { + if(ParticleTracks[Index].TransformData.BeginOffset > InContext.TickRecord.GetTime()) + { + // Track hasn't begun yet so skip evaluation + continue; + } + + FTransform* EvalTransform = nullptr; + TMap* EvalCurves = nullptr; + + if(InContext.bEvaluateTransform) + { + Result.Transform.AddUninitialized(); + EvalTransform = &Result.Transform.Last(); + } + + if(InContext.bEvaluateCurves) + { + Result.Curves.AddUninitialized(); + EvalCurves = &Result.Curves.Last(); + } + + if(TrackToParticle.IsValidIndex(Index)) + { + Result.ParticleIndices.Add(TrackToParticle[Index]); + } + else + { + Result.ParticleIndices.Add(INDEX_NONE); + } + + EvaluateSingle(Index, InContext.TickRecord, EvalTransform, EvalCurves); + } + } + + if(InContext.bEvaluateEvents) + { + Result.Events.Reserve(EventTracks.Num()); + EvaluateEvents(InContext.TickRecord, Result.Events); + } + + // Update the tick record on completion for the next run + InContext.TickRecord.LastTime = InContext.TickRecord.GetTime(); + InContext.TickRecord.CurrentDt = 0.0f; + + return Result; +} + +void UChaosCache::BuildSpawnableFromComponent(UPrimitiveComponent* InComponent) +{ + Spawnable.DuplicatedTemplate = StaticDuplicateObject(InComponent, this); + Spawnable.InitialTransform = InComponent->GetComponentToWorld(); +} + +const FCacheSpawnableTemplate& UChaosCache::GetSpawnableTemplate() const +{ + return Spawnable; +} + +void UChaosCache::EvaluateSingle(int32 InIndex, FPlaybackTickRecord& InTickRecord, FTransform* OutOptTransform, TMap* OutOptCurves) +{ + // check to satisfy SA, external callers check validity in Evaluate + checkSlow(ParticleTracks.IsValidIndex(InIndex)); + FPerParticleCacheData& Data = ParticleTracks[InIndex]; + + if(OutOptTransform) + { + EvaluateTransform(Data, InTickRecord.GetTime(), *OutOptTransform); + (*OutOptTransform) = (*OutOptTransform) * InTickRecord.SpaceTransform; + } + + if(OutOptCurves) + { + EvaluateCurves(Data, InTickRecord.GetTime(), *OutOptCurves); + } +} + +void UChaosCache::EvaluateTransform(const FPerParticleCacheData& InData, float InTime, FTransform& OutTransform) +{ + OutTransform = InData.TransformData.Evaluate(InTime); +} + +void UChaosCache::EvaluateCurves(const FPerParticleCacheData& InData, float InTime, TMap& OutCurves) +{ + for(const TPair& Curve : InData.CurveData) + { + OutCurves.FindOrAdd(Curve.Key) = Curve.Value.Eval(InTime, 0.0f); + } +} + +void UChaosCache::EvaluateEvents(FPlaybackTickRecord& InTickRecord, TMap>& OutEvents) +{ + OutEvents.Reset(); + + for(TTuple& Track : EventTracks) + { + FCacheEventTrack& TrackRef = Track.Value; + if(TrackRef.Num() == 0) + { + continue; + } + + int32* BeginIndexPtr = InTickRecord.LastEventPerTrack.Find(Track.Key); + const int32 BeginIndex = BeginIndexPtr ? *BeginIndexPtr : 0; + + TArrayView TimeStampView(&TrackRef.TimeStamps[BeginIndex], TrackRef.TimeStamps.Num() - BeginIndex); + + int32 BeginEventIndex = Algo::UpperBound(TimeStampView, InTickRecord.LastTime) + BeginIndex; + const int32 EndEventIndex = Algo::UpperBound(TimeStampView, InTickRecord.GetTime()) + BeginIndex; + + TArray NewHandles; + NewHandles.Reserve(EndEventIndex - BeginEventIndex); + + // Add anything we found + while(BeginEventIndex != EndEventIndex) + { + NewHandles.Add(TrackRef.GetEventHandle(BeginEventIndex)); + ++BeginEventIndex; + } + + // If we added any handles then we must have a new index for the lastevent tracker in the tick record + if(NewHandles.Num() > 0) + { + int32& OutBeginIndex = BeginIndexPtr ? *BeginIndexPtr : InTickRecord.LastEventPerTrack.Add(Track.Key); + OutBeginIndex = NewHandles.Last().Index; + + // Push to the result container + OutEvents.Add(TTuple>(Track.Key, MoveTemp(NewHandles))); + } + } +} + +FTransform FParticleTransformTrack::Evaluate(float InCacheTime) const +{ + QUICK_SCOPE_CYCLE_COUNTER(QSTAT_EvalParticleTransformTrack); + const int32 NumKeys = GetNumKeys(); + + if(NumKeys > 0) + { + if(InCacheTime < BeginOffset) + { + // Take first key + return FTransform(RawTransformTrack.RotKeys[0], RawTransformTrack.PosKeys[0]); + } + else if(InCacheTime > KeyTimestamps.Last()) + { + // Take last key + return FTransform(RawTransformTrack.RotKeys.Last(), RawTransformTrack.PosKeys.Last()); + } + else + { + // Valid in-range, evaluate + if(NumKeys == 1) + { + return FTransform(RawTransformTrack.RotKeys[0], RawTransformTrack.PosKeys[0]); + } + + // Find the first key with a timestamp greater than InCacheTIme + int32 IndexBeyond = 0; + { + QUICK_SCOPE_CYCLE_COUNTER(QSTAT_UpperBound); + IndexBeyond = Algo::UpperBound(KeyTimestamps, InCacheTime); + } + + if(IndexBeyond == INDEX_NONE) + { + // Must be equal to the last key + return FTransform(RawTransformTrack.RotKeys.Last(), RawTransformTrack.PosKeys.Last()); + } + + const int32 IndexBefore = IndexBeyond - 1; + + if(IndexBefore == INDEX_NONE) + { + // Must have been equal to first key + return FTransform(RawTransformTrack.RotKeys[0], RawTransformTrack.PosKeys[0]); + } + + // Need to interpolate + const float Interval = KeyTimestamps[IndexBeyond] - KeyTimestamps[IndexBefore]; + const float Fraction = (InCacheTime - KeyTimestamps[IndexBefore]) / Interval; + + // Slerp rotation - lerp translation + return FTransform(FQuat::Slerp(RawTransformTrack.RotKeys[IndexBefore], RawTransformTrack.RotKeys[IndexBeyond], Fraction), + FMath::Lerp(RawTransformTrack.PosKeys[IndexBefore], RawTransformTrack.PosKeys[IndexBeyond], Fraction)); + } + } + + return FTransform::Identity; +} + +const int32 FParticleTransformTrack::GetNumKeys() const +{ + return KeyTimestamps.Num(); +} + +const float FParticleTransformTrack::GetDuration() const +{ + if(GetNumKeys() > 1) + { + return KeyTimestamps.Last() - KeyTimestamps[0]; + } + return 0.0f; +} + +const float FParticleTransformTrack::GetBeginTime() const +{ + const int32 NumKeys = GetNumKeys(); + if(NumKeys > 0) + { + return KeyTimestamps[0]; + } + + return 0.0f; +} + +const float FParticleTransformTrack::GetEndTime() const +{ + const int32 NumKeys = GetNumKeys(); + if(NumKeys > 0) + { + return KeyTimestamps.Last(); + } + + return 0.0f; +} diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/ChaosCachingPlugin.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/ChaosCachingPlugin.cpp new file mode 100644 index 000000000000..62c079e38b5f --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Private/Chaos/ChaosCachingPlugin.cpp @@ -0,0 +1,13 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/ChaosCachingPlugin.h" +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_MODULE(IChaosCachingPlugin, ChaosCaching) + +DEFINE_LOG_CATEGORY(LogChaosCache) + +void IChaosCachingPlugin::StartupModule() {} + +void IChaosCachingPlugin::ShutdownModule() {} diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/Adapters/CacheAdapter.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/Adapters/CacheAdapter.h new file mode 100644 index 000000000000..aa0cadfa6031 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/Adapters/CacheAdapter.h @@ -0,0 +1,194 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Chaos/Core.h" +#include "Chaos/ParticleHandleFwd.h" +#include "Features/IModularFeature.h" +#include "Templates/SubclassOf.h" +#include "Chaos/PBDRigidsEvolutionFwd.h" + +class UClass; +class UChaosCache; +class UPrimitiveComponent; +struct FPlaybackTickRecord; + +DECLARE_LOG_CATEGORY_EXTERN(LogCacheAdapter, Log, All); + +struct FPendingFrameWrite; + +namespace Chaos +{ + using FPhysicsSolver = FPBDRigidsSolver; +} + +namespace Chaos +{ + class FComponentCacheAdapter; + struct FAdapterUtil + { + static CHAOSCACHING_API FComponentCacheAdapter* GetBestAdapterForClass(TSubclassOf InComponentClass, bool bAllowDerived = true); + }; + + /** + * Base adapter type for interfacing with Chaos simulation caches. Any component wishing to use caches must have a + * compatible record adapter and a playback adapter to produce and consume cache data. + * + * This base interface should not be used to implement those adapters however but the derived classes + * FCacheRecordAdapter and FCachePlaybackAdapter (declared below) should instead be used. + */ + class CHAOSCACHING_API FComponentCacheAdapter : public IModularFeature + { + public: + enum class SupportType : uint8 + { + None, + Direct, + Derived + }; + + /** Registration name for modular features module */ + static const FName FeatureName; + static const uint8 EngineAdapterPriotityBegin; + static const uint8 UserAdapterPriotityBegin; + + FComponentCacheAdapter() = default; + virtual ~FComponentCacheAdapter() = default; + + /** + * Query whether InComponentClass can be driven by this adapter. If a class implementing this interface says it + * can support a component it *may* be chosen as the driving adapter for that component and will be expeted to + * be able to handle Pull/Push operations if it is chosen. + * + * If the adapter returns SupportType::Derived and another adapter returns SupportType::Direct then the adapter + * that can directly drive the class is chosen to drive the cache. If multiple adapters return + * SupportType::Direct then the first is chosen. If multiple adapters return SupportType::Derived then the + * adapter with the closest desired class will be chosen (closest in terms of inheritance hierarchy between the + * desired direct class and the provided class) + * + * If the adapter can only handle one specific type then only using SupportType::Direct and SupportType::None + * will make sure it only receives calls from the cache if exactly that component class is selected + * + * @param InComponent - the component class to test + * + * @return the type of support this adapter can give for the specified class + */ + virtual SupportType SupportsComponentClass(UClass* InComponentClass) const = 0; + + /** + * Query the class that this adapter wants to drive. This will be used to work out the most likely candidate for + * an adapter when multiple adapters return SupportType::Derived. For example in the following hierarchy: + * + * UPrimitiveComponent + * | + * UDerivedComponentA + * | + * UDerivedComponentB + * + * An adapter directly supporing UDerivedComponentA that returns SupportType::Derived for a class of + * UDerivedComponentB will be picked over an adapter that directly supports UPrimitiveComponent and returns + * SupportType::Derived for a class of UDerivedComponentB + * + * Note: All classes returned from this MUST have UPrimitiveComponent in their hierarchy as that is the base + * physically-capable component + */ + virtual UClass* GetDesiredClass() const = 0; + + /** + * Gets the priority for an adapter. + * When two or more adapters give the same support level for a given component class this priority will be + * used to decide which adapter will be used. All base engine level adapters per type will use a priority + * between EngineAdapterPriotityBegin and UserAdapterPriorityBegin defined in this interface. + * + * Users implementing adapters intended to override all engine functionality should return priorities + * above UserAdapterPriorityBegin to ensure they will always be selected above engine adapters. + */ + virtual uint8 GetPriority() const = 0; + + /** + * Called to retrieve the solver for a specific component. Required until a more generic method + * of solver binding for components is devised. + * #BGTODO Remove when multiple solver concept moved into primitive component + * @param InComponent Component to resolve the solver for + */ + virtual Chaos::FPhysicsSolver* GetComponentSolver(UPrimitiveComponent* InComponent) const = 0; + + /** + * Called from the game thread to initialize a component and cache ready to record a cache + * @param InComponent Target component to initialize + * @param InCache Target cache to initialize + */ + virtual bool InitializeForRecord(UPrimitiveComponent* InComponent, UChaosCache* InCache) const = 0; + + /** + * Called from the game thread to initialize a component and cache ready to playback a cache + * @param InComponent Target component to initialize + * @param InCache Target cache to initialize + */ + virtual bool InitializeForPlayback(UPrimitiveComponent* InComponent, UChaosCache* InCache) const = 0; + + /** + * Called by a cache observer when a component should be recorded to a cache through this adapter. + * The time provided is the absolute time from the beginning of the cache recording that the adapter is + * expected to record to. Almost always recording should be done from Record_PostSolve + */ + virtual void Record_PostSolve(UPrimitiveComponent* InComp, const FTransform& InRootTransform, FPendingFrameWrite& OutFrame, Chaos::FReal InTime) const = 0; + + /** + * Called by a cache observer actor when a cache needs to be applied to a component through this adapter. + * The time provided is the absolute time from the beginning of the cache playback that the adapter is + * expected to apply to the supplied component. Note this is called on the physics thread. + */ + virtual void Playback_PreSolve(UPrimitiveComponent* InComponent, + UChaosCache* InCache, + Chaos::FReal InTime, + FPlaybackTickRecord& TickRecord, + TArray*>& OutUpdatedRigids) const = 0; + + /** + * Gets a unique identifier for the adapter + * + * This must return a stable GUID that uniquely identifies any derived classes. This GUID is embedded into + * any cache that an adapter writes to so it can be matched up on replay. Changing the GUID returned will + * permanently invalidate any caches that were recorded with this adapter and they will no longer be able + * to playback + */ + virtual FGuid GetGuid() const = 0; + + /** + * Determines whether a cache is able to safely playback on a component. + * After the GUID for an adapter is validated this check will be called from the game thread to ensure + * an adapter is able to safely play back for the provided component. + * @param InComponent Target component attempting playback. + * @param InCache Requested cache to playback. + */ + virtual bool ValidForPlayback(UPrimitiveComponent* InComponent, UChaosCache* InCache) const = 0; + + protected: + private: + }; + + void RegisterAdapter(FComponentCacheAdapter* InAdapter); + void UnregisterAdapter(FComponentCacheAdapter* InAdapter); + + /** Helper to handle automatic global registration for adapter types - with type checking for valid adapter types */ + template + struct TAutoRegisterCacheAdapter + { + static_assert(TPointerIsConvertibleFromTo::Value, "AdapterType is not an adapter"); + + TAutoRegisterCacheAdapter() + { + RegisterAdapter(&Adapter); + } + + ~TAutoRegisterCacheAdapter() + { + UnregisterAdapter(&Adapter); + } + + private: + AdapterType Adapter; + }; + +} // namespace Chaos diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/Adapters/GeometryCollectionComponentCacheAdapter.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/Adapters/GeometryCollectionComponentCacheAdapter.h new file mode 100644 index 000000000000..23b511373a9b --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/Adapters/GeometryCollectionComponentCacheAdapter.h @@ -0,0 +1,65 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CacheAdapter.h" +#include "Chaos/CacheEvents.h" +#include "GeometryCollectionComponentCacheAdapter.generated.h" + +USTRUCT() +struct FEnableStateEvent : public FCacheEventBase +{ + GENERATED_BODY() + + static FName EventName; + + FEnableStateEvent() + : Index(INDEX_NONE) + , bEnable(false) + { + } + + FEnableStateEvent(int32 InIndex, bool bInEnable) + : Index(InIndex) + , bEnable(bInEnable) + { + } + + UPROPERTY() + int32 Index; + + UPROPERTY() + bool bEnable; +}; + +class UChaosCache; +class UPrimitiveComponent; + +namespace Chaos +{ + class FGeometryCollectionCacheAdapter : public FComponentCacheAdapter + { + public: + virtual ~FGeometryCollectionCacheAdapter() = default; + + // FComponentCacheAdapter interface + SupportType SupportsComponentClass(UClass* InComponentClass) const override; + UClass* GetDesiredClass() const override; + uint8 GetPriority() const override; + FGuid GetGuid() const override; + Chaos::FPhysicsSolver* GetComponentSolver(UPrimitiveComponent* InComponent) const override; + bool ValidForPlayback(UPrimitiveComponent* InComponent, UChaosCache* InCache) const override; + bool InitializeForRecord(UPrimitiveComponent* InComponent, UChaosCache* InCache) const override; + bool InitializeForPlayback(UPrimitiveComponent* InComponent, UChaosCache* InCache) const override; + void Record_PostSolve(UPrimitiveComponent* InComp, const FTransform& InRootTransform, FPendingFrameWrite& OutFrame, Chaos::FReal InTime) const override; + void Playback_PreSolve(UPrimitiveComponent* InComponent, + UChaosCache* InCache, + Chaos::FReal InTime, + FPlaybackTickRecord& TickRecord, + TArray*>& OutUpdatedRigids) const override; + + // End FComponentCacheAdapter interface + + private: + }; +} // namespace Chaos diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/Adapters/StaticMeshComponentCacheAdapter.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/Adapters/StaticMeshComponentCacheAdapter.h new file mode 100644 index 000000000000..34dab55edebd --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/Adapters/StaticMeshComponentCacheAdapter.h @@ -0,0 +1,31 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CacheAdapter.h" + +namespace Chaos +{ + class FStaticMeshCacheAdapter : public FComponentCacheAdapter + { + public: + virtual ~FStaticMeshCacheAdapter() = default; + + SupportType SupportsComponentClass(UClass* InComponentClass) const override; + UClass* GetDesiredClass() const override; + uint8 GetPriority() const override; + FGuid GetGuid() const override; + bool ValidForPlayback(UPrimitiveComponent* InComponent, UChaosCache* InCache) const override; + Chaos::FPhysicsSolver* GetComponentSolver(UPrimitiveComponent* InComponent) const override; + bool InitializeForRecord(UPrimitiveComponent* InComponent, UChaosCache* InCache) const override; + bool InitializeForPlayback(UPrimitiveComponent* InComponent, UChaosCache* InCache) const override; + void Record_PostSolve(UPrimitiveComponent* InComp, const FTransform& InRootTransform, FPendingFrameWrite& OutFrame, Chaos::FReal InTime) const override; + void Playback_PreSolve(UPrimitiveComponent* InComponent, + UChaosCache* InCache, + Chaos::FReal InTime, + FPlaybackTickRecord& TickRecord, + TArray*>& OutUpdatedRigids) const override; + + private: + }; +} // namespace Chaos diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/CacheCollection.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/CacheCollection.h new file mode 100644 index 000000000000..b664cb5dfc7c --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/CacheCollection.h @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CacheCollection.generated.h" + +class UChaosCache; + +UCLASS() +class CHAOSCACHING_API UChaosCacheCollection : public UObject +{ + GENERATED_BODY() +public: + + UChaosCache* FindCache(const FName& CacheName) const; + UChaosCache* FindOrAddCache(const FName& CacheName); + + void FlushAllCacheWrites(); + + const TArray GetCaches() const; + + UPROPERTY(EditAnywhere, Instanced, Category="Caching", meta=(EditFixedOrder)) + TArray Caches; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/CacheEvents.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/CacheEvents.h new file mode 100644 index 000000000000..1e6816f44525 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/CacheEvents.h @@ -0,0 +1,167 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Chaos/ChaosCachingPlugin.h" + +#include "CacheEvents.generated.h" + +/** + * Base type for all events, ALL events must derive from this so we have a fallback for serialization + * when we can't find the actual event struct. + */ +USTRUCT() +struct FCacheEventBase +{ + GENERATED_BODY(); + + FCacheEventBase() {} + + virtual ~FCacheEventBase() {} +}; + +USTRUCT() +struct FCacheEventTrack +{ + GENERATED_BODY() + + struct FHandle + { + FHandle() + : Track(nullptr) + , Version(0) + , Index(INDEX_NONE) + { + } + + template + T* Get() const + { + return IsAlive() ? Track->GetEvent(Index) : nullptr; + } + + private: + friend class UChaosCache; + friend struct FCacheEventTrack; + + bool IsAlive() const; + + FCacheEventTrack* Track; + int32 Version; + int32 Index; + }; + + FCacheEventTrack(); + FCacheEventTrack(FName InName, UScriptStruct* InStruct); + + ~FCacheEventTrack(); + + UPROPERTY() + FName Name; + + /** Type of the event stored in this track. Must inherit FCacheEventBase */ + UPROPERTY() + UScriptStruct* Struct; + + UPROPERTY() + TArray TimeStamps; + + TArray EventData; + + /** Pushes an event to the track, this will perform a copy of the event and store it inside the track */ + template + void PushEvent(float TimeStamp, const T& InEvent) + { + if(!Struct) + { + UE_LOG(LogChaosCache, Error, TEXT("Attempted to add an event to a track that has no struct")); + return; + } + + // Have to push the right kind of data + check(T::StaticStruct() == Struct); + PushEventInternal(TimeStamp, &InEvent); + } + + template + T* GetEvent(int32 Index) + { + if(EventData.IsValidIndex(Index) && Struct) + { + check(T::StaticStruct() == Struct); + return reinterpret_cast(EventData[Index]); + } + + return nullptr; + } + + /** Get a handle to an event that can easily resolve the inner event type without knowing the track */ + FHandle GetEventHandle(int32 Index); + + template + TArray GetEvents(float T0, float T1, int32* OptOutBeginIndex = nullptr) + { + check(T::StaticStruct() == Struct); + + TArray OutEvents; + int32 BeginIndex = Algo::LowerBound(TimeStamps, T0); + int32 EndIndex = Algo::LowerBound(TimeStamps, T1); + + if(TimeStamps.IsValidIndex(BeginIndex)) + { + if(OptOutBeginIndex) + { + (*OptOutBeginIndex) = BeginIndex; + } + + EndIndex = TimeStamps.IsValidIndex(EndIndex) ? EndIndex : TimeStamps.Num(); + const int32 NumOutEvents = EndIndex - BeginIndex; + OutEvents.Reserve(NumOutEvents); + + for(int32 EventIndex = BeginIndex; EventIndex < EndIndex; ++EventIndex) + { + OutEvents.Add(reinterpret_cast(EventData[EventIndex])); + } + } + + return OutEvents; + } + + /** Because the memory management for this track is manual, Destroy all will destroy the stored structs correctly*/ + void DestroyAll(); + + /** Custom serialize handles generic event serialization */ + bool Serialize(FArchive& Ar); + + /** Merge the events from another track, leaving the other empty. The other track must be of the same event type */ + void Merge(FCacheEventTrack&& Other); + + /** The transient version changes whenever the size of the event array changes and invalidates all old event handles */ + int32 GetTransientVersion() const; + + int32 Num() const + { + return EventData.Num(); + } + +private: + /** + * Version/Modified counter to invalidate evaluation handles if something holds on to them over time + * Because handles are used during evaluation we don't need to store this per event + */ + int32 TransientVersion; + void PushEventInternal(float TimeStep, const void* Event); + void LoadEventsFromArchive(FArchive& Ar); + void SaveEventsToArchive(FArchive& Ar); +}; + +using FCacheEventHandle = FCacheEventTrack::FHandle; + +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithSerializer = true, + }; +}; diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/CacheManagerActor.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/CacheManagerActor.h new file mode 100644 index 000000000000..09cd873b0838 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/CacheManagerActor.h @@ -0,0 +1,241 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Chaos/ParticleHandleFwd.h" +#include "Chaos/PBDRigidsEvolutionFwd.h" +#include "ChaosCache.h" +#include "Engine/EngineTypes.h" +#include "Chaos/Core.h" +#include "GameFramework/Actor.h" + +#include "CacheManagerActor.generated.h" + +class UChaosCacheCollection; +class UPrimitiveComponent; + +namespace Chaos +{ +class FComponentCacheAdapter; +} + +UENUM() +enum class ECacheMode : uint8 +{ + None, + Play, + Record +}; + +UENUM() +enum class EStartMode : uint8 +{ + Timed, + Triggered, +}; + +namespace Chaos +{ + using FPhysicsSolver = FPBDRigidsSolver; +} + +USTRUCT() +struct CHAOSCACHING_API FObservedComponent +{ + GENERATED_BODY() + + FObservedComponent() + : CacheName(NAME_None) + , CacheMode(ECacheMode::None) + , StartMode(EStartMode::Timed) + , TimedDuration(0.0f) + { + ResetRuntimeData(); + } + + /** Unique name for the cache, used as a key into the cache collection */ + UPROPERTY(EditAnywhere, Category = "Caching") + FName CacheName; + + /** The component observed by this object for either playback or recording */ + UPROPERTY(EditAnywhere, Category = "Caching", meta = (UseComponentPicker, AllowAnyActor)) + FComponentReference ComponentRef; + + /** How to use the cache - playback or record */ + UPROPERTY(EditAnywhere, Category = "Caching") + ECacheMode CacheMode; + + /** + * How to trigger the cache record or playback, timed will start counting at BeginPlay, Triggered will begin + * counting from when the owning cache manager is requested to trigger the cache action + * @see AChaosCacheManager::TriggerObservedComponent + */ + UPROPERTY(EditAnywhere, Category = "Caching") + EStartMode StartMode; + + /** + * The time after BeginPlay or a call to AChaosCacheManager::TriggerObservedComponent to wait before beginning + * the playback or recording of the component + */ + UPROPERTY(EditAnywhere, Category = "Caching") + float TimedDuration; + + /** Prepare runtime tick data for a new run */ + void ResetRuntimeData(); + + /** Gets the component from the internal component ref */ + UPrimitiveComponent* GetComponent(); + UPrimitiveComponent* GetComponent() const; + +private: + friend class AChaosCacheManager; + + bool bTriggered; // Whether the observed component is active + Chaos::FReal AbsoluteTime; // Time since BeginPlay + Chaos::FReal TimeSinceTrigger; // Time since our trigger happened + UChaosCache* Cache; // Cache to play - picked up in BeginPlay on the manager. + FPlaybackTickRecord TickRecord; // Tick record to track where we are in playback +}; + +struct FPerSolverData +{ + /* Handles to solver events to push/pull cache data */ + FDelegateHandle PreSolveHandle; + FDelegateHandle PreBufferHandle; + FDelegateHandle PostSolveHandle; + + /** List of the tick records for each playback index, tracks where the last tick was */ + TArray PlaybackTickRecords; + /** List of indices for components tagged for playback - avoids iterating non playback components */ + TArray PlaybackIndices; + /** List of indices for components tagged for record - avoids iterating non record components */ + TArray RecordIndices; + /** List of particles in the solver that are pending a kinematic update to be pushed back to their owner */ + TArray*> PendingKinematicUpdates; +}; + +UCLASS() +class CHAOSCACHING_API AChaosCacheManager : public AActor +{ + GENERATED_BODY() + +public: + AChaosCacheManager(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** + * The Cache Collection asset to use for this observer. This can be used for playback and record simultaneously + * across multiple components depending on the settings for that component. + */ + UPROPERTY(EditInstanceOnly, Category = "Caching") + UChaosCacheCollection* CacheCollection; + + /** AActor interface */ + void TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction) override; + /** end AActor interface */ + + /** UObject interface */ +#if WITH_EDITOR + friend class IChaosCachingEditorPlugin; + void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + /** end UObject interface */ + + /** + * Sets the playback mode of every observed component to the specified mode + * @param InMode Mode to set + */ + UFUNCTION(BlueprintCallable, Category = "Caching") + void SetAllMode(ECacheMode InMode); + + /** + * Resets all components back to the world space transform they had when the cache for them was originally recorded + * if one is available + */ + UFUNCTION(BlueprintCallable, Category = "Caching") + void ResetAllComponentTransforms(); + + /** + * Resets the component at the specified index in the observed list back to the world space transform it had when the + * cache for it was originally recorded if one is available + * @param InIndex Index of the component to reset + */ + UFUNCTION(BlueprintCallable, Category = "Caching") + void ResetSingleTransform(int32 InIndex); + +#if WITH_EDITOR + /** + * Set the component at the specified index in the observed array to be the selected component in the outliner. + * This will also make that component's owner the selected actor in the outliner. + */ + void SelectComponent(int32 InIndex); +#endif + +protected: + /** AActor interface */ + void BeginPlay() override; + void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + /** End AActor interface */ + + /** Handles physics thread pre-solve (push kinematic data for components under playback) */ + void HandlePreSolve(Chaos::FReal InDt, Chaos::FPhysicsSolver* InSolver); + + /** Handles physics thread pre-buffer (mark dirty kinematic particles) */ + void HandlePreBuffer(Chaos::FReal InDt, Chaos::FPhysicsSolver* InSolver); + + /** Handles physics thread post-solve (record data for components under record) */ + void HandlePostSolve(Chaos::FReal InDt, Chaos::FPhysicsSolver* InSolver); + + /** + * Triggers a component to play or record. + * If the cache manager has an observed component entry for InComponent and it is a triggered entry + * this will begin the playback or record for that component, otherwise no action is taken. + * @param InComponent Component to trigger + */ + UFUNCTION(BlueprintCallable, Category = "Caching") + void TriggerComponent(UPrimitiveComponent* InComponent); + + /** + * Triggers a component to play or record. + * Searches the observed component list for an entry matching InCacheName and triggers the + * playback or recording of the linked observed component + * @param InCacheName Cache name to trigger + */ + UFUNCTION(BlueprintCallable, Category = "Caching") + void TriggerComponentByCache(FName InCacheName); + + /** Triggers the recording or playback of all observed components */ + UFUNCTION(BlueprintCallable, Category = "Caching") + void TriggerAll(); + + FObservedComponent* FindObservedComponent(UPrimitiveComponent* InComponent); + FObservedComponent& AddNewObservedComponent(UPrimitiveComponent* InComponent); + FObservedComponent& FindOrAddObservedComponent(UPrimitiveComponent* InComponent); + +private: + friend class UActorFactoryCacheManager; // Allows the actor factory to set up the observed list. See UActorFactoryCacheManager::PostSpawnActor + + using FTickObservedFunction = TUniqueFunction; + + /** + * Helper function to apply a callable to observed components if they've been triggered, all of the Dt/time + * bookkeeping is handled in one place + * @param InIndices Index list of the observed components to update + * @param InDt Delta for the tick + * @param InCallable Callable to fire if the observed component is active + */ + void TickObservedComponents(const TArray& InIndices, Chaos::FReal InDt, FTickObservedFunction InCallable); + + /** List of observed objects and their caches */ + UPROPERTY(EditAnywhere, Category = "Caching") + TArray ObservedComponents; + + /** 1-1 list of adapters for the observed components, populated on BeginPlay */ + TArray ActiveAdapters; + + /** List of particles returned by the adapter as requiring a kinematic update */ + TMap PerSolverData; + + /** Lists of currently open caches that need to be closed when complete */ + TArray> OpenRecordCaches; + TArray> OpenPlaybackCaches; +}; diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/ChaosCache.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/ChaosCache.h new file mode 100644 index 000000000000..bc3d4fef4584 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/ChaosCache.h @@ -0,0 +1,361 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Animation/AnimTypes.h" +#include "CacheEvents.h" +#include "Containers/Queue.h" +#include "Curves/RichCurve.h" +#include "Logging/LogMacros.h" + +#include + +#include "ChaosCache.generated.h" + +USTRUCT() +struct FParticleTransformTrack +{ + GENERATED_BODY() + + /** + * List of all the transforms this cache cares about, recorded from the simulated transforms of the particles + * observed by the adapter that created the cache + */ + UPROPERTY() + FRawAnimSequenceTrack RawTransformTrack; + + /** The offset from the beginning of the cache that holds this track that the track starts */ + UPROPERTY() + float BeginOffset; + + /** + * The above raw track is just the key data and doesn't know at which time those keys are placed, this is + * a list of the timestamps for each entry in TransformTrack + */ + UPROPERTY() + TArray KeyTimestamps; + + /** + * Evaluates the transform track at the specified time, returning the evaluated transform. When in between + * keys translations will be linearly interpolated and rotations spherically interpolated + * @param InCacheTime Absolute time from the beginning of the entire owning cache to evaluate. + */ + FTransform Evaluate(float InCacheTime) const; + + const int32 GetNumKeys() const; + const float GetDuration() const; + const float GetBeginTime() const; + const float GetEndTime() const; +}; + +USTRUCT() +struct FPerParticleCacheData +{ + GENERATED_BODY() + + UPROPERTY() + FParticleTransformTrack TransformData; + + /** + * Named curve data. This can be particle or other continuous curve data pushed by the adapter that created the + * cache. Any particle property outside of the transforms will be placed in this container with a suitable name for + * the property. Blueprints and adapters can add whatever data they need to this container. + */ + UPROPERTY() + TMap CurveData; +}; + +USTRUCT() +struct FCacheSpawnableTemplate +{ + GENERATED_BODY() + + UPROPERTY(VisibleAnywhere, Category = "Caching") + UObject* DuplicatedTemplate; + + UPROPERTY(VisibleAnywhere, Category = "Caching") + FTransform InitialTransform; +}; + +struct FPlaybackTickRecord +{ + FPlaybackTickRecord() + : CurrentDt(0.0f) + , LastTime(0.0f) + , SpaceTransform(FTransform::Identity) + { + } + + void Reset() + { + LastTime = 0.0f; + LastEventPerTrack.Reset(); + } + + float GetTime() const + { + return LastTime + CurrentDt; + } + + void SetDt(float NewDt) + { + CurrentDt = NewDt; + } + + void SetSpaceTransform(const FTransform& InTransform) + { + SpaceTransform = InTransform; + } + + const FTransform& GetSpaceTransform() const + { + return SpaceTransform; + } + +private: + friend class UChaosCache; + float CurrentDt; + float LastTime; + TMap LastEventPerTrack; + FTransform SpaceTransform; +}; + +struct FCacheEvaluationContext +{ + FCacheEvaluationContext() = delete; + explicit FCacheEvaluationContext(FPlaybackTickRecord& InRecord) + : TickRecord(InRecord) + , bEvaluateTransform(false) + , bEvaluateCurves(false) + , bEvaluateEvents(false) + { + } + + FPlaybackTickRecord& TickRecord; + bool bEvaluateTransform; + bool bEvaluateCurves; + bool bEvaluateEvents; + TArray EvaluationIndices; +}; + +struct FCacheEvaluationResult +{ +public: + float EvaluatedTime; + TArray ParticleIndices; + TArray Transform; + TArray> Curves; + TMap> Events; +}; + +struct FPendingParticleWrite +{ + int32 ParticleIndex; + FTransform PendingTransform; + TArray> PendingCurveData; +}; + +struct FPendingFrameWrite +{ + float Time; + TArray PendingParticleData; + TArray> PendingCurveData; + TMap PendingEvents; + + template + FCacheEventTrack& FindOrAddEventTrack(FName InName) + { + // All event data must derive FCacheEventBase to be safely stored generically + check(T::StaticStruct()->IsChildOf(FCacheEventBase::StaticStruct())); + + FCacheEventTrack* Existing = PendingEvents.Find(InName); + + return Existing ? *Existing : PendingEvents.Add(TTuple(InName, FCacheEventTrack(InName, T::StaticStruct()))); + } + + template + void PushEvent(FName InName, float InTime, const T& InEventStruct) + { + FCacheEventTrack& Track = FindOrAddEventTrack(InName); + Track.PushEvent(InTime, InEventStruct); + } +}; + +/** + * A type that only the Chaos Cache is capable of constructing, passed back from TryRecord and TryPlayback to ensure the user is permitted to use the cache + * This is also passed back to the EndPlayback and EndRecord functions to ensure that the caller has a valid token for the cache. + */ +class UChaosCache; +struct FCacheUserToken +{ + bool IsOpen() const + { + return bIsOpen && Owner; + } + + // Allow moving, invalidating the old token + FCacheUserToken(FCacheUserToken&& Other) + { + bIsOpen = Other.bIsOpen; + bIsRecord = Other.bIsRecord; + Owner = Other.Owner; + + Other.bIsOpen = false; + Other.bIsRecord = false; + Other.Owner = nullptr; + } + +private: + friend UChaosCache; + + bool bIsOpen; + bool bIsRecord; + UChaosCache* Owner; + + explicit FCacheUserToken(bool bInOpen, bool bInRecord, UChaosCache* InOwner) + : bIsOpen(bInOpen) + , bIsRecord(bInRecord) + , Owner(InOwner) + { + } + + FCacheUserToken() = delete; + FCacheUserToken(const FCacheUserToken&) = delete; + FCacheUserToken& operator=(const FCacheUserToken&) = delete; + FCacheUserToken& operator=(FCacheUserToken&&) = delete; +}; + +UCLASS() +class CHAOSCACHING_API UChaosCache : public UObject +{ + GENERATED_BODY() +public: + /** + * As we record post-simulate of physics, we're almost always taking data from a non-main thread (physics thread). + * Because of this we can't directly write into the cache, but instead into a pending frame queue that needs to be + * flushed on the main thread to write the pending data into the final storage. + */ + void FlushPendingFrames(); + + /** + * Reset and initialize a cache to make it ready to record the specified component + * @param InComponent Component to prepare the cache for + */ + FCacheUserToken BeginRecord(UPrimitiveComponent* InComponent, FGuid InAdapterId); + + /** + * End the recording session for the cache. At this point the cache is deemed to now contain + * all of the required data from the recording session and can then be post-processed and + * optimized which may involve key elimination and compression into a final format for runtime + * @param InOutToken The token that was given by BeginRecord + */ + void EndRecord(FCacheUserToken& InOutToken); + + /** + * Initialise the cache for playback, may not take any actual action on the cache but + * will provide the caller with a valid cache user token if it is safe to continue with playback + */ + FCacheUserToken BeginPlayback(); + + /** + * End a playback session for the cache. There can be multiple playback sessions open for a + * cache as long as there isn't a recording session. Calling EndPlayback with a valid open + * token will decrease the session count. + * @param InOutToken The token that was given by BeginRecord + */ + void EndPlayback(FCacheUserToken& InOutToken); + + /** + * Adds a new frame to process to a threadsafe queue for later processing in FlushPendingFrames + * @param InFrame New frame to accept, moved into the internal threadsafe queue + */ + void AddFrame_Concurrent(FPendingFrameWrite&& InFrame); + + /** + * Gets the recorded duration of the cache + */ + float GetDuration() const; + + /** + * Evaluate the cache with the specified parameters, returning the evaluated results + * @param InContext evaluation context + * @see FCacheEvaluationContext + */ + FCacheEvaluationResult Evaluate(const FCacheEvaluationContext& InContext); + + /** + * Initializes the spawnable template from a currently existing component so it can be spawned by the editor + * when a cache is dragged into the scene. + * @param InComponent Component to build the spawnable template from + */ + void BuildSpawnableFromComponent(UPrimitiveComponent* InComponent); + + /** + * Read access to the spawnable template stored in the cache + */ + const FCacheSpawnableTemplate& GetSpawnableTemplate() const; + + /** + * Evaluates a single particle from the tracks array + * @param InIndex Particle track index (unchecked, ensure valid before call) + * @param InTickRecord Tick record for this evaluation + * @param OutOptTransform Transform to fill, skipped if null + * @param OutOptCurves Curves to fill, skipped if null + */ + void EvaluateSingle(int32 InIndex, FPlaybackTickRecord& InTickRecord, FTransform* OutOptTransform, TMap* OutOptCurves); + + void EvaluateTransform(const FPerParticleCacheData& InData, float InTime, FTransform& OutTransform); + void EvaluateCurves(const FPerParticleCacheData& InData, float InTime, TMap& OutCurves); + void EvaluateEvents(FPlaybackTickRecord& InTickRecord, TMap>& OutEvents); + + UPROPERTY(VisibleAnywhere, Category = "Caching") + float RecordedDuration; + + UPROPERTY(VisibleAnywhere, Category = "Caching") + uint32 NumRecordedFrames; + + /** Maps a track index in the cache to the original particle index specified when recording */ + UPROPERTY() + TArray TrackToParticle; + + /** Per-particle data, includes transforms, velocities and other per-particle, per-frame data */ + UPROPERTY() + TArray ParticleTracks; + + /** Per component/cache curve data, any continuous data that isn't per-particle can be stored here */ + UPROPERTY() + TMap CurveData; + + template + FCacheEventTrack& FindOrAddEventTrack(FName InName) + { + // All event data must derive FCacheEventBase to be safely stored generically + check(T::StaticStruct().IsChildOf(FCacheEventBase::StaticStruct())); + + FCacheEventTrack* Existing = EventTracks.Find(InName); + + return Existing ? *Existing : EventTracks.Add(InName, FCacheEventTrack(InName, T::StaticStruct())); + } + +private: + friend class AChaosCacheManager; + + /** Timestamped generic event tracks */ + UPROPERTY() + TMap EventTracks; + + /** Spawn template for an actor that can play this cache */ + UPROPERTY(VisibleAnywhere, Category = "Caching") + FCacheSpawnableTemplate Spawnable; + + /** GUID identifier for the adapter that spawned this cache */ + UPROPERTY() + FGuid AdapterGuid; + + /** Pending writes from all threads to be consumed on the game thread, triggered by the recording cache manager */ + TQueue PendingWrites; + + /** Counts for current number of users, should only ever have one recorder, and if we do no playbacks */ + std::atomic CurrentRecordCount; + std::atomic CurrentPlaybackCount; +}; diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/ChaosCachingPlugin.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/ChaosCachingPlugin.h new file mode 100644 index 000000000000..c8f7a5d3038d --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCaching/Public/Chaos/ChaosCachingPlugin.h @@ -0,0 +1,40 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" +#include "Logging/LogMacros.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogChaosCache, Verbose, All); + +class IChaosCachingPlugin : public IModuleInterface +{ +public: + virtual void StartupModule(); + virtual void ShutdownModule(); + + /** + * Singleton-like access to this module's interface. This is just for convenience! + * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. + * + * @return Returns singleton instance, loading the module on demand if needed + */ + static inline IChaosCachingPlugin& Get() + { + return FModuleManager::LoadModuleChecked("ChaosCaching"); + } + + /** + * 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("ChaosCaching"); + } + +private: +}; diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/ChaosCachingEditor.Build.cs b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/ChaosCachingEditor.Build.cs new file mode 100644 index 000000000000..fd4e8b3398ac --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/ChaosCachingEditor.Build.cs @@ -0,0 +1,34 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class ChaosCachingEditor : ModuleRules + { + public ChaosCachingEditor(ReadOnlyTargetRules Target) : base(Target) + { + PrivateIncludePaths.Add("ChaosCachingEditor/Private"); + PublicIncludePaths.Add(ModuleDirectory + "/Public"); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Slate", + "SlateCore", + "InputCore", + "Engine", + "UnrealEd", + "EditorStyle", + "PropertyEditor", + "BlueprintGraph", + "ToolMenus", + "PhysicsCore", + "ChaosCaching" + }); + + SetupModulePhysicsSupport(Target); + PrivateDefinitions.Add("CHAOS_INCLUDE_LEVEL_1=1"); + } + } +} diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/ActorFactoryCacheManager.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/ActorFactoryCacheManager.cpp new file mode 100644 index 000000000000..07558eefa833 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/ActorFactoryCacheManager.cpp @@ -0,0 +1,80 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/ActorFactoryCacheManager.h" +#include "AssetData.h" +#include "Chaos/CacheCollection.h" +#include "Chaos/CacheManagerActor.h" +#include "Chaos/ChaosCache.h" +#include "Components/PrimitiveComponent.h" +#include "Kismet2/ComponentEditorUtils.h" + +#define LOCTEXT_NAMESPACE "CacheManagerActorFactory" + +UActorFactoryCacheManager::UActorFactoryCacheManager() +{ + DisplayName = LOCTEXT("DisplayName", "Chaos Cache Manager"); + NewActorClass = AChaosCacheManager::StaticClass(); + bUseSurfaceOrientation = false; +} + +bool UActorFactoryCacheManager::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) +{ + if(!AssetData.IsValid() || !AssetData.GetClass()->IsChildOf(UChaosCacheCollection::StaticClass())) + { + OutErrorMsg = LOCTEXT("NoCollection", "A valid cache collection must be specified."); + return false; + } + + return true; +} + +void UActorFactoryCacheManager::PostSpawnActor(UObject* Asset, AActor* NewActor) +{ + AChaosCacheManager* Manager = Cast(NewActor); + UChaosCacheCollection* Collection = Cast(Asset); + // The cachemanager exists now, start adding our spawnables + if(Manager && Collection) + { + if(UWorld* World = Manager->GetWorld()) + { + Manager->CacheCollection = Collection; + for(UChaosCache* Cache : Collection->GetCaches()) + { + if(!Cache) + { + continue; + } + + const FCacheSpawnableTemplate& Template = Cache->GetSpawnableTemplate(); + if(Template.DuplicatedTemplate) + { + check(Template.DuplicatedTemplate->GetClass()->IsChildOf(UPrimitiveComponent::StaticClass())); + + UPrimitiveComponent* NewComponent = CastChecked(StaticDuplicateObject(Template.DuplicatedTemplate, Manager)); + NewComponent->SetWorldTransform(Template.InitialTransform); + Manager->AddInstanceComponent(NewComponent); + NewComponent->RegisterComponent(); + + FObservedComponent& Observed = Manager->AddNewObservedComponent(NewComponent); + // AddNewObservedComponent will have given this a unique name as if it was going to build a new cache, override to the actual cache name + Observed.CacheName = Cache->GetFName(); + Observed.CacheMode = ECacheMode::Play; + Observed.StartMode = EStartMode::Timed; + Observed.TimedDuration = 0.0f; + } + } + } + } +} + +UObject* UActorFactoryCacheManager::GetAssetFromActorInstance(AActor* ActorInstance) +{ + if(AChaosCacheManager* Manager = Cast(ActorInstance)) + { + return Manager->CacheCollection; + } + + return nullptr; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/AssetTypeActions_ChaosCacheCollection.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/AssetTypeActions_ChaosCacheCollection.cpp new file mode 100644 index 000000000000..b2b2324fe012 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/AssetTypeActions_ChaosCacheCollection.cpp @@ -0,0 +1,46 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/AssetTypeActions_ChaosCacheCollection.h" + +#include "Chaos/CacheCollection.h" + +#define LOCTEXT_NAMESPACE "AssetActions_ChaosCacheCollection" + +FText FAssetTypeActions_ChaosCacheCollection::GetName() const +{ + return LOCTEXT("Name", "Chaos Cache Collection"); +} + +UClass* FAssetTypeActions_ChaosCacheCollection::GetSupportedClass() const +{ + return UChaosCacheCollection::StaticClass(); +} + +FColor FAssetTypeActions_ChaosCacheCollection::GetTypeColor() const +{ + return FColor(255, 127, 40); +} + +void FAssetTypeActions_ChaosCacheCollection::GetActions(const TArray& InObjects, + class FMenuBuilder& MenuBuilder) +{ +} + +void FAssetTypeActions_ChaosCacheCollection::OpenAssetEditor( + const TArray& InObjects, TSharedPtr EditWithinLevelEditor /*= TSharedPtr()*/) +{ + FSimpleAssetEditor::CreateEditor(EToolkitMode::Standalone, EditWithinLevelEditor, InObjects); +} + +uint32 FAssetTypeActions_ChaosCacheCollection::GetCategories() +{ + return EAssetTypeCategories::Physics; +} + +FText FAssetTypeActions_ChaosCacheCollection::GetAssetDescription(const struct FAssetData& AssetData) const +{ + return LOCTEXT("Description", "A collection of physically active component caches that can be used to record and " + "replay Chaos simulation recordings."); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheCollectionCustomization.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheCollectionCustomization.cpp new file mode 100644 index 000000000000..32bc2b196237 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheCollectionCustomization.cpp @@ -0,0 +1,186 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/CacheCollectionCustomization.h" +#include "Chaos/CacheCollection.h" +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailChildrenBuilder.h" +#include "PropertyCustomizationHelpers.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/SInlineEditableTextBlock.h" +#include "Chaos/ChaosCache.h" + +#define LOCTEXT_NAMESPACE "CacheCollectionDetails" + +TSharedRef FCacheCollectionDetails::MakeInstance() +{ + return MakeShareable(new FCacheCollectionDetails); +} + +void FCacheCollectionDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + TArray> Objects; + DetailBuilder.GetObjectsBeingCustomized(Objects); + + // In the simple asset editor we should only get one object + if(Objects.Num() != 1) + { + return; + } + + Item = Objects[0]; + + if(UChaosCacheCollection* Collection = GetSelectedCollection()) + { + NameEditBoxes.SetNum(Collection->Caches.Num()); + + TSharedRef CachesProp = DetailBuilder.GetProperty("Caches"); + IDetailCategoryBuilder& CacheCategory = DetailBuilder.EditCategory(CachesProp->GetDefaultCategoryName()); + + TSharedRef CacheArrayBuilder = MakeShareable(new FDetailArrayBuilder(CachesProp, true, false, true)); + CacheArrayBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FCacheCollectionDetails::GenerateCacheArrayElementWidget, &DetailBuilder)); + CacheCategory.AddCustomBuilder(CacheArrayBuilder); + } +} + +UChaosCacheCollection* FCacheCollectionDetails::GetSelectedCollection() +{ + if(UObject* ItemObj = Item.Get()) + { + if(UChaosCacheCollection* Collection = Cast(ItemObj)) + { + return Collection; + } + } + + return nullptr; +} + +const UChaosCacheCollection* FCacheCollectionDetails::GetSelectedCollection() const +{ + if(const UObject* ItemObj = Item.Get()) + { + if(const UChaosCacheCollection* Collection = Cast(ItemObj)) + { + return Collection; + } + } + + return nullptr; +} + +FText FCacheCollectionDetails::GetCacheName(int32 InCacheIndex) const +{ + if(const UChaosCacheCollection* Collection = GetSelectedCollection()) + { + if(Collection->Caches.IsValidIndex(InCacheIndex) && Collection->Caches[InCacheIndex]) + { + return FText::FromString(Collection->Caches[InCacheIndex]->GetName()); + } + } + + return LOCTEXT("InvalidObject", "Invalid"); +} + +void FCacheCollectionDetails::OnDeleteCache(int32 InArrayIndex, IDetailLayoutBuilder* InLayoutBuilder) +{ + if(UChaosCacheCollection* Collection = GetSelectedCollection()) + { + if(Collection->Caches.IsValidIndex(InArrayIndex) && Collection->Caches[InArrayIndex]) + { + Collection->Caches.RemoveAt(InArrayIndex); + InLayoutBuilder->ForceRefreshDetails(); + } + } +} + +bool IsValidName(UChaosCacheCollection* Collection, FName InName) +{ + if(Collection) + { + UObject* Existing = StaticFindObject(UChaosCache::StaticClass(), Collection, *InName.ToString()); + return !Existing; + } + return false; +} + +void FCacheCollectionDetails::OnChangeCacheName(const FText& InNewName, int32 InIndex) +{ + UChaosCacheCollection* Collection = GetSelectedCollection(); + + if(!Collection) + { + return; + } + + TSharedPtr TextBox = NameEditBoxes[InIndex]; + if(!IsValidName(Collection, FName(InNewName.ToString()))) + { + TextBox->SetError(LOCTEXT("InvalidNameError", "Invalid Cache Name")); + } + else + { + TextBox->SetError(TEXT("")); + } +} + +void FCacheCollectionDetails::OnCommitCacheName(const FText& InNewName, ETextCommit::Type InTextCommit, int32 InIndex) +{ + if(InTextCommit != ETextCommit::OnEnter) + { + // Focus was taken away, don't perform the edit + return; + } + + if(UChaosCacheCollection* Collection = GetSelectedCollection()) + { + FName NewName(*InNewName.ToString()); + if(IsValidName(Collection, NewName)) + { + UChaosCache* CurrCache = Collection->Caches[InIndex]; + CurrCache->Rename(*NewName.ToString()); + } + } +} + +void FCacheCollectionDetails::GenerateCacheArrayElementWidget(TSharedRef InPropertyHandle, int32 ArrayIndex, IDetailChildrenBuilder& ChildrenBuilder, IDetailLayoutBuilder* DetailLayout) +{ + IDetailPropertyRow& PropRow = ChildrenBuilder.AddProperty(InPropertyHandle); + PropRow.ShowPropertyButtons(false); + PropRow.OverrideResetToDefault(FResetToDefaultOverride::Hide()); + + FDetailWidgetRow& WidgetRow = PropRow.CustomWidget(true); + + WidgetRow.NameContent() + [ + InPropertyHandle->CreatePropertyNameWidget() + ]; + + WidgetRow.ValueContent() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Fill) + .AutoWidth() + [ + SAssignNew(NameEditBoxes[ArrayIndex], SEditableTextBox) + .Text(this, &FCacheCollectionDetails::GetCacheName, ArrayIndex) + .OnTextChanged(this, &FCacheCollectionDetails::OnChangeCacheName, ArrayIndex) + .OnTextCommitted(this, &FCacheCollectionDetails::OnCommitCacheName, ArrayIndex) + ] + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Fill) + .Padding(5,0,0,0) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateSP(this, &FCacheCollectionDetails::OnDeleteCache, ArrayIndex, DetailLayout)) + ] + ]; +} + + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheCollectionFactory.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheCollectionFactory.cpp new file mode 100644 index 000000000000..c25b51d41ae5 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheCollectionFactory.cpp @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/CacheCollectionFactory.h" + +#include "Chaos/CacheCollection.h" + +UCacheCollectionFactory::UCacheCollectionFactory() +{ + SupportedClass = UChaosCacheCollection::StaticClass(); +} + +bool UCacheCollectionFactory::CanCreateNew() const +{ + return true; +} + +bool UCacheCollectionFactory::FactoryCanImport(const FString& Filename) +{ + return false; +} + +UObject* UCacheCollectionFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, + UObject* Context, FFeedbackContext* Warn) +{ + return NewObject(InParent, InClass, InName, Flags); +} + +bool UCacheCollectionFactory::ShouldShowInNewMenu() const +{ + return true; +} + +bool UCacheCollectionFactory::ConfigureProperties() +{ + return true; +} diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheEditorCommands.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheEditorCommands.cpp new file mode 100644 index 000000000000..e3414e708233 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheEditorCommands.cpp @@ -0,0 +1,14 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/CacheEditorCommands.h" + +#define LOCTEXT_NAMESPACE "CacheEditorCommands" + +void FCachingEditorCommands::RegisterCommands() +{ + UI_COMMAND(CreateCacheManager, "Create Cache Manager", "Adds a cache manager to observe compatible components in the selection set.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(SetManagerRecordAll, "Set Record All", "Sets selected cache managers to record all of their observed components.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(SetManagerPlayAll, "Set Play All", "Sets selected cache managers to play all of their observed components.", EUserInterfaceActionType::Button, FInputChord()); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheManagerCustomization.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheManagerCustomization.cpp new file mode 100644 index 000000000000..25d3ff7041ed --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/CacheManagerCustomization.cpp @@ -0,0 +1,225 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/CacheManagerCustomization.h" +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "PropertyCustomizationHelpers.h" +#include "Widgets/Input/SButton.h" +#include "Chaos/CacheManagerActor.h" +#include "IDetailChildrenBuilder.h" + +#define LOCTEXT_NAMESPACE "CacheManagerDetails" + +FReply OnClickSetAllButton(TArray Managers, ECacheMode NewMode) +{ + for(AChaosCacheManager* Manager : Managers) + { + if(Manager) + { + Manager->SetAllMode(NewMode); + } + } + + return FReply::Handled(); +} + +FReply OnClickResetTransforms(TArray Managers) +{ + for(AChaosCacheManager* Manager : Managers) + { + if(Manager) + { + Manager->ResetAllComponentTransforms(); + } + } + + return FReply::Handled(); +} + +TSharedRef FCacheManagerDetails::MakeInstance() +{ + return MakeShareable(new FCacheManagerDetails); +} + +void FCacheManagerDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + TArray> Selected = DetailBuilder.GetSelectedObjects(); + TArray CacheManagersInSelection; + + for(TWeakObjectPtr Ptr : Selected) + { + if(AChaosCacheManager* Manager = Cast(Ptr.Get())) + { + CacheManagersInSelection.Add(Manager); + } + } + + if(CacheManagersInSelection.Num() == 0) + { + return; + } + + IDetailCategoryBuilder& CachingCategory = DetailBuilder.EditCategory("Caching"); + FDetailWidgetRow& SetAllRow = CachingCategory.AddCustomRow(FText::GetEmpty()); + + SetAllRow.NameContent() + [ + SNew(STextBlock) + .Font(DetailBuilder.GetDetailFont()) + .Text(LOCTEXT("SetAllLabel", "Set All")) + ]; + + SetAllRow.ValueContent() + .MinDesiredWidth(300.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Static(&OnClickSetAllButton, CacheManagersInSelection, ECacheMode::Record) + [ + SNew(STextBlock) + .Text(LOCTEXT("SetAllRecord", "Record")) + ] + ] + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Static(&OnClickSetAllButton, CacheManagersInSelection, ECacheMode::Play) + [ + SNew(STextBlock) + .Text(LOCTEXT("SetAllPlay", "Play")) + ] + ] + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Static(&OnClickSetAllButton, CacheManagersInSelection, ECacheMode::None) + [ + SNew(STextBlock) + .Text(LOCTEXT("SetAllNone", "None")) + ] + ] + ]; + + FDetailWidgetRow& ResetPositionsRow = CachingCategory.AddCustomRow(FText::GetEmpty()); + + ResetPositionsRow.ValueContent() + .MinDesiredWidth(300.0f) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Static(&OnClickResetTransforms, CacheManagersInSelection) + [ + SNew(STextBlock) + .Text(LOCTEXT("ResetPositions", "Reset All Component Transforms")) + ] + ]; +} + +void FCacheManagerDetails::GenerateCacheArrayElementWidget(TSharedRef PropertyHandle, int32 ArrayIndex, IDetailChildrenBuilder& ChildrenBuilder, IDetailLayoutBuilder* DetailLayout) +{ +} + +TSharedRef FObservedComponentDetails::MakeInstance() +{ + return MakeShareable(new FObservedComponentDetails); +} + +void FObservedComponentDetails::CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) +{ + HeaderRow + .NameContent() + [ + PropertyHandle->CreatePropertyNameWidget() + ] + .ValueContent() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + PropertyHandle->CreatePropertyValueWidget(/*bDisplayDefaultPropertyButtons =*/false) + ]; +} + +FReply OnClickResetSingleTransform(AChaosCacheManager* InManager, int32 InIndex) +{ + if(InManager) + { + InManager->ResetSingleTransform(InIndex); + } + + return FReply::Handled(); +} + +FReply OnClickSelectComponent(AChaosCacheManager* InManager, int32 InIndex) +{ + if(InManager) + { + InManager->SelectComponent(InIndex); + } + + return FReply::Handled(); +} + +void FObservedComponentDetails::CustomizeChildren(TSharedRef PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) +{ + uint32 NumChildren = 0; + PropertyHandle->GetNumChildren(NumChildren); + + for(uint32 ChildNum = 0; ChildNum < NumChildren; ++ChildNum) + { + ChildBuilder.AddProperty(PropertyHandle->GetChildHandle(ChildNum).ToSharedRef()); + } + + TArray> SelectedObjects = ChildBuilder.GetParentCategory().GetParentLayout().GetSelectedObjects(); + if(SelectedObjects.Num() == 1) + { + if(AChaosCacheManager* SelectedManager = Cast(SelectedObjects[0].Get())) + { + const int32 ArrayIndex = PropertyHandle->GetIndexInArray(); + + ChildBuilder.AddCustomRow(FText::GetEmpty()) + .ValueContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(0.0f, 0.0f, 0.0f, 3.0f) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Static(&OnClickResetSingleTransform, SelectedManager, ArrayIndex) + [ + SNew(STextBlock) + .Text(LOCTEXT("ItemResetTransform", "Reset Transform")) + ] + ] + + SVerticalBox::Slot() + .Padding(0.0f, 0.0f, 0.0f, 3.0f) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Static(&OnClickSelectComponent, SelectedManager, ArrayIndex) + [ + SNew(STextBlock) + .Text(LOCTEXT("ItemSelect", "Select Component")) + ] + ] + ]; + } + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/ChaosCachingEditorPlugin.cpp b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/ChaosCachingEditorPlugin.cpp new file mode 100644 index 000000000000..a7d13730ce88 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Private/Chaos/ChaosCachingEditorPlugin.cpp @@ -0,0 +1,284 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Chaos/ChaosCachingEditorPlugin.h" + +#include "AssetToolsModule.h" +#include "Chaos/Adapters/CacheAdapter.h" +#include "Chaos/AssetTypeActions_ChaosCacheCollection.h" +#include "Chaos/CacheCollectionCustomization.h" +#include "Chaos/CacheEditorCommands.h" +#include "Chaos/CacheManagerActor.h" +#include "Chaos/CacheManagerCustomization.h" +#include "CoreMinimal.h" +#include "Engine/Selection.h" +#include "Kismet2/ComponentEditorUtils.h" +#include "Modules/ModuleManager.h" +#include "PropertyEditorModule.h" +#include "ToolMenus.h" + +IMPLEMENT_MODULE(IChaosCachingEditorPlugin, ChaosCachingEditor) + +#define LOCTEXT_NAMESPACE "CacheEditorPlugin" + +void IChaosCachingEditorPlugin::StartupModule() +{ + AssetTypeActions_ChaosCacheCollection = new FAssetTypeActions_ChaosCacheCollection(); + + FAssetToolsModule& AssetToolsModule = FAssetToolsModule::GetModule(); + IAssetTools& AssetTools = AssetToolsModule.Get(); + AssetTools.RegisterAssetTypeActions(MakeShareable(AssetTypeActions_ChaosCacheCollection)); + + FCachingEditorCommands::Register(); + + StartupHandle = UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &IChaosCachingEditorPlugin::RegisterMenus)); + + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.RegisterCustomClassLayout("ChaosCacheCollection", FOnGetDetailCustomizationInstance::CreateStatic(&FCacheCollectionDetails::MakeInstance)); + PropertyModule.RegisterCustomClassLayout("ChaosCacheManager", FOnGetDetailCustomizationInstance::CreateStatic(&FCacheManagerDetails::MakeInstance)); + PropertyModule.RegisterCustomPropertyTypeLayout("ObservedComponent", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FObservedComponentDetails::MakeInstance)); +} + +void IChaosCachingEditorPlugin::ShutdownModule() +{ + if(UObjectInitialized()) + { + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.UnregisterCustomPropertyTypeLayout("ObservedComponent"); + PropertyModule.UnregisterCustomClassLayout("ChaosCacheManager"); + PropertyModule.UnregisterCustomClassLayout("ChaosCacheCollection"); + + UToolMenus::UnRegisterStartupCallback(StartupHandle); + + FCachingEditorCommands::Unregister(); + + FAssetToolsModule& AssetToolsModule = FAssetToolsModule::GetModule(); + IAssetTools& AssetTools = AssetToolsModule.Get(); + + AssetTools.UnregisterAssetTypeActions(AssetTypeActions_ChaosCacheCollection->AsShared()); + } +} + +bool IsCreateCacheManagerVisible(); +bool IsSetAllRecordVisible(); +bool IsSetAllPlayVisible(); + +void IChaosCachingEditorPlugin::RegisterMenus() +{ + FToolMenuOwnerScoped OwnerScope(this); + + UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.ActorContextMenu"); + + FToolMenuSection* Section = Menu->FindSection("Chaos"); + if(!Section) + { + Section = &Menu->AddSection("Chaos", LOCTEXT("ChaosSectionLabel", "Chaos")); + } + + Section->InitSection("Chaos", LOCTEXT("ChaosSectionLabel", "Chaos"), FToolMenuInsert()); + + Section->AddSubMenu("CachingSub", + LOCTEXT("SubMenu_Caching", "Caching"), + LOCTEXT("Tooltip_Caching", "Options for manipulating cache managers and their observed components"), + FNewToolMenuDelegate::CreateLambda([this](UToolMenu* InMenu) { + FToolMenuSection& CacheSubMenuSection = InMenu->AddSection("Caching"); + RegisterCachingSubMenu(InMenu, &CacheSubMenuSection); + }), + FUIAction(FExecuteAction(), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateLambda([]() -> bool + { + return IsCreateCacheManagerVisible() || IsSetAllPlayVisible() || IsSetAllRecordVisible(); + })), + EUserInterfaceActionType::Button); +} + +void IChaosCachingEditorPlugin::RegisterCachingSubMenu(UToolMenu* InMenu, FToolMenuSection* InSection) +{ + InSection->AddMenuEntry("CreateCacheManager", + LOCTEXT("MenuItem_CreateCacheManager", "Create Cache Manager"), + LOCTEXT("MenuItem_CreateCacheManager_ToolTip", "Adds a cache manager to observe compatible components in the selection set."), + FSlateIcon(), + FUIAction(FExecuteAction::CreateRaw(this, &IChaosCachingEditorPlugin::OnCreateCacheManager), + FCanExecuteAction(), + FIsActionChecked(), + FIsActionButtonVisible::CreateStatic(&IsCreateCacheManagerVisible))); + + InSection->AddMenuEntry("SetRecordAll", + LOCTEXT("MenuItem_SetRecordAll", "Set All Record"), + LOCTEXT("MenuItem_SetRecordAll_ToolTip", "Sets selected cache managers to record all of their observed components."), + FSlateIcon(), + FUIAction(FExecuteAction::CreateRaw(this, &IChaosCachingEditorPlugin::OnSetAllRecord), + FCanExecuteAction(), + FIsActionChecked(), + FIsActionButtonVisible::CreateStatic(&IsSetAllRecordVisible))); + + InSection->AddMenuEntry("SetPlayAll", + LOCTEXT("MenuItem_SetPlayAll", "Set All Play"), + LOCTEXT("MenuItem_SetPlayAll_ToolTip", "Sets selected cache managers to playback all of their observed components."), + FSlateIcon(), + FUIAction(FExecuteAction::CreateRaw(this, &IChaosCachingEditorPlugin::OnSetAllPlay), + FCanExecuteAction(), + FIsActionChecked(), + FIsActionButtonVisible::CreateStatic(&IsSetAllPlayVisible))); +} + +void IChaosCachingEditorPlugin::OnCreateCacheManager() +{ + auto SpawnManager = [](UWorld* InWorld) -> AChaosCacheManager* { + FActorSpawnParameters Params; + return InWorld->SpawnActor(); + }; + + AChaosCacheManager* Manager = nullptr; + + // Get the implementation of our adapters for identifying compatible components + IModularFeatures& ModularFeatures = IModularFeatures::Get(); + TArray Adapters = ModularFeatures.GetModularFeatureImplementations(Chaos::FComponentCacheAdapter::FeatureName); + + USelection* SelectedActors = GEditor->GetSelectedActors(); + + TArray Actors; + SelectedActors->GetSelectedObjects(Actors); + + TArray ComponentArray; + for(AActor* Actor : Actors) + { + ComponentArray.Reset(); + Actor->GetComponents(ComponentArray); + + for(UActorComponent* Component : ComponentArray) + { + if(Component->CreationMethod == EComponentCreationMethod::UserConstructionScript) + { + // Can't hold references to UCS created components (See FComponentEditorUtils::MakeComponentReference) + continue; + } + + if(UPrimitiveComponent* PrimitiveComp = Cast(Component)) + { + Chaos::FComponentCacheAdapter* BestFitAdapter = Chaos::FAdapterUtil::GetBestAdapterForClass(PrimitiveComp->GetClass()); + + // Can't be observed + if(!BestFitAdapter) + { + continue; + } + + // If we get here without a manager, lazy spawn one + if(!Manager) + { + Manager = SpawnManager(Component->GetWorld()); + } + + check(Manager); + + FObservedComponent* Existing = Manager->ObservedComponents.FindByPredicate([PrimitiveComp](const FObservedComponent& InItem) { + return InItem.GetComponent() == PrimitiveComp; + }); + + if(!Existing) + { + FObservedComponent& NewEntry = Manager->AddNewObservedComponent(PrimitiveComp); + } + } + } + } +} + +bool IsCreateCacheManagerVisible() +{ + IModularFeatures& ModularFeatures = IModularFeatures::Get(); + TArray Adapters = ModularFeatures.GetModularFeatureImplementations(Chaos::FComponentCacheAdapter::FeatureName); + + USelection* SelectedActors = GEditor->GetSelectedActors(); + + TArray Actors; + SelectedActors->GetSelectedObjects(Actors); + + TArray ComponentArray; + for(AActor* Actor : Actors) + { + ComponentArray.Reset(); + Actor->GetComponents(ComponentArray); + + for(UActorComponent* Component : ComponentArray) + { + if(Component->CreationMethod == EComponentCreationMethod::UserConstructionScript) + { + // Can't hold references to UCS created components (See FComponentEditorUtils::MakeComponentReference) + continue; + } + + if(UPrimitiveComponent* PrimitiveComp = Cast(Component)) + { + Chaos::FComponentCacheAdapter* BestFitAdapter = Chaos::FAdapterUtil::GetBestAdapterForClass(PrimitiveComp->GetClass()); + + // Can't be observed + if(!BestFitAdapter) + { + continue; + } + + // We have an adapter which means it's possible to observe this component so the option to create a manager should be visible + return true; + } + } + } + + return false; +} + +void IChaosCachingEditorPlugin::OnSetAllPlay() +{ + USelection* SelectedActors = GEditor->GetSelectedActors(); + + TArray CacheManagers; + SelectedActors->GetSelectedObjects(CacheManagers); + + for(AChaosCacheManager* Manager : CacheManagers) + { + if(Manager) + { + Manager->SetAllMode(ECacheMode::Play); + } + } +} + +template +bool SelectionContains() +{ + static_assert(TIsDerivedFrom::Value, "Must be an actor type to be a selected actor."); + + USelection* SelectedActors = GEditor->GetSelectedActors(); + + TArray Items; + SelectedActors->GetSelectedObjects(Items); + + return Items.Num() > 0; +} + +bool IsSetAllPlayVisible() +{ + return SelectionContains(); +} + +void IChaosCachingEditorPlugin::OnSetAllRecord() +{ + USelection* SelectedActors = GEditor->GetSelectedActors(); + + TArray CacheManagers; + SelectedActors->GetSelectedObjects(CacheManagers); + + for(AChaosCacheManager* Manager : CacheManagers) + { + if(Manager) + { + Manager->SetAllMode(ECacheMode::Record); + } + } +} + +bool IsSetAllRecordVisible() +{ + return SelectionContains(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/ActorFactoryCacheManager.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/ActorFactoryCacheManager.h new file mode 100644 index 000000000000..3fb4795d7612 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/ActorFactoryCacheManager.h @@ -0,0 +1,26 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "ActorFactories/ActorFactory.h" + +#include "ActorFactoryCacheManager.generated.h" + +class AActor; +struct FAssetData; + +UCLASS() +class CHAOSCACHINGEDITOR_API UActorFactoryCacheManager : public UActorFactory +{ + GENERATED_BODY() + + UActorFactoryCacheManager(); + + //~ Begin UActorFactory Interface + virtual bool CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) override; + virtual void PostSpawnActor(UObject* Asset, AActor* NewActor) override; + virtual UObject* GetAssetFromActorInstance(AActor* ActorInstance) override; + //~ End UActorFactory Interface +}; diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/AssetTypeActions_ChaosCacheCollection.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/AssetTypeActions_ChaosCacheCollection.h new file mode 100644 index 000000000000..507e374b3111 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/AssetTypeActions_ChaosCacheCollection.h @@ -0,0 +1,17 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "AssetTypeActions_Base.h" + +class FAssetTypeActions_ChaosCacheCollection : public FAssetTypeActions_Base +{ +public: + virtual FText GetName() const override; + virtual UClass* GetSupportedClass() const override; + virtual FColor GetTypeColor() const override; + virtual void GetActions(const TArray& InObjects, class FMenuBuilder& MenuBuilder) override; + virtual void OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()) override; + virtual uint32 GetCategories() override; + virtual FText GetAssetDescription(const struct FAssetData& AssetData) const override; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheCollectionCustomization.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheCollectionCustomization.h new file mode 100644 index 000000000000..4dadc1412ce7 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheCollectionCustomization.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IDetailCustomization.h" +#include "Templates/SharedPointer.h" +#include "UObject/WeakObjectPtrTemplates.h" +#include "Types/SlateEnums.h" + +class IDetailLayoutBuilder; +class IPropertyHandle; +class IDetailChildrenBuilder; + +class UObject; +class UChaosCacheCollection; + +class SEditableTextBox; + +class FCacheCollectionDetails : public IDetailCustomization +{ +public: + static TSharedRef MakeInstance(); + void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + +private: + + UChaosCacheCollection* GetSelectedCollection(); + const UChaosCacheCollection* GetSelectedCollection() const; + FText GetCacheName(int32 InCacheIndex) const; + void OnDeleteCache(int32 InArrayIndex, IDetailLayoutBuilder* InLayoutBuilder); + + void OnChangeCacheName(const FText& InNewName, int32 InIndex); + void OnCommitCacheName(const FText& InNewName, ETextCommit::Type InTextCommit, int32 InIndex); + + void GenerateCacheArrayElementWidget(TSharedRef PropertyHandle, int32 ArrayIndex, IDetailChildrenBuilder& ChildrenBuilder, IDetailLayoutBuilder* DetailLayout); + TWeakObjectPtr Item; + + TArray> NameEditBoxes; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheCollectionFactory.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheCollectionFactory.h new file mode 100644 index 000000000000..7fc6e84297da --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheCollectionFactory.h @@ -0,0 +1,32 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Factories/Factory.h" + +#include "CacheCollectionFactory.generated.h" + +UCLASS() +class UCacheCollectionFactory : public UFactory +{ + GENERATED_BODY() +public: + + UCacheCollectionFactory(); + + /** UFactory Interface */ + bool CanCreateNew() const override; + bool FactoryCanImport(const FString& Filename) override; + UObject* FactoryCreateNew(UClass* InClass, + UObject* InParent, + FName InName, + EObjectFlags Flags, + UObject* Context, + FFeedbackContext* Warn) override; + + bool ShouldShowInNewMenu() const override; + bool ConfigureProperties() override; + /** End UFactory Interface */ + +private: +}; diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheEditorCommands.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheEditorCommands.h new file mode 100644 index 000000000000..0f4582efdad8 --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheEditorCommands.h @@ -0,0 +1,25 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EditorStyleSet.h" +#include "Framework/Commands/Commands.h" + +class FCachingEditorCommands : public TCommands +{ +public: + FCachingEditorCommands() + : TCommands( + TEXT("ChaosCacheEditor"), NSLOCTEXT("ChaosCacheEditorCommands", "ContextDesc", "Chaos Cache"), NAME_None, FEditorStyle::GetStyleSetName()) + { + } + + virtual ~FCachingEditorCommands() = default; + + CHAOSCACHINGEDITOR_API virtual void RegisterCommands(); + + TSharedPtr CreateCacheManager; + TSharedPtr SetManagerRecordAll; + TSharedPtr SetManagerPlayAll; +}; diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAHandleDetailCustomization.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheManagerCustomization.h similarity index 50% rename from Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAHandleDetailCustomization.h rename to Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheManagerCustomization.h index d6d199f28b8b..7a3a194643e3 100644 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAHandleDetailCustomization.h +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/CacheManagerCustomization.h @@ -2,31 +2,29 @@ #pragma once -#include "CoreMinimal.h" -#include "Widgets/SWidget.h" +#include "IDetailCustomization.h" +#include "Templates/SharedPointer.h" #include "IPropertyTypeCustomization.h" -#include "PropertyHandle.h" -class FDetailWidgetRow; +class IDetailLayoutBuilder; +class IPropertyHandle; class IDetailChildrenBuilder; -class FLSAHandleDetailCustomization : public IPropertyTypeCustomization +class FCacheManagerDetails : public IDetailCustomization { public: - - using ThisClass = FLSAHandleDetailCustomization; - - //~ Begin IPropertyTypeCustomization Interface. - virtual void CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override; - virtual void CustomizeChildren(TSharedRef PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) override; - //~ End IPropertyTypeCustomization Interface. - - static TSharedRef MakeInstance(); + static TSharedRef MakeInstance(); + void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; private: - FName GetSelectedHandle(bool& bMultipleValues) const; - void OnHandleSelectionChanged(FName NewHandle); + void GenerateCacheArrayElementWidget(TSharedRef PropertyHandle, int32 ArrayIndex, IDetailChildrenBuilder& ChildrenBuilder, IDetailLayoutBuilder* DetailLayout); +}; - TSharedPtr HandleProperty; +class FObservedComponentDetails : public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance(); + virtual void CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override; + virtual void CustomizeChildren(TSharedRef PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) override; }; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/ChaosCachingEditorPlugin.h b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/ChaosCachingEditorPlugin.h new file mode 100644 index 000000000000..bcd7455b722d --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosCaching/Source/ChaosCachingEditor/Public/Chaos/ChaosCachingEditorPlugin.h @@ -0,0 +1,55 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" +#include "Delegates/IDelegateInstance.h" + +class FAssetTypeActions_ChaosCacheCollection; + +class UToolMenu; +struct FToolMenuSection; + +/** + * The public interface to this module + */ +class IChaosCachingEditorPlugin : public IModuleInterface +{ + TArray EditorCommands; + +public: + virtual void StartupModule(); + virtual void ShutdownModule(); + + /** + * Singleton-like access to this module's interface. This is just for convenience! + * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. + * + * @return Returns singleton instance, loading the module on demand if needed + */ + static inline IChaosCachingEditorPlugin& Get() + { + return FModuleManager::LoadModuleChecked("ChaosCachingEditorPlugin"); + } + + /** + * 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("ChaosCachingEditorPlugin"); } + +private: + + void RegisterMenus(); + void RegisterCachingSubMenu(UToolMenu* InMenu, FToolMenuSection* InSection); + + void OnCreateCacheManager(); + void OnSetAllPlay(); + void OnSetAllRecord(); + + FAssetTypeActions_ChaosCacheCollection* AssetTypeActions_ChaosCacheCollection; + FDelegateHandle StartupHandle; +}; diff --git a/Engine/Plugins/Experimental/HairStrands/Shaders/Private/NiagaraDataInterfaceFieldSystem.ush b/Engine/Plugins/Experimental/ChaosNiagara/Shaders/NiagaraDataInterfaceFieldSystem.ush similarity index 86% rename from Engine/Plugins/Experimental/HairStrands/Shaders/Private/NiagaraDataInterfaceFieldSystem.ush rename to Engine/Plugins/Experimental/ChaosNiagara/Shaders/NiagaraDataInterfaceFieldSystem.ush index 3db5858bd06e..c1c78d56d22d 100644 --- a/Engine/Plugins/Experimental/HairStrands/Shaders/Private/NiagaraDataInterfaceFieldSystem.ush +++ b/Engine/Plugins/Experimental/ChaosNiagara/Shaders/NiagaraDataInterfaceFieldSystem.ush @@ -11,23 +11,52 @@ NiagaraDataInterfaceFieldSystem.ush * ----------------------------------------------------------------- */ + #define USE_BAKED_FIELD 1 + struct FDIFieldSystemContext { Buffer FieldNodesParamsBuffer; Buffer FieldNodesOffsetsBuffer; Buffer FieldCommandsNodesBuffer; + Texture3D VectorFieldTexture; + SamplerState VectorFieldSampler; + Texture3D ScalarFieldTexture; + SamplerState ScalarFieldSampler; + Texture3D IntegerFieldTexture; + SamplerState IntegerFieldSampler; + int3 FieldDimensions; + float3 MinBounds; + float3 MaxBounds; }; #define DIFieldSystem_DECLARE_CONSTANTS(NAME)\ Buffer FieldNodesParamsBuffer_##NAME;\ Buffer FieldNodesOffsetsBuffer_##NAME;\ Buffer FieldCommandsNodesBuffer_##NAME;\ +Texture3D VectorFieldTexture_##NAME;\ +SamplerState VectorFieldSampler_##NAME;\ +Texture3D ScalarFieldTexture_##NAME;\ +SamplerState ScalarFieldSampler_##NAME;\ +Texture3D IntegerFieldTexture_##NAME;\ +SamplerState IntegerFieldSampler_##NAME;\ +int3 FieldDimensions_##NAME;\ +float3 MinBounds_##NAME;\ +float3 MaxBounds_##NAME;\ #define DIFieldSystem_MAKE_CONTEXT(NAME)\ FDIFieldSystemContext DIContext; \ DIContext.FieldNodesParamsBuffer = FieldNodesParamsBuffer_##NAME;\ DIContext.FieldNodesOffsetsBuffer = FieldNodesOffsetsBuffer_##NAME;\ DIContext.FieldCommandsNodesBuffer = FieldCommandsNodesBuffer_##NAME;\ +DIContext.VectorFieldTexture = VectorFieldTexture_##NAME;\ +DIContext.VectorFieldSampler = VectorFieldSampler_##NAME;\ +DIContext.ScalarFieldTexture = ScalarFieldTexture_##NAME;\ +DIContext.ScalarFieldSampler = ScalarFieldSampler_##NAME;\ +DIContext.IntegerFieldTexture = IntegerFieldTexture_##NAME;\ +DIContext.IntegerFieldSampler = IntegerFieldSampler_##NAME;\ +DIContext.FieldDimensions = FieldDimensions_##NAME;\ +DIContext.MinBounds = MinBounds_##NAME;\ +DIContext.MaxBounds = MaxBounds_##NAME;\ /* ----------------------------------------------------------------- * Field System defines @@ -36,6 +65,10 @@ DIContext.FieldCommandsNodesBuffer = FieldCommandsNodesBuffer_##NAME;\ #define MAX_DATAS 64 +#define NUM_VECTOR_TYPES 4 +#define NUM_SCALAR_TYPES 0 +#define NUM_INTEGER_TYPES 0 + #define NONE_TYPE 0 #define RESULTS_TYPE 1 #define INTEGER_TYPE 2 @@ -51,7 +84,7 @@ DIContext.FieldCommandsNodesBuffer = FieldCommandsNodesBuffer_##NAME;\ #define ANGULAR_VELOCITY 6 #define ANGULAR_TORQUE 7 #define INTERNAL_CLUSTER_STRAIN 8 -#define DISTANCE_THRESHOLD 9 +#define DISABLE_THRESHOLD 9 #define SLEEPING_THRESHOLD 10 #define POSITION_STATIC 11 #define POSITION_ANIMATED 12 @@ -60,6 +93,25 @@ DIContext.FieldCommandsNodesBuffer = FieldCommandsNodesBuffer_##NAME;\ #define COLLISION_GROUP 15 #define ACTIVATE_DISABLED 16 +#define VECTOR_LINEARFORCE 0 +#define VECTOR_LINEARVELOCITY 1 +#define VECTOR_ANGULARVELOCITY 2 +#define VECTOR_ANGULARTORQUE 3 +#define VECTOR_POSITIONTARGET 4 + +#define SCALAR_EXTERNALCLUSTERSTRAIN 0 +#define SCALAR_FIELDKILL 1 +#define SCALAR_SLEEPINGTHRESHOLD 2 +#define SCALAR_DISABLETHRESHOLD 3 +#define SCALAR_INTERNALCLUSTERSTRAIN 4 +#define SCALAR_DYNAMICCONSTRAINT 5 + +#define INTEGER_DYNAMICSTATE 0 +#define INTEGER_ACTIVATEDISABLED 1 +#define INTEGER_COLLISIONGROUP 2 +#define INTEGER_POSITIONANIMATED 3 +#define INTEGER_POSITIONSTATIC 4 + #define NONE_FIELD 0 #define UNIFORM_INTEGER 1 #define RADIAL_MASK_INTEGER 2 @@ -94,10 +146,22 @@ DIContext.FieldCommandsNodesBuffer = FieldCommandsNodesBuffer_##NAME;\ #define CULLING_INSIDE 0 #define CULLING_OUTSIDE 1 -struct ContextType +/* ----------------------------------------------------------------- + * Quat utils + * ----------------------------------------------------------------- + */ + +float3 FieldRotateVectorByQuat(in float3 Vector, in float4 Quat) { - float NodesDatas[MAX_DATAS]; -}; + float3 T = 2.0 * cross(Quat.xyz,Vector); + return Vector + Quat.w * T + cross(Quat.xyz,T); +} + +float3 FieldUnRotateVectorByQuat(in float3 Vector, in float4 Quat) +{ + float3 T = 2.0 * cross(Quat.xyz,Vector); + return Vector - Quat.w * T + cross(Quat.xyz,T); +} /* ----------------------------------------------------------------- * Uniform Integer Field @@ -316,7 +380,7 @@ void DIFieldSystem_EvaluateBoxFalloffScalar(in FDIFieldSystemContext DIContext, BoxScale.y != 0.0 ? 1.0 / BoxScale.y : 0.0, BoxScale.z != 0.0 ? 1.0 / BoxScale.z : 0.0); - const float3 LocalPosition = UnrotateVectorByQuat(SamplePosition-BoxTranslation,BoxRotation) * InverseScale; + const float3 LocalPosition = FieldUnRotateVectorByQuat(SamplePosition-BoxTranslation,BoxRotation) * InverseScale; const float3 DeltaPosition = abs(LocalPosition) - float3(1.0,1.0,1.0); const int ClosestAxis = ((DeltaPosition.x > DeltaPosition.y) && (DeltaPosition.x > DeltaPosition.z)) ? 0 : ( DeltaPosition.y > DeltaPosition.z) ? 1 : 2; @@ -366,7 +430,7 @@ void DIFieldSystem_EvaluateNoiseScalar(in FDIFieldSystemContext DIContext, in fl DIContext.FieldNodesParamsBuffer[NodeOffset+NOISE_SCALEZ]); const float3 DeltaBound = MaxBound - MinBound; - const float3 LocalVector = RotateVectorByQuat((SamplePosition-MinBound) * NoiseScale, NoiseRotation); + const float3 LocalVector = FieldRotateVectorByQuat((SamplePosition-MinBound) * NoiseScale, NoiseRotation); const float3 LocalCoord = float3( (DeltaBound.x != 0.0) ? LocalVector.x / DeltaBound.x : 0.0, (DeltaBound.y != 0.0) ? LocalVector.y / DeltaBound.y : 0.0, @@ -792,31 +856,65 @@ void DIFieldSystem_SampleFieldDatas(in FDIFieldSystemContext DIContext, in float } float3 DIFieldSystem_SampleFieldVector(in FDIFieldSystemContext DIContext, in float3 SamplePosition, - in float3 MinBound, in float3 MaxBound, in int CommandType) + in int CommandType, in int CommandIndex) { +#if USE_BAKED_FIELD == 0 float ContextDatas[MAX_DATAS]; int DatasIndex = 0; - DIFieldSystem_SampleFieldDatas(DIContext, SamplePosition, MinBound, MaxBound, CommandType, VECTOR_TYPE, ContextDatas, DatasIndex ); + DIFieldSystem_SampleFieldDatas(DIContext, SamplePosition, DIContext.MinBounds, DIContext.MaxBounds, CommandType, VECTOR_TYPE, ContextDatas, DatasIndex ); return (DatasIndex == 3) ? float3(ContextDatas[0],ContextDatas[1],ContextDatas[2]) : float3(0,0,0); +#else + float3 SamplePoint = (DIContext.MaxBounds != DIContext.MinBounds) ? (SamplePosition - DIContext.MinBounds) / + (DIContext.MaxBounds-DIContext.MinBounds) : float3(0,0,0); + SamplePoint = clamp(SamplePoint,float3(0,0,0), float3(1,1,1)); + + SamplePoint.z = (NUM_VECTOR_TYPES != 0) ? + (SamplePoint.z * (1.0 - 1.0 / DIContext.FieldDimensions.z) + CommandIndex) / NUM_VECTOR_TYPES : SamplePoint.z; + + return DIContext.VectorFieldTexture.SampleLevel(DIContext.VectorFieldSampler,SamplePoint,0).xyz; +#endif } float DIFieldSystem_SampleFieldScalar(in FDIFieldSystemContext DIContext, in float3 SamplePosition, - in float3 MinBound, in float3 MaxBound, in int CommandType) + in int CommandType, in int CommandIndex) { +#if USE_BAKED_FIELD == 0 float ContextDatas[MAX_DATAS]; int DatasIndex = 0; - DIFieldSystem_SampleFieldDatas(DIContext, SamplePosition, MinBound, MaxBound, CommandType, SCALAR_TYPE, ContextDatas, DatasIndex ); + DIFieldSystem_SampleFieldDatas(DIContext, SamplePosition, DIContext.MinBounds, DIContext.MaxBounds, CommandType, SCALAR_TYPE, ContextDatas, DatasIndex ); return (DatasIndex == 1) ? ContextDatas[0] : 0; +#else + float3 SamplePoint = (DIContext.MaxBounds != DIContext.MinBounds) ? (SamplePosition - DIContext.MinBounds) / + (DIContext.MaxBounds-DIContext.MinBounds) : float3(0,0,0); + SamplePoint = clamp(SamplePoint,float3(0,0,0), float3(1,1,1)); + + SamplePoint.z = (NUM_SCALAR_TYPES != 0) ? + (SamplePoint.z * (1.0 - 1.0 / DIContext.FieldDimensions.z) + CommandIndex) / NUM_SCALAR_TYPES : SamplePoint.z; + + return DIContext.ScalarFieldTexture.Sample(DIContext.ScalarFieldSampler,SamplePoint); +#endif } int DIFieldSystem_SampleFieldInteger(in FDIFieldSystemContext DIContext, in float3 SamplePosition, - in float3 MinBound, in float3 MaxBound, in int CommandType) + in int CommandType, in int CommandIndex) { +#if USE_BAKED_FIELD == 0 float ContextDatas[MAX_DATAS]; int DatasIndex = 0; - DIFieldSystem_SampleFieldDatas(DIContext, SamplePosition, MinBound, MaxBound, CommandType, INTEGER_TYPE, ContextDatas, DatasIndex ); + DIFieldSystem_SampleFieldDatas(DIContext, SamplePosition, DIContext.MinBounds, DIContext.MaxBounds, CommandType, INTEGER_TYPE, ContextDatas, DatasIndex ); return (DatasIndex == 1) ? ContextDatas[0] : 0; + +#else + float3 SamplePoint = (DIContext.MaxBounds != DIContext.MinBounds) ? (SamplePosition - DIContext.MinBounds) / + (DIContext.MaxBounds-DIContext.MinBounds) : float3(0,0,0); + SamplePoint = clamp(SamplePoint,float3(0,0,0), float3(1,1,1)); + + SamplePoint.z = (NUM_INTEGER_TYPES != 0) ? + (SamplePoint.z * (1.0 - 1.0 / DIContext.FieldDimensions.z) + CommandIndex) / NUM_INTEGER_TYPES : SamplePoint.z; + + return DIContext.IntegerFieldTexture.Sample(DIContext.IntegerFieldSampler,SamplePoint); +#endif } \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/ChaosNiagara.Build.cs b/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/ChaosNiagara.Build.cs index 47f682c636b3..d008c7250bd4 100644 --- a/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/ChaosNiagara.Build.cs +++ b/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/ChaosNiagara.Build.cs @@ -13,21 +13,27 @@ public class ChaosNiagara : ModuleRules PrivateDependencyModuleNames.AddRange( new string[] { + "Core", "CoreUObject", "Engine", + "Projects", "Slate", "SlateCore", "NiagaraCore", "Niagara", "NiagaraShader", "RenderCore", - "ChaosSolverEngine", + "VectorVM", + "RHI", + "ChaosSolverEngine", "Chaos", "ChaosSolvers", "GeometryCollectionEngine", "GeometryCollectionCore", - "PhysicsCore" - } + "PhysicsCore", + "FieldSystemCore", + "FieldSystemEngine", + } ); PublicDependencyModuleNames.AddRange( @@ -43,7 +49,9 @@ public class ChaosNiagara : ModuleRules "NiagaraVertexFactories", "ChaosSolverEngine", "GeometryCollectionEngine", - "GeometryCollectionCore" + "GeometryCollectionCore", + "FieldSystemCore", + "FieldSystemEngine" } ); diff --git a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfaceFieldSystem.h b/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Classes/NiagaraDataInterfaceFieldSystem.h similarity index 63% rename from Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfaceFieldSystem.h rename to Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Classes/NiagaraDataInterfaceFieldSystem.h index b487f24d806f..0b4f65f7fdc2 100644 --- a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfaceFieldSystem.h +++ b/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Classes/NiagaraDataInterfaceFieldSystem.h @@ -16,10 +16,19 @@ struct FNDIFieldSystemArrays { static const uint32 NumFields = FFieldNodeBase::ESerializationType::FieldNode_FReturnResultsTerminal + 1; static const uint32 NumCommands = EFieldPhysicsType::Field_PhysicsType_Max; - - TStaticArray FieldCommandsNodes; - TArray FieldNodesOffsets; - TArray FieldNodesParams; + + TStaticArray FieldCommandsNodes; + TArray FieldNodesOffsets; + TArray FieldNodesParams; + + TArray ArrayFieldDatas; + TArray VectorFieldDatas; + TArray ScalarFieldDatas; + TArray IntegerFieldDatas; + + FIntVector FieldDimensions; + FVector MinBounds; + FVector MaxBounds; }; /** Render buffers that will be used in hlsl functions */ @@ -29,7 +38,8 @@ struct FNDIFieldSystemBuffer : public FRenderResource bool IsValid() const; /** Set the assets that will be used to affect the buffer */ - void Initialize(const TArray>& FieldSystems, const TArray>& FieldComponents); + void Initialize(const TArray>& FieldSystems, const TArray>& FieldComponents, + const FIntVector& FieldDimensions, const FVector& MinBounds, const FVector& MaxBounds); /** Update the buffers */ void Update(); @@ -52,6 +62,15 @@ struct FNDIFieldSystemBuffer : public FRenderResource /** Field commands nodes buffer */ FRWBuffer FieldCommandsNodesBuffer; + /** Vector Field Texture */ + FTextureRWBuffer3D VectorFieldTexture; + + /** Scalar Field Texture */ + FTextureRWBuffer3D ScalarFieldTexture; + + /** Integer Field Texture */ + FTextureRWBuffer3D IntegerFieldTexture; + /** The field systems to be used*/ TArray> FieldSystems; @@ -76,8 +95,8 @@ struct FNDIFieldSystemData }; /** Data Interface for the strand base */ -UCLASS(EditInlineNew, Category = "Strands", meta = (DisplayName = "Field System")) -class HAIRSTRANDSNIAGARA_API UNiagaraDataInterfaceFieldSystem : public UNiagaraDataInterface +UCLASS(EditInlineNew, Category = "Chaos", meta = (DisplayName = "Field System")) +class CHAOSNIAGARA_API UNiagaraDataInterfaceFieldSystem : public UNiagaraDataInterface { GENERATED_UCLASS_BODY() @@ -85,10 +104,6 @@ public: DECLARE_NIAGARA_DI_PARAMETER(); - /** Field system. */ - UPROPERTY(EditAnywhere, Category = "Source") - UFieldSystem* DefaultSource; - /** Blue print. */ UPROPERTY(EditAnywhere, Category = "Source") UBlueprint* BlueprintSource; @@ -97,6 +112,18 @@ public: UPROPERTY(EditAnywhere, Category = "Source") AActor* SourceActor; + /** The source actor from which to sample */ + UPROPERTY(EditAnywhere, Category = "Field") + FIntVector FieldDimensions; + + /** The source actor from which to sample */ + UPROPERTY(EditAnywhere, Category = "Field") + FVector MinBounds; + + /** The source actor from which to sample */ + UPROPERTY(EditAnywhere, Category = "Field") + FVector MaxBounds; + /** The source component from which to sample */ TArray> SourceComponents; @@ -137,6 +164,48 @@ public: /** Sample the field angular torque */ void SampleAngularTorque(FVectorVMContext& Context); + /** Sample the field dynamic state */ + void SampleDynamicState(FVectorVMContext& Context); + + /** Sample the field dynamic constraint */ + void SampleDynamicConstraint(FVectorVMContext& Context); + + /** Sample the field collision group */ + void SampleCollisionGroup(FVectorVMContext& Context); + + /** Sample the field activate disabled */ + void SampleActivateDisabled(FVectorVMContext& Context); + + /** Sample the field kill */ + void SampleFieldKill(FVectorVMContext& Context); + + /** Sample the field external cluster strain */ + void SampleExternalClusterStrain(FVectorVMContext& Context); + + /** Sample the field internal cluster strain */ + void SampleInternalClusterStrain(FVectorVMContext& Context); + + /** Sample the field distance threshold */ + void SampleDisableThreshold(FVectorVMContext& Context); + + /** Sample the field sleeping threshold */ + void SampleSleepingThreshold(FVectorVMContext& Context); + + /** Sample the field static position */ + void SamplePositionStatic(FVectorVMContext& Context); + + /** Sample the field animated position */ + void SamplePositionAnimated(FVectorVMContext& Context); + + /** Sample the field target position */ + void SamplePositionTarget(FVectorVMContext& Context); + + /** Get the field dimensions */ + void GetFieldDimensions(FVectorVMContext& Context); + + /** Get the field bounds */ + void GetFieldBounds(FVectorVMContext& Context); + /** Name of field commands nodes buffer */ static const FString FieldCommandsNodesBufferName; @@ -146,6 +215,33 @@ public: /** Name of field nodes params buffer */ static const FString FieldNodesOffsetsBufferName; + /** Name of the vector field texture */ + static const FString VectorFieldTextureName; + + /** Name of the vector field sampler */ + static const FString VectorFieldSamplerName; + + /** Name of the scalar field texture*/ + static const FString ScalarFieldTextureName; + + /** Name of the scalar field sampler */ + static const FString ScalarFieldSamplerName; + + /** Name of the integer field texture*/ + static const FString IntegerFieldTextureName; + + /** Name of the integer field sampler */ + static const FString IntegerFieldSamplerName; + + /** Name of the field dimension property */ + static const FString FieldDimensionsName; + + /** Name of the min bounds property */ + static const FString MinBoundsName; + + /** Name of the max bounds property */ + static const FString MaxBoundsName; + protected: /** Copy one niagara DI to this */ virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; @@ -160,21 +256,12 @@ struct FNDIFieldSystemProxy : public FNiagaraDataInterfaceProxy /** Get the data that will be passed to render*/ virtual void ConsumePerInstanceDataFromGameThread(void* PerInstanceData, const FNiagaraSystemInstanceID& Instance) override; - /** Initialize the Proxy data strands buffer */ + /** Initialize the Proxy data Chaos buffer */ void InitializePerInstanceData(const FNiagaraSystemInstanceID& SystemInstance); /** Destroy the proxy data if necessary */ void DestroyPerInstanceData(NiagaraEmitterInstanceBatcher* Batcher, const FNiagaraSystemInstanceID& SystemInstance); - /** Launch all pre stage functions */ - virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - - /** Launch all post stage functions */ - virtual void PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - - /** Reset the buffers */ - virtual void ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - /** List of proxy data for each system instances*/ TMap SystemInstancesToProxyData; }; diff --git a/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Private/ChaosNiagara.cpp b/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Private/ChaosNiagara.cpp index 5294c23db6bb..7118a083dd25 100644 --- a/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Private/ChaosNiagara.cpp +++ b/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Private/ChaosNiagara.cpp @@ -2,12 +2,18 @@ #include "ChaosNiagara.h" #include "Modules/ModuleManager.h" +#include "Interfaces/IPluginManager.h" +#include "ShaderCore.h" + #define LOCTEXT_NAMESPACE "FChaosNiagaraModule" void FChaosNiagaraModule::StartupModule() { // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + + FString PluginShaderDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("ChaosNiagara"))->GetBaseDir(), TEXT("Shaders")); + AddShaderSourceDirectoryMapping(TEXT("/Plugin/Experimental/ChaosNiagara"), PluginShaderDir); } void FChaosNiagaraModule::ShutdownModule() diff --git a/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Private/NiagaraDataInterfaceChaosDestruction.cpp b/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Private/NiagaraDataInterfaceChaosDestruction.cpp index c3c08c65be4b..01d8e881051b 100644 --- a/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Private/NiagaraDataInterfaceChaosDestruction.cpp +++ b/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Private/NiagaraDataInterfaceChaosDestruction.cpp @@ -448,18 +448,15 @@ bool UNiagaraDataInterfaceChaosDestruction::InitPerInstanceData(void* PerInstanc #if INCLUDE_CHAOS if (SystemInstance) { - if (UNiagaraComponent* NiagaraComponent = SystemInstance->GetComponent()) + if (UWorld* World = SystemInstance->GetWorld()) { - if (UWorld* World = NiagaraComponent->GetWorld()) - { - int32 NewIdx = Solvers.Add(FSolverData()); + int32 NewIdx = Solvers.Add(FSolverData()); - FSolverData& SolverData = Solvers[NewIdx]; - SolverData.PhysScene = World->GetPhysicsScene(); - SolverData.Solver = SolverData.PhysScene->GetSolver(); + FSolverData& SolverData = Solvers[NewIdx]; + SolverData.PhysScene = World->GetPhysicsScene(); + SolverData.Solver = SolverData.PhysScene->GetSolver(); - RegisterWithSolverEventManager(SolverData.Solver); - } + RegisterWithSolverEventManager(SolverData.Solver); } } #endif @@ -4353,7 +4350,7 @@ public: FNiagaraDataInterfaceProxyChaosDestruction* ChaosDestructionInterfaceProxy = static_cast(Context.DataInterface); if (ChaosDestructionInterfaceProxy) { - FNiagaraDIChaosDestruction_GPUData* InstanceData = ChaosDestructionInterfaceProxy->SystemsToGPUInstanceData.Find(Context.SystemInstance); + FNiagaraDIChaosDestruction_GPUData* InstanceData = ChaosDestructionInterfaceProxy->SystemsToGPUInstanceData.Find(Context.SystemInstanceID); ensure(InstanceData); diff --git a/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Private/NiagaraDataInterfaceFieldSystem.cpp b/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Private/NiagaraDataInterfaceFieldSystem.cpp new file mode 100644 index 000000000000..77db48ae200d --- /dev/null +++ b/Engine/Plugins/Experimental/ChaosNiagara/Source/ChaosNiagara/Private/NiagaraDataInterfaceFieldSystem.cpp @@ -0,0 +1,2061 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "NiagaraDataInterfaceFieldSystem.h" +#include "NiagaraShader.h" +#include "NiagaraComponent.h" +#include "NiagaraRenderer.h" +#include "NiagaraSystemInstance.h" +#include "NiagaraEmitterInstanceBatcher.h" +#include "ShaderParameterUtils.h" +#include "Field/FieldSystemActor.h" +#include "Field/FieldSystem.h" +#include "Field/FieldSystemNodes.h" + +#define LOCTEXT_NAMESPACE "NiagaraDataInterfaceFieldSystem" +DEFINE_LOG_CATEGORY_STATIC(LogFieldSystem, Log, All); + +//------------------------------------------------------------------------------------------------------------ + +static const FName SampleLinearVelocityName(TEXT("SampleLinearVelocity")); +static const FName SampleAngularVelocityName(TEXT("SampleAngularVelocity")); +static const FName SampleLinearForceName(TEXT("SampleLinearForce")); +static const FName SampleAngularTorqueName(TEXT("SampleAngularTorque")); +static const FName SamplePositionTargetName(TEXT("SamplePositionTarget")); + +static const FName SampleExternalClusterStrainName(TEXT("SampleExternalClusterStrain")); +static const FName SampleInternalClusterStrainName(TEXT("SampleInternalClusterStrain")); +static const FName SampleFieldKillName(TEXT("SampleFieldKill")); +static const FName SampleDynamicConstraintName(TEXT("SampleDynamicConstraint")); +static const FName SampleSleepingThresholdName(TEXT("SampleSleepingThreshold")); +static const FName SampleDisableThresholdName(TEXT("SampleDisableThreshold")); + +static const FName SampleDynamicStateName(TEXT("SampleDynamicState")); +static const FName SampleActivateDisabledName(TEXT("SampleActivateDisabled")); +static const FName SampleCollisionGroupName(TEXT("SampleCollisionGroup")); +static const FName SamplePositionAnimatedName(TEXT("SamplePositionAnimated")); +static const FName SamplePositionStaticName(TEXT("SamplePositionStatic")); + +static const FName GetFieldDimensionsName(TEXT("GetFieldDimensions")); +static const FName GetFieldBoundsName(TEXT("GetFieldBounds")); + +//------------------------------------------------------------------------------------------------------------ + +static const TArray VectorTypes = { EFieldPhysicsType::Field_LinearForce, + EFieldPhysicsType::Field_LinearVelocity, + EFieldPhysicsType::Field_AngularVelociy, + EFieldPhysicsType::Field_AngularTorque, + EFieldPhysicsType::Field_PositionTarget}; + +enum EFieldVectorIndices +{ + Vector_LinearForce, + Vector_LinearVelocity, + Vector_AngularVelocity, + Vector_AngularTorque, + Vector_PositionTarget +}; + +static const TArray ScalarTypes = { EFieldPhysicsType::Field_ExternalClusterStrain, + EFieldPhysicsType::Field_Kill, + EFieldPhysicsType::Field_SleepingThreshold, + EFieldPhysicsType::Field_DisableThreshold, + EFieldPhysicsType::Field_InternalClusterStrain, + EFieldPhysicsType::Field_DynamicConstraint}; + +enum EFieldScalarIndices +{ + Scalar_ExternalClusterStrain, + Scalar_Kill, + Scalar_SleepingThreshold, + Scalar_DisableThreshold, + Scalar_InternalClusterStrain, + Scalar_DynamicConstraint +}; + +static const TArray IntegerTypes = { EFieldPhysicsType::Field_DynamicState, + EFieldPhysicsType::Field_ActivateDisabled, + EFieldPhysicsType::Field_CollisionGroup, + EFieldPhysicsType::Field_PositionAnimated, + EFieldPhysicsType::Field_PositionStatic}; + +enum EFieldIntegerIndices +{ + Integer_DynamicState, + Integer_ActivateDisabled, + Integer_CollisionGroup, + Integer_PositionAnimated, + Integer_PositionStatic +}; + +//------------------------------------------------------------------------------------------------------------ + +const FString UNiagaraDataInterfaceFieldSystem::FieldCommandsNodesBufferName(TEXT("FieldCommandsNodesBuffer_")); +const FString UNiagaraDataInterfaceFieldSystem::FieldNodesParamsBufferName(TEXT("FieldNodesParamsBuffer_")); +const FString UNiagaraDataInterfaceFieldSystem::FieldNodesOffsetsBufferName(TEXT("FieldNodesOffsetsBuffer_")); + +const FString UNiagaraDataInterfaceFieldSystem::VectorFieldTextureName(TEXT("VectorFieldTexture_")); +const FString UNiagaraDataInterfaceFieldSystem::VectorFieldSamplerName(TEXT("VectorFieldSampler_")); + +const FString UNiagaraDataInterfaceFieldSystem::ScalarFieldTextureName(TEXT("ScalarFieldTexture_")); +const FString UNiagaraDataInterfaceFieldSystem::ScalarFieldSamplerName(TEXT("ScalarFieldSampler_")); + +const FString UNiagaraDataInterfaceFieldSystem::IntegerFieldTextureName(TEXT("IntegerFieldTexture_")); +const FString UNiagaraDataInterfaceFieldSystem::IntegerFieldSamplerName(TEXT("IntegerFieldSampler_")); + +const FString UNiagaraDataInterfaceFieldSystem::FieldDimensionsName(TEXT("FieldDimensions_")); +const FString UNiagaraDataInterfaceFieldSystem::MinBoundsName(TEXT("MinBounds_")); +const FString UNiagaraDataInterfaceFieldSystem::MaxBoundsName(TEXT("MaxBounds_")); + +//------------------------------------------------------------------------------------------------------------ + +struct FNDIFieldSystemParametersName +{ + FNDIFieldSystemParametersName(const FString& Suffix) + { + FieldCommandsNodesBufferName = UNiagaraDataInterfaceFieldSystem::FieldCommandsNodesBufferName + Suffix; + FieldNodesParamsBufferName = UNiagaraDataInterfaceFieldSystem::FieldNodesParamsBufferName + Suffix; + FieldNodesOffsetsBufferName = UNiagaraDataInterfaceFieldSystem::FieldNodesOffsetsBufferName + Suffix; + + VectorFieldTextureName = UNiagaraDataInterfaceFieldSystem::VectorFieldTextureName + Suffix; + VectorFieldSamplerName = UNiagaraDataInterfaceFieldSystem::VectorFieldSamplerName + Suffix; + + ScalarFieldTextureName = UNiagaraDataInterfaceFieldSystem::ScalarFieldTextureName + Suffix; + ScalarFieldSamplerName = UNiagaraDataInterfaceFieldSystem::ScalarFieldSamplerName + Suffix; + + IntegerFieldTextureName = UNiagaraDataInterfaceFieldSystem::IntegerFieldTextureName + Suffix; + IntegerFieldSamplerName = UNiagaraDataInterfaceFieldSystem::IntegerFieldSamplerName + Suffix; + + FieldDimensionsName = UNiagaraDataInterfaceFieldSystem::FieldDimensionsName + Suffix; + MinBoundsName = UNiagaraDataInterfaceFieldSystem::MinBoundsName + Suffix; + MaxBoundsName = UNiagaraDataInterfaceFieldSystem::MaxBoundsName + Suffix; + } + + FString FieldCommandsNodesBufferName; + FString FieldNodesParamsBufferName; + FString FieldNodesOffsetsBufferName; + + FString VectorFieldTextureName; + FString VectorFieldSamplerName; + + FString ScalarFieldTextureName; + FString ScalarFieldSamplerName; + + FString IntegerFieldTextureName; + FString IntegerFieldSamplerName; + + FString FieldDimensionsName; + FString MinBoundsName; + FString MaxBoundsName; +}; + +//------------------------------------------------------------------------------------------------------------ + +template +void CreateInternalBuffer(const uint32 ElementCount, const BufferType* InputData, FRWBuffer& OutputBuffer) +{ + if (ElementCount > 0) + { + const uint32 BufferCount = ElementCount * ElementSize; + const uint32 BufferBytes = sizeof(BufferType) * BufferCount; + + if (InitBuffer) + { + OutputBuffer.Initialize(sizeof(BufferType), BufferCount, PixelFormat, BUF_Static); + } + void* OutputData = RHILockVertexBuffer(OutputBuffer.Buffer, 0, BufferBytes, RLM_WriteOnly); + + FMemory::Memcpy(OutputData, InputData, BufferBytes); + RHIUnlockVertexBuffer(OutputBuffer.Buffer); + } +} +template +void CreateInternalTexture(const uint32 DimensionX, const uint32 DimensionY, const uint32 DimensionZ, const BufferType* InputData, FTextureRWBuffer3D& OutputBuffer) +{ + if (DimensionX * DimensionY * DimensionZ > 0) + { + const uint32 BlockBytes = sizeof(BufferType) * ElementSize; + + if (InitBuffer) + { + OutputBuffer.Initialize(BlockBytes, DimensionX, DimensionY, DimensionZ, PixelFormat); + } + FUpdateTextureRegion3D UpdateRegion(0, 0, 0, 0, 0, 0, DimensionX, DimensionY, DimensionZ); + + const uint8* TextureDatas = (const uint8*)InputData; + RHIUpdateTexture3D(OutputBuffer.Buffer, 0, UpdateRegion, DimensionX * BlockBytes, + DimensionX * DimensionY * BlockBytes, TextureDatas); + } +} + + +void BuildNodeParams(FFieldNodeBase* FieldNode, FNDIFieldSystemArrays* OutAssetArrays) +{ + if (FieldNode && OutAssetArrays) + { + if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FUniformInteger) + { + FUniformInteger* LocalNode = StaticCast(FieldNode); + + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FUniformInteger); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FRadialIntMask) + { + FRadialIntMask* LocalNode = StaticCast(FieldNode); + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FRadialIntMask); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Radius); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.X); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Y); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Z); + OutAssetArrays->FieldNodesParams.Add(LocalNode->InteriorValue); + OutAssetArrays->FieldNodesParams.Add(LocalNode->ExteriorValue); + OutAssetArrays->FieldNodesParams.Add(LocalNode->SetMaskCondition); + + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FUniformScalar) + { + FUniformScalar* LocalNode = StaticCast(FieldNode); + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FUniformScalar); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FRadialFalloff) + { + FRadialFalloff* LocalNode = StaticCast(FieldNode); + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FRadialFalloff); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); + OutAssetArrays->FieldNodesParams.Add(LocalNode->MinRange); + OutAssetArrays->FieldNodesParams.Add(LocalNode->MaxRange); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Default); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Radius); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.X); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Y); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Z); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Falloff); + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FPlaneFalloff) + { + FPlaneFalloff* LocalNode = StaticCast(FieldNode); + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FPlaneFalloff); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); + OutAssetArrays->FieldNodesParams.Add(LocalNode->MinRange); + OutAssetArrays->FieldNodesParams.Add(LocalNode->MaxRange); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Default); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Distance); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.X); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Y); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Z); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Normal.X); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Normal.Y); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Normal.Z); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Falloff); + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FBoxFalloff) + { + FBoxFalloff* LocalNode = StaticCast(FieldNode); + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FBoxFalloff); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); + OutAssetArrays->FieldNodesParams.Add(LocalNode->MinRange); + OutAssetArrays->FieldNodesParams.Add(LocalNode->MaxRange); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Default); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().X); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().Y); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().Z); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().W); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetTranslation().X); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetTranslation().Y); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetTranslation().Z); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetScale3D().X); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetScale3D().Y); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetScale3D().Z); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Falloff); + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FNoiseField) + { + FNoiseField* LocalNode = StaticCast(FieldNode); + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FNoiseField); + OutAssetArrays->FieldNodesParams.Add(LocalNode->MinRange); + OutAssetArrays->FieldNodesParams.Add(LocalNode->MaxRange); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().X); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().Y); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().Z); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().W); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetTranslation().X); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetTranslation().Y); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetTranslation().Z); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetScale3D().X); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetScale3D().Y); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetScale3D().Z); + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FUniformVector) + { + FUniformVector* LocalNode = StaticCast(FieldNode); + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FUniformVector); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Direction.X); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Direction.Y); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Direction.Z); + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FRadialVector) + { + FRadialVector* LocalNode = StaticCast(FieldNode); + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FRadialVector); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.X); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Y); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Z); + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FRandomVector) + { + FRandomVector* LocalNode = StaticCast(FieldNode); + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FRandomVector); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FSumScalar) + { + FSumScalar* LocalNode = StaticCast(FieldNode); + + BuildNodeParams(LocalNode->ScalarRight.Get(), OutAssetArrays); + BuildNodeParams(LocalNode->ScalarLeft.Get(), OutAssetArrays); + + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FSumScalar); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); + OutAssetArrays->FieldNodesParams.Add(LocalNode->ScalarRight != nullptr); + OutAssetArrays->FieldNodesParams.Add(LocalNode->ScalarLeft != nullptr); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Operation); + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FSumVector) + { + FSumVector* LocalNode = StaticCast(FieldNode); + + BuildNodeParams(LocalNode->Scalar.Get(), OutAssetArrays); + BuildNodeParams(LocalNode->VectorRight.Get(), OutAssetArrays); + BuildNodeParams(LocalNode->VectorLeft.Get(), OutAssetArrays); + + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FSumVector); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Scalar.Get() != nullptr); + OutAssetArrays->FieldNodesParams.Add(LocalNode->VectorRight.Get() != nullptr); + OutAssetArrays->FieldNodesParams.Add(LocalNode->VectorLeft.Get() != nullptr); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Operation); + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FConversionField) + { + if (FieldNode->Type() == FFieldNodeBase::EFieldType::EField_Int32) + { + FConversionField* LocalNode = StaticCast*>(FieldNode); + + BuildNodeParams(LocalNode->InputField.Get(), OutAssetArrays); + + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FConversionField); + OutAssetArrays->FieldNodesParams.Add(LocalNode->InputField.Get() != nullptr); + } + else if (FieldNode->Type() == FFieldNodeBase::EFieldType::EField_Float) + { + FConversionField* LocalNode = StaticCast*>(FieldNode); + + BuildNodeParams(LocalNode->InputField.Get(), OutAssetArrays); + + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FConversionField); + OutAssetArrays->FieldNodesParams.Add(LocalNode->InputField.Get() != nullptr); + } + } + else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FCullingField) + { + if (FieldNode->Type() == FFieldNodeBase::EFieldType::EField_Int32) + { + FCullingField* LocalNode = StaticCast*>(FieldNode); + + BuildNodeParams(LocalNode->Culling.Get(), OutAssetArrays); + BuildNodeParams(LocalNode->Input.Get(), OutAssetArrays); + + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FCullingField); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Culling.Get() != nullptr); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Input.Get() != nullptr); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Operation); + } + else if (FieldNode->Type() == FFieldNodeBase::EFieldType::EField_Float) + { + FCullingField* LocalNode = StaticCast*>(FieldNode); + + BuildNodeParams(LocalNode->Culling.Get(), OutAssetArrays); + BuildNodeParams(LocalNode->Input.Get(), OutAssetArrays); + + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FCullingField); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Culling.Get() != nullptr); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Input.Get() != nullptr); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Operation); + } + else if (FieldNode->Type() == FFieldNodeBase::EFieldType::EField_FVector) + { + FCullingField* LocalNode = StaticCast*>(FieldNode); + + BuildNodeParams(LocalNode->Culling.Get(), OutAssetArrays); + BuildNodeParams(LocalNode->Input.Get(), OutAssetArrays); + + OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); + OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); + OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FCullingField); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Culling.Get() != nullptr); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Input.Get() != nullptr); + OutAssetArrays->FieldNodesParams.Add(LocalNode->Operation); + } + } + } +} + + +template +FFieldNode* GetFieldNode(const TArray>& FieldSystems, const EFieldPhysicsType FieldType) +{ + for (int32 SystemIndex = 0; SystemIndex < FieldSystems.Num(); ++SystemIndex) + { + TWeakObjectPtr FieldSystem = FieldSystems[SystemIndex]; + if (FieldSystem.IsValid() && FieldSystem.Get() != nullptr) + { + TArray< FFieldSystemCommand >& FieldCommands = FieldSystem->Commands; + for (int32 CommandIndex = 0; CommandIndex < FieldCommands.Num(); ++CommandIndex) + { + const EFieldPhysicsType CommandType = GetFieldPhysicsType(FieldCommands[CommandIndex].TargetAttribute); + if (CommandType == FieldType && FieldCommands[CommandIndex].RootNode.Get()) + { + return static_cast*>( + FieldCommands[CommandIndex].RootNode.Get()); + } + } + } + } + return nullptr; +} + +void BakeFieldArrays(const TArray>& FieldSystems, const TArray>& FieldComponents, + FNDIFieldSystemArrays* OutAssetArrays) +{ + const int32 FieldSize = OutAssetArrays->FieldDimensions.X * OutAssetArrays->FieldDimensions.Y * OutAssetArrays->FieldDimensions.Z; + + OutAssetArrays->ArrayFieldDatas.Init(0.0, FieldSize * 4 * VectorTypes.Num()); + OutAssetArrays->VectorFieldDatas.Init(FVector(0.f), FieldSize * VectorTypes.Num()); + OutAssetArrays->ScalarFieldDatas.Init(0.0, FieldSize * ScalarTypes.Num()); + OutAssetArrays->IntegerFieldDatas.Init(0, FieldSize * IntegerTypes.Num()); + + TArray IndicesArray; + ContextIndex::ContiguousIndices(IndicesArray, FieldSize); + + TArrayView IndexView(&(IndicesArray[0]), IndicesArray.Num()); + + TArray SamplesArray; + SamplesArray.Init(FVector(0.f), FieldSize); + + const FVector CellSize = (OutAssetArrays->MaxBounds - OutAssetArrays->MinBounds) / + FVector(OutAssetArrays->FieldDimensions.X - 1, OutAssetArrays->FieldDimensions.Y - 1, OutAssetArrays->FieldDimensions.Z - 1); + + int32 SampleIndex = 0; + //for (int32 GridIndexX = 0; GridIndexX < OutAssetArrays->FieldDimensions.X; ++GridIndexX) + for (int32 GridIndexZ = 0; GridIndexZ < OutAssetArrays->FieldDimensions.Z; ++GridIndexZ) + { + for (int32 GridIndexY = 0; GridIndexY < OutAssetArrays->FieldDimensions.Y; ++GridIndexY) + { + for (int32 GridIndexX = 0; GridIndexX < OutAssetArrays->FieldDimensions.X; ++GridIndexX) + //for (int32 GridIndexZ = 0; GridIndexZ < OutAssetArrays->FieldDimensions.Z; ++GridIndexZ) + { + SamplesArray[SampleIndex++] = OutAssetArrays->MinBounds + FVector(GridIndexX, GridIndexY, GridIndexZ) * CellSize; + } + } + } + TArrayView SamplesView(&(SamplesArray.operator[](0)), FieldSize); + + FFieldContext FieldContext{ + IndexView, + SamplesView, + FFieldContext::UniquePointerMap() + }; + + int32 VectorBegin = 0; + for (int32 TypeIndex = 0; TypeIndex < VectorTypes.Num(); ++TypeIndex) + { + TArrayView ResultsView(&(OutAssetArrays->VectorFieldDatas[0]) + VectorBegin, FieldSize); + FFieldNode* CommandRoot = GetFieldNode(FieldSystems, VectorTypes[TypeIndex]); + if (CommandRoot) + { + CommandRoot->Evaluate(FieldContext, ResultsView); + + for (int32 ArrayIndex = VectorBegin, VectorEnd = VectorBegin + FieldSize; ArrayIndex < VectorEnd; ++ArrayIndex) + { + //UE_LOG(LogFieldSystem, Warning, TEXT("Sample Field = %d %d %s"), TypeIndex, ArrayIndex, *OutAssetArrays->VectorFieldDatas[ArrayIndex].ToString()); + OutAssetArrays->ArrayFieldDatas[4 * ArrayIndex] = OutAssetArrays->VectorFieldDatas[ArrayIndex].X; + OutAssetArrays->ArrayFieldDatas[4 * ArrayIndex + 1] = OutAssetArrays->VectorFieldDatas[ArrayIndex].Y; + OutAssetArrays->ArrayFieldDatas[4 * ArrayIndex + 2] = OutAssetArrays->VectorFieldDatas[ArrayIndex].Z; + } + } + VectorBegin += FieldSize; + } + int32 ScalarBegin = 0; + for (int32 TypeIndex = 0; TypeIndex < ScalarTypes.Num(); ++TypeIndex) + { + TArrayView ResultsView(&(OutAssetArrays->ScalarFieldDatas[0]) + ScalarBegin, FieldSize); + FFieldNode* CommandRoot = GetFieldNode(FieldSystems, ScalarTypes[TypeIndex]); + if (CommandRoot) + { + CommandRoot->Evaluate(FieldContext, ResultsView); + } + ScalarBegin += FieldSize; + } + int32 IntegerBegin = 0; + for (int32 TypeIndex = 0; TypeIndex < IntegerTypes.Num(); ++TypeIndex) + { + TArrayView ResultsView(&(OutAssetArrays->IntegerFieldDatas[0]) + IntegerBegin, FieldSize); + FFieldNode* CommandRoot = GetFieldNode(FieldSystems, IntegerTypes[TypeIndex]); + if (CommandRoot) + { + CommandRoot->Evaluate(FieldContext, ResultsView); + } + IntegerBegin += FieldSize; + } +} + +void CreateInternalArrays(const TArray>& FieldSystems, const TArray>& FieldComponents, + FNDIFieldSystemArrays* OutAssetArrays) +{ + if (OutAssetArrays != nullptr) + { + OutAssetArrays->FieldNodesOffsets.Empty(); + OutAssetArrays->FieldNodesParams.Empty(); + + for (uint32 FieldIndex = 0; FieldIndex < FNDIFieldSystemArrays::NumCommands + 1; ++FieldIndex) + { + OutAssetArrays->FieldCommandsNodes[FieldIndex] = 0; + } + for (int32 SystemIndex = 0; SystemIndex < FieldSystems.Num(); ++SystemIndex) + { + TWeakObjectPtr FieldSystem = FieldSystems[SystemIndex]; + if (FieldSystem.IsValid() && FieldSystem.Get() != nullptr) + { + TArray< FFieldSystemCommand >& FieldCommands = FieldSystem->Commands; + for (int32 CommandIndex = 0; CommandIndex < FieldCommands.Num(); ++CommandIndex) + { + const EFieldPhysicsType CommandType = GetFieldPhysicsType(FieldCommands[CommandIndex].TargetAttribute); + OutAssetArrays->FieldCommandsNodes[CommandType + 1] = OutAssetArrays->FieldNodesOffsets.Num(); + + TUniquePtr& RootNode = FieldCommands[CommandIndex].RootNode; + BuildNodeParams(RootNode.Get(), OutAssetArrays); + + OutAssetArrays->FieldCommandsNodes[CommandType + 1] = OutAssetArrays->FieldNodesOffsets.Num() - + OutAssetArrays->FieldCommandsNodes[CommandType + 1]; + } + } + } + for (uint32 FieldIndex = 1; FieldIndex < FNDIFieldSystemArrays::NumCommands + 1; ++FieldIndex) + { + OutAssetArrays->FieldCommandsNodes[FieldIndex] += OutAssetArrays->FieldCommandsNodes[FieldIndex - 1]; + } + + BakeFieldArrays(FieldSystems, FieldComponents, OutAssetArrays); + } +} + +void UpdateInternalArrays(const TArray>& FieldSystems, const TArray>& FieldComponents, + FNDIFieldSystemArrays* OutAssetArrays) +{ + CreateInternalArrays(FieldSystems, FieldComponents, OutAssetArrays); +} + +//------------------------------------------------------------------------------------------------------------ + + +bool FNDIFieldSystemBuffer::IsValid() const +{ + return (0 < FieldSystems.Num() && FieldSystems[0].IsValid() && + FieldSystems[0].Get() != nullptr) && (AssetArrays.IsValid() && AssetArrays.Get() != nullptr) && FieldSystems.Num() == FieldSystems.Num(); +} + +void FNDIFieldSystemBuffer::Initialize(const TArray>& InFieldSystems, const TArray>& InFieldComponents, + const FIntVector& FieldDimensions, const FVector& MinBounds, const FVector& MaxBounds) +{ + FieldSystems = InFieldSystems; + FieldComponents = InFieldComponents; + + AssetArrays = MakeUnique(); + + if (IsValid()) + { + AssetArrays->FieldDimensions = FieldDimensions; + AssetArrays->MinBounds = MinBounds; + AssetArrays->MaxBounds = MaxBounds; + + CreateInternalArrays(FieldSystems, FieldComponents, AssetArrays.Get()); + } +} + +void FNDIFieldSystemBuffer::Update() +{ + if (IsValid()) + { + UpdateInternalArrays(FieldSystems, FieldComponents, AssetArrays.Get()); + + FNDIFieldSystemBuffer* ThisBuffer = this; + ENQUEUE_RENDER_COMMAND(UpdateFieldSystem)( + [ThisBuffer](FRHICommandListImmediate& RHICmdList) mutable + { + CreateInternalBuffer(ThisBuffer->AssetArrays->FieldNodesParams.Num(), ThisBuffer->AssetArrays->FieldNodesParams.GetData(), ThisBuffer->FieldNodesParamsBuffer); + CreateInternalBuffer(ThisBuffer->AssetArrays->FieldCommandsNodes.Num(), ThisBuffer->AssetArrays->FieldCommandsNodes.GetData(), ThisBuffer->FieldCommandsNodesBuffer); + CreateInternalBuffer(ThisBuffer->AssetArrays->FieldNodesOffsets.Num(), ThisBuffer->AssetArrays->FieldNodesOffsets.GetData(), ThisBuffer->FieldNodesOffsetsBuffer); + + CreateInternalTexture( + ThisBuffer->AssetArrays->FieldDimensions.X, ThisBuffer->AssetArrays->FieldDimensions.Y, ThisBuffer->AssetArrays->FieldDimensions.Z * VectorTypes.Num(), + ThisBuffer->AssetArrays->ArrayFieldDatas.GetData(), ThisBuffer->VectorFieldTexture); + CreateInternalTexture( + ThisBuffer->AssetArrays->FieldDimensions.X, ThisBuffer->AssetArrays->FieldDimensions.Y, ThisBuffer->AssetArrays->FieldDimensions.Z * ScalarTypes.Num(), + ThisBuffer->AssetArrays->ScalarFieldDatas.GetData(), ThisBuffer->ScalarFieldTexture); + CreateInternalTexture( + ThisBuffer->AssetArrays->FieldDimensions.X, ThisBuffer->AssetArrays->FieldDimensions.Y, ThisBuffer->AssetArrays->FieldDimensions.Z * IntegerTypes.Num(), + ThisBuffer->AssetArrays->IntegerFieldDatas.GetData(), ThisBuffer->IntegerFieldTexture); + } + ); + } +} + +void FNDIFieldSystemBuffer::InitRHI() +{ + if (IsValid()) + { + CreateInternalBuffer(AssetArrays->FieldNodesParams.Num(), AssetArrays->FieldNodesParams.GetData(), FieldNodesParamsBuffer); + CreateInternalBuffer(AssetArrays->FieldCommandsNodes.Num(), AssetArrays->FieldCommandsNodes.GetData(), FieldCommandsNodesBuffer); + CreateInternalBuffer(AssetArrays->FieldNodesOffsets.Num(), AssetArrays->FieldNodesOffsets.GetData(), FieldNodesOffsetsBuffer); + + CreateInternalTexture( + AssetArrays->FieldDimensions.X, AssetArrays->FieldDimensions.Y, AssetArrays->FieldDimensions.Z * VectorTypes.Num(), + AssetArrays->ArrayFieldDatas.GetData(), VectorFieldTexture); + CreateInternalTexture( + AssetArrays->FieldDimensions.X, AssetArrays->FieldDimensions.Y, AssetArrays->FieldDimensions.Z * ScalarTypes.Num(), + AssetArrays->ScalarFieldDatas.GetData(), ScalarFieldTexture); + CreateInternalTexture( + AssetArrays->FieldDimensions.X, AssetArrays->FieldDimensions.Y, AssetArrays->FieldDimensions.Z * IntegerTypes.Num(), + AssetArrays->IntegerFieldDatas.GetData(), IntegerFieldTexture); + } +} + +void FNDIFieldSystemBuffer::ReleaseRHI() +{ + FieldNodesParamsBuffer.Release(); + FieldCommandsNodesBuffer.Release(); + FieldNodesOffsetsBuffer.Release(); + + VectorFieldTexture.Release(); + ScalarFieldTexture.Release(); + IntegerFieldTexture.Release(); +} + +//------------------------------------------------------------------------------------------------------------ + +void FNDIFieldSystemData::Release() +{ + if (FieldSystemBuffer) + { + BeginReleaseResource(FieldSystemBuffer); + ENQUEUE_RENDER_COMMAND(DeleteResource)( + [ParamPointerToRelease = FieldSystemBuffer](FRHICommandListImmediate& RHICmdList) + { + delete ParamPointerToRelease; + }); + FieldSystemBuffer = nullptr; + } +} + +bool FNDIFieldSystemData::Init(UNiagaraDataInterfaceFieldSystem* Interface, FNiagaraSystemInstance* SystemInstance) +{ + FieldSystemBuffer = nullptr; + + if (Interface != nullptr && SystemInstance != nullptr) + { + Interface->ExtractSourceComponent(SystemInstance); + + const FTransform WorldTransform = SystemInstance->GetWorldTransform(); + + FieldSystemBuffer = new FNDIFieldSystemBuffer(); + FieldSystemBuffer->Initialize(Interface->FieldSystems, Interface->SourceComponents, + Interface->FieldDimensions, WorldTransform.GetTranslation() + Interface->MinBounds, + WorldTransform.GetTranslation() + Interface->MaxBounds); + + BeginInitResource(FieldSystemBuffer); + } + + return true; +} + +//------------------------------------------------------------------------------------------------------------ + +struct FNDIFieldSystemParametersCS : public FNiagaraDataInterfaceParametersCS +{ + DECLARE_TYPE_LAYOUT(FNDIFieldSystemParametersCS, NonVirtual); +public: + void Bind(const FNiagaraDataInterfaceGPUParamInfo& ParameterInfo, const class FShaderParameterMap& ParameterMap) + { + FNDIFieldSystemParametersName ParamNames(*ParameterInfo.DataInterfaceHLSLSymbol); + + FieldCommandsNodesBuffer.Bind(ParameterMap, *ParamNames.FieldCommandsNodesBufferName); + FieldNodesParamsBuffer.Bind(ParameterMap, *ParamNames.FieldNodesParamsBufferName); + FieldNodesOffsetsBuffer.Bind(ParameterMap, *ParamNames.FieldNodesOffsetsBufferName); + + VectorFieldTexture.Bind(ParameterMap, *ParamNames.VectorFieldTextureName); + VectorFieldSampler.Bind(ParameterMap, *ParamNames.VectorFieldSamplerName); + + ScalarFieldTexture.Bind(ParameterMap, *ParamNames.ScalarFieldTextureName); + ScalarFieldSampler.Bind(ParameterMap, *ParamNames.ScalarFieldSamplerName); + + IntegerFieldTexture.Bind(ParameterMap, *ParamNames.IntegerFieldTextureName); + IntegerFieldSampler.Bind(ParameterMap, *ParamNames.IntegerFieldSamplerName); + + FieldDimensions.Bind(ParameterMap, *ParamNames.FieldDimensionsName); + MinBounds.Bind(ParameterMap, *ParamNames.MinBoundsName); + MaxBounds.Bind(ParameterMap, *ParamNames.MaxBoundsName); + + if (!FieldNodesParamsBuffer.IsBound()) + { + UE_LOG(LogFieldSystem, Warning, TEXT("Binding failed for FNDIFieldSystemParametersCS %s. Was it optimized out?"), *ParamNames.FieldNodesParamsBufferName) + } + if (!FieldCommandsNodesBuffer.IsBound()) + { + UE_LOG(LogFieldSystem, Warning, TEXT("Binding failed for FNDIFieldSystemParametersCS %s. Was it optimized out?"), *ParamNames.FieldCommandsNodesBufferName) + } + if (!FieldNodesOffsetsBuffer.IsBound()) + { + UE_LOG(LogFieldSystem, Warning, TEXT("Binding failed for FNDIFieldSystemParametersCS %s. Was it optimized out?"), *ParamNames.FieldNodesOffsetsBufferName) + } + } + + void Set(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) const + { + check(IsInRenderingThread()); + + FRHIComputeShader* ComputeShaderRHI = RHICmdList.GetBoundComputeShader(); + + FNDIFieldSystemProxy* InterfaceProxy = + static_cast(Context.DataInterface); + FNDIFieldSystemData* ProxyData = + InterfaceProxy->SystemInstancesToProxyData.Find(Context.SystemInstanceID); + + FRHISamplerState* SamplerState = TStaticSamplerState::GetRHI(); + + if (ProxyData != nullptr && ProxyData->FieldSystemBuffer && ProxyData->FieldSystemBuffer->IsInitialized() + && ProxyData->FieldSystemBuffer->AssetArrays) + { + FNDIFieldSystemBuffer* AssetBuffer = ProxyData->FieldSystemBuffer; + SetSRVParameter(RHICmdList, ComputeShaderRHI, FieldNodesParamsBuffer, AssetBuffer->FieldNodesParamsBuffer.SRV); + SetSRVParameter(RHICmdList, ComputeShaderRHI, FieldCommandsNodesBuffer, AssetBuffer->FieldCommandsNodesBuffer.SRV); + SetSRVParameter(RHICmdList, ComputeShaderRHI, FieldNodesOffsetsBuffer, AssetBuffer->FieldNodesOffsetsBuffer.SRV); + + SetSRVParameter(RHICmdList, ComputeShaderRHI, VectorFieldTexture, AssetBuffer->VectorFieldTexture.SRV); + SetSamplerParameter(RHICmdList, ComputeShaderRHI, VectorFieldSampler, SamplerState); + + SetSRVParameter(RHICmdList, ComputeShaderRHI, ScalarFieldTexture, AssetBuffer->ScalarFieldTexture.SRV); + SetSamplerParameter(RHICmdList, ComputeShaderRHI, ScalarFieldSampler, SamplerState); + + SetSRVParameter(RHICmdList, ComputeShaderRHI, IntegerFieldTexture, AssetBuffer->IntegerFieldTexture.SRV); + SetSamplerParameter(RHICmdList, ComputeShaderRHI, IntegerFieldSampler, SamplerState); + + SetShaderValue(RHICmdList, ComputeShaderRHI, FieldDimensions, AssetBuffer->AssetArrays->FieldDimensions); + SetShaderValue(RHICmdList, ComputeShaderRHI, MinBounds, AssetBuffer->AssetArrays->MinBounds); + SetShaderValue(RHICmdList, ComputeShaderRHI, MaxBounds, AssetBuffer->AssetArrays->MaxBounds); + } + else + { + SetSRVParameter(RHICmdList, ComputeShaderRHI, FieldNodesParamsBuffer, FNiagaraRenderer::GetDummyFloatBuffer()); + SetSRVParameter(RHICmdList, ComputeShaderRHI, FieldCommandsNodesBuffer, FNiagaraRenderer::GetDummyIntBuffer()); + SetSRVParameter(RHICmdList, ComputeShaderRHI, FieldNodesOffsetsBuffer, FNiagaraRenderer::GetDummyIntBuffer()); + + SetSRVParameter(RHICmdList, ComputeShaderRHI, VectorFieldTexture, FNiagaraRenderer::GetDummyTextureReadBuffer2D()); + SetSamplerParameter(RHICmdList, ComputeShaderRHI, VectorFieldSampler, SamplerState); + + SetSRVParameter(RHICmdList, ComputeShaderRHI, ScalarFieldTexture, FNiagaraRenderer::GetDummyTextureReadBuffer2D()); + SetSamplerParameter(RHICmdList, ComputeShaderRHI, ScalarFieldSampler, SamplerState); + + SetSRVParameter(RHICmdList, ComputeShaderRHI, IntegerFieldTexture, FNiagaraRenderer::GetDummyTextureReadBuffer2D()); + SetSamplerParameter(RHICmdList, ComputeShaderRHI, IntegerFieldSampler, SamplerState); + + SetShaderValue(RHICmdList, ComputeShaderRHI, FieldDimensions, FIntVector(1, 1, 1)); + SetShaderValue(RHICmdList, ComputeShaderRHI, MinBounds, FVector(0, 0, 0)); + SetShaderValue(RHICmdList, ComputeShaderRHI, MaxBounds, FVector(0, 0, 0)); + } + } + + void Unset(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) const + { + } + +private: + + LAYOUT_FIELD(FShaderResourceParameter, FieldNodesParamsBuffer); + LAYOUT_FIELD(FShaderResourceParameter, FieldCommandsNodesBuffer); + LAYOUT_FIELD(FShaderResourceParameter, FieldNodesOffsetsBuffer); + + LAYOUT_FIELD(FShaderResourceParameter, VectorFieldTexture); + LAYOUT_FIELD(FShaderResourceParameter, VectorFieldSampler); + + LAYOUT_FIELD(FShaderResourceParameter, ScalarFieldTexture); + LAYOUT_FIELD(FShaderResourceParameter, ScalarFieldSampler); + + LAYOUT_FIELD(FShaderResourceParameter, IntegerFieldTexture); + LAYOUT_FIELD(FShaderResourceParameter, IntegerFieldSampler); + + LAYOUT_FIELD(FShaderParameter, FieldDimensions); + LAYOUT_FIELD(FShaderParameter, MinBounds); + LAYOUT_FIELD(FShaderParameter, MaxBounds); +}; + +IMPLEMENT_TYPE_LAYOUT(FNDIFieldSystemParametersCS); + +IMPLEMENT_NIAGARA_DI_PARAMETER(UNiagaraDataInterfaceFieldSystem, FNDIFieldSystemParametersCS); + + +//------------------------------------------------------------------------------------------------------------ + +void FNDIFieldSystemProxy::ConsumePerInstanceDataFromGameThread(void* PerInstanceData, const FNiagaraSystemInstanceID& Instance) +{ + FNDIFieldSystemData* SourceData = static_cast(PerInstanceData); + FNDIFieldSystemData* TargetData = &(SystemInstancesToProxyData.FindOrAdd(Instance)); + + ensure(TargetData); + if (TargetData) + { + TargetData->FieldSystemBuffer = SourceData->FieldSystemBuffer; + } + else + { + UE_LOG(LogFieldSystem, Log, TEXT("ConsumePerInstanceDataFromGameThread() ... could not find %d"), Instance); + } +} + +void FNDIFieldSystemProxy::InitializePerInstanceData(const FNiagaraSystemInstanceID& SystemInstance) +{ + check(IsInRenderingThread()); + + FNDIFieldSystemData* TargetData = SystemInstancesToProxyData.Find(SystemInstance); + TargetData = &SystemInstancesToProxyData.Add(SystemInstance); +} + +void FNDIFieldSystemProxy::DestroyPerInstanceData(NiagaraEmitterInstanceBatcher* Batcher, const FNiagaraSystemInstanceID& SystemInstance) +{ + check(IsInRenderingThread()); + SystemInstancesToProxyData.Remove(SystemInstance); +} + +//------------------------------------------------------------------------------------------------------------ + +UNiagaraDataInterfaceFieldSystem::UNiagaraDataInterfaceFieldSystem(FObjectInitializer const& ObjectInitializer) + : Super(ObjectInitializer) + , BlueprintSource(nullptr) + , SourceActor(nullptr) + , FieldDimensions(10, 10, 10) + , MinBounds(-50, -50, -50) + , MaxBounds(50, 50, 50) + , SourceComponents() + , FieldSystems() +{ + Proxy.Reset(new FNDIFieldSystemProxy()); +} + +void UNiagaraDataInterfaceFieldSystem::ExtractSourceComponent(FNiagaraSystemInstance* SystemInstance) +{ + TWeakObjectPtr SourceComponent; + if (SourceActor) + { + AFieldSystemActor* FieldSystemActor = Cast(SourceActor); + if (FieldSystemActor != nullptr) + { + SourceComponent = FieldSystemActor->GetFieldSystemComponent(); + } + else + { + SourceComponent = SourceActor->FindComponentByClass(); + } + } + else if (USceneComponent* AttachComponent = SystemInstance->GetAttachComponent()) + { + // First try to find the source component up the attach hierarchy + for (USceneComponent* Curr = AttachComponent; Curr; Curr = Curr->GetAttachParent()) + { + UFieldSystemComponent* SourceComp = Cast(Curr); + if (SourceComp && SourceComp->FieldSystem) + { + SourceComponent = SourceComp; + break; + } + } + + if (!SourceComponent.IsValid()) + { + // Fall back on the outer chain to find the component + if (UFieldSystemComponent* OuterComp = AttachComponent->GetTypedOuter()) + { + SourceComponent = OuterComp; + } + } + } + if (BlueprintSource) + { + AFieldSystemActor* FieldSystemActor = Cast(BlueprintSource->GeneratedClass.GetDefaultObject()); + if (FieldSystemActor != nullptr) + { + SourceComponent = FieldSystemActor->FieldSystemComponent; + } + } + + SourceComponents.Empty(); + FieldSystems.Empty(); + if (SourceComponent != nullptr) + { + SourceComponents.Add(SourceComponent); + FieldSystems.Add(SourceComponent->FieldSystem); + } +} + +bool UNiagaraDataInterfaceFieldSystem::InitPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) +{ + FNDIFieldSystemData* InstanceData = new (PerInstanceData) FNDIFieldSystemData(); + + check(InstanceData); + + return InstanceData->Init(this, SystemInstance); +} + +void UNiagaraDataInterfaceFieldSystem::DestroyPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) +{ + FNDIFieldSystemData* InstanceData = static_cast(PerInstanceData); + + InstanceData->Release(); + InstanceData->~FNDIFieldSystemData(); + + FNDIFieldSystemProxy* ThisProxy = GetProxyAs(); + ENQUEUE_RENDER_COMMAND(FNiagaraDIDestroyInstanceData) ( + [ThisProxy, InstanceID = SystemInstance->GetId(), Batcher = SystemInstance->GetBatcher()](FRHICommandListImmediate& CmdList) + { + ThisProxy->SystemInstancesToProxyData.Remove(InstanceID); + } + ); +} + +bool UNiagaraDataInterfaceFieldSystem::PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float InDeltaSeconds) +{ + FNDIFieldSystemData* InstanceData = static_cast(PerInstanceData); + if (InstanceData->FieldSystemBuffer && SystemInstance) + { + InstanceData->FieldSystemBuffer->Update(); + } + return false; +} + +bool UNiagaraDataInterfaceFieldSystem::CopyToInternal(UNiagaraDataInterface* Destination) const +{ + if (!Super::CopyToInternal(Destination)) + { + return false; + } + + UNiagaraDataInterfaceFieldSystem* OtherTyped = CastChecked(Destination); + OtherTyped->FieldSystems = FieldSystems; + OtherTyped->SourceActor = SourceActor; + OtherTyped->SourceComponents = SourceComponents; + OtherTyped->BlueprintSource = BlueprintSource; + OtherTyped->FieldDimensions = FieldDimensions; + OtherTyped->MinBounds = MinBounds; + OtherTyped->MaxBounds = MaxBounds; + + return true; +} + +bool UNiagaraDataInterfaceFieldSystem::Equals(const UNiagaraDataInterface* Other) const +{ + if (!Super::Equals(Other)) + { + return false; + } + const UNiagaraDataInterfaceFieldSystem* OtherTyped = CastChecked(Other); + + return (OtherTyped->FieldSystems == FieldSystems) && (OtherTyped->SourceActor == SourceActor) && + (OtherTyped->SourceComponents == SourceComponents) + && (OtherTyped->BlueprintSource == BlueprintSource && (OtherTyped->FieldDimensions == FieldDimensions) + && (OtherTyped->MinBounds == MinBounds) && (OtherTyped->MaxBounds == MaxBounds)); +} + +void UNiagaraDataInterfaceFieldSystem::PostInitProperties() +{ + Super::PostInitProperties(); + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + FNiagaraTypeRegistry::Register(FNiagaraTypeDefinition(GetClass()), true, false, false); + } +} + +void UNiagaraDataInterfaceFieldSystem::GetFunctions(TArray& OutFunctions) +{ + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleLinearVelocityName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Linear Velocity"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleAngularVelocityName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Angular Velocity"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleLinearForceName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Linear Force"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleAngularTorqueName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Angular Torque"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SamplePositionTargetName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Position Target"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleExternalClusterStrainName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("External Cluster Strain"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleInternalClusterStrainName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Internal Cluster Strain"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleFieldKillName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Field Kill"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleSleepingThresholdName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Sleeping Threshold"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleDisableThresholdName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Disable Threshold"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleDynamicConstraintName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Dynamic Constraint"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleDynamicStateName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Dynamic State"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleCollisionGroupName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Collision Group"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SamplePositionStaticName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Position Static"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SamplePositionAnimatedName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Position Animated"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = SampleActivateDisabledName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Activate Disabled"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = GetFieldDimensionsName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Field Dimensions"))); + + OutFunctions.Add(Sig); + } + { + FNiagaraFunctionSignature Sig; + Sig.Name = GetFieldBoundsName; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Min Bounds"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Max Bounds"))); + + OutFunctions.Add(Sig); + } +} + +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleLinearVelocity); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleAngularVelocity); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleLinearForce); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleAngularTorque); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SamplePositionTarget); + +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleExternalClusterStrain); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleInternalClusterStrain); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleSleepingThreshold); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleDisableThreshold); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleDynamicConstraint); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleFieldKill); + +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SamplePositionAnimated); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SamplePositionStatic); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleCollisionGroup); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleDynamicState); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleActivateDisabled); + +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, GetFieldDimensions); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, GetFieldBounds); + +void UNiagaraDataInterfaceFieldSystem::GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction& OutFunc) +{ + if (BindingInfo.Name == SampleLinearVelocityName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 3); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleLinearVelocity)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SampleAngularVelocityName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 3); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleAngularVelocity)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SampleLinearForceName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 3); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleLinearForce)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SampleAngularTorqueName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 3); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleAngularTorque)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SamplePositionTargetName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 3); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SamplePositionTarget)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SampleExternalClusterStrainName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 1); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleExternalClusterStrain)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SampleInternalClusterStrainName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 1); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleInternalClusterStrain)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SampleSleepingThresholdName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 1); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleSleepingThreshold)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SampleDisableThresholdName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 1); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleDisableThreshold)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SampleFieldKillName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 1); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleFieldKill)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SampleDynamicConstraintName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 1); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleDynamicConstraint)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SamplePositionAnimatedName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 1); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SamplePositionAnimated)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SamplePositionStaticName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 1); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SamplePositionStatic)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SampleDynamicStateName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 1); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleDynamicState)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SampleCollisionGroupName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 1); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleCollisionGroup)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SampleActivateDisabledName) + { + check(BindingInfo.GetNumInputs() == 4 && BindingInfo.GetNumOutputs() == 1); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleActivateDisabled)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == GetFieldDimensionsName) + { + check(BindingInfo.GetNumInputs() == 1 && BindingInfo.GetNumOutputs() == 3); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleLinearForce)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == GetFieldBoundsName) + { + check(BindingInfo.GetNumInputs() == 1 && BindingInfo.GetNumOutputs() == 6); + NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleAngularTorque)::Bind(this, OutFunc); + } +} + +void UNiagaraDataInterfaceFieldSystem::GetFieldDimensions(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + VectorVM::FExternalFuncRegisterHandler OutDimensionX(Context); + VectorVM::FExternalFuncRegisterHandler OutDimensionY(Context); + VectorVM::FExternalFuncRegisterHandler OutDimensionZ(Context); + + const FIntVector FieldDimension = (InstData && InstData->FieldSystemBuffer && InstData->FieldSystemBuffer->AssetArrays) ? + InstData->FieldSystemBuffer->AssetArrays->FieldDimensions : FIntVector(1, 1, 1); + + for (int32 i = 0; i < Context.NumInstances; ++i) + { + *OutDimensionX.GetDest() = FieldDimension.X; + *OutDimensionY.GetDest() = FieldDimension.Y; + *OutDimensionZ.GetDest() = FieldDimension.Z; + + OutDimensionX.Advance(); + OutDimensionY.Advance(); + OutDimensionZ.Advance(); + } +} + +void UNiagaraDataInterfaceFieldSystem::GetFieldBounds(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + VectorVM::FExternalFuncRegisterHandler OutMinX(Context); + VectorVM::FExternalFuncRegisterHandler OutMinY(Context); + VectorVM::FExternalFuncRegisterHandler OutMinZ(Context); + VectorVM::FExternalFuncRegisterHandler OutMaxX(Context); + VectorVM::FExternalFuncRegisterHandler OutMaxY(Context); + VectorVM::FExternalFuncRegisterHandler OutMaxZ(Context); + + const FVector MinBound = (InstData && InstData->FieldSystemBuffer && InstData->FieldSystemBuffer->AssetArrays) ? + InstData->FieldSystemBuffer->AssetArrays->MinBounds : FVector(0, 0, 0); + + const FVector MaxBound = (InstData && InstData->FieldSystemBuffer && InstData->FieldSystemBuffer->AssetArrays) ? + InstData->FieldSystemBuffer->AssetArrays->MinBounds : FVector(0, 0, 0); + + for (int32 i = 0; i < Context.NumInstances; ++i) + { + *OutMinX.GetDest() = MinBound.X; + *OutMinY.GetDest() = MinBound.Y; + *OutMinZ.GetDest() = MinBound.Z; + *OutMaxX.GetDest() = MaxBound.X; + *OutMaxY.GetDest() = MaxBound.Y; + *OutMaxZ.GetDest() = MaxBound.Z; + + OutMinX.Advance(); + OutMinY.Advance(); + OutMinZ.Advance(); + OutMaxX.Advance(); + OutMaxY.Advance(); + OutMaxZ.Advance(); + } +} + +void SampleVectorField(FVectorVMContext& Context, const EFieldPhysicsType VectorType, const int32 VectorIndex) +{ + VectorVM::FUserPtrHandler InstData(Context); + + // Inputs + VectorVM::FExternalFuncInputHandler SamplePositionXParam(Context); + VectorVM::FExternalFuncInputHandler SamplePositionYParam(Context); + VectorVM::FExternalFuncInputHandler SamplePositionZParam(Context); + + // Outputs... + VectorVM::FExternalFuncRegisterHandler OutVectoreFieldXParam(Context); + VectorVM::FExternalFuncRegisterHandler OutVectoreFieldYParam(Context); + VectorVM::FExternalFuncRegisterHandler OutVectoreFieldZParam(Context); + + const bool HasValidArrays = InstData && InstData->FieldSystemBuffer && InstData->FieldSystemBuffer->AssetArrays && + (InstData->FieldSystemBuffer->AssetArrays->VectorFieldDatas.Num() != 0); + if (HasValidArrays) + { + const FVector MinBounds = InstData->FieldSystemBuffer->AssetArrays->MinBounds; + const FVector MaxBounds = InstData->FieldSystemBuffer->AssetArrays->MaxBounds; + + const FIntVector FieldDimensions = InstData->FieldSystemBuffer->AssetArrays->FieldDimensions; + const int32 TypeSize = FieldDimensions.X * FieldDimensions.Y * FieldDimensions.Z; + + const FVector FieldSize(FieldDimensions.X, FieldDimensions.Y, FieldDimensions.Z * VectorTypes.Num()); + const FVector BoundSize = MaxBounds - MinBounds; + const FVector InverseBounds = (BoundSize.X > 0.0 && BoundSize.Y > 0.0 && BoundSize.Z > 0.0) ? + FVector(1, 1, 1) / BoundSize : FVector(0, 0, 0); + + FVector* FieldData = &InstData->FieldSystemBuffer->AssetArrays->VectorFieldDatas[0]; + + for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) + { + FVector SamplePoint = (FVector(SamplePositionXParam.Get(), + SamplePositionYParam.Get(), + SamplePositionZParam.Get()) - MinBounds) * InverseBounds; + + SamplePoint = FVector(FMath::Clamp(SamplePoint.X, 0.0f, 1.0f), + FMath::Clamp(SamplePoint.Y, 0.0f, 1.0f), + FMath::Clamp(SamplePoint.Z, 0.0f, 1.0f)); + + SamplePoint.Z = (VectorTypes.Num() != 0) ? + (SamplePoint.Z * (1.0 - 1.0 / FieldDimensions.Z) + VectorIndex) / VectorTypes.Num() : SamplePoint.Z; + + SamplePoint = SamplePoint * FieldSize; + + FVector IndexMin(FGenericPlatformMath::FloorToFloat(SamplePoint.X), + FGenericPlatformMath::FloorToFloat(SamplePoint.Y), + FGenericPlatformMath::FloorToFloat(SamplePoint.Z)); + + FVector IndexMax = IndexMin + FVector(1, 1, 1); + FVector V(0, 0, 0); + if (IndexMin.X < FieldSize.X && IndexMin.Y < FieldSize.Y && IndexMin.Z < FieldSize.Z && + IndexMax.X < FieldSize.X && IndexMax.Y < FieldSize.Y && IndexMax.Z < FieldSize.Z) + { + + FVector SampleFraction = SamplePoint - IndexMin; + + FVector V000 = FieldData[int32(IndexMin.X + FieldSize.X * IndexMin.Y + + FieldSize.X * FieldSize.Y * IndexMin.Z)]; + FVector V100 = FieldData[int32(IndexMax.X + FieldSize.X * IndexMin.Y + + FieldSize.X * FieldSize.Y * IndexMin.Z)]; + FVector V010 = FieldData[int32(IndexMin.X + FieldSize.X * IndexMax.Y + + FieldSize.X * FieldSize.Y * IndexMin.Z)]; + FVector V110 = FieldData[int32(IndexMax.X + FieldSize.X * IndexMax.Y + + FieldSize.X * FieldSize.Y * IndexMin.Z)]; + FVector V001 = FieldData[int32(IndexMin.X + FieldSize.X * IndexMin.Y + + FieldSize.X * FieldSize.Y * IndexMax.Z)]; + FVector V101 = FieldData[int32(IndexMax.X + FieldSize.X * IndexMin.Y + + FieldSize.X * FieldSize.Y * IndexMax.Z)]; + FVector V011 = FieldData[int32(IndexMin.X + FieldSize.X * IndexMax.Y + + FieldSize.X * FieldSize.Y * IndexMax.Z)]; + FVector V111 = FieldData[int32(IndexMax.X + FieldSize.X * IndexMax.Y + + FieldSize.X * FieldSize.Y * IndexMax.Z)]; + + // Blend x-axis + FVector V00 = FMath::Lerp(V000, V100, SampleFraction.X); + FVector V01 = FMath::Lerp(V001, V101, SampleFraction.X); + FVector V10 = FMath::Lerp(V010, V110, SampleFraction.X); + FVector V11 = FMath::Lerp(V011, V111, SampleFraction.X); + + // Blend y-axis + FVector V0 = FMath::Lerp(V00, V10, SampleFraction.Y); + FVector V1 = FMath::Lerp(V01, V11, SampleFraction.Y); + + // Blend z-axis + V = FMath::Lerp(V0, V1, SampleFraction.Z); + + //UE_LOG(LogFieldSystem, Warning, TEXT("Instance Index = %d | Sample Position = %s | Sample Result = %s | Sample Index = %d"), + // InstanceIdx, *SamplePoint.ToString(), *V000.ToString(), int32(IndexMin.X + FieldSize.X * IndexMin.Y + + // FieldSize.X * FieldSize.Y * IndexMin.Z)); + } + + // Write final output... + *OutVectoreFieldXParam.GetDest() = V.X; + *OutVectoreFieldYParam.GetDest() = V.Y; + *OutVectoreFieldZParam.GetDest() = V.Z; + + SamplePositionXParam.Advance(); + SamplePositionXParam.Advance(); + SamplePositionXParam.Advance(); + + OutVectoreFieldXParam.Advance(); + OutVectoreFieldYParam.Advance(); + OutVectoreFieldZParam.Advance(); + } + } +} + +void SampleScalarField(FVectorVMContext& Context, const EFieldPhysicsType ScalarType, const int32 VectorIndex) +{ + VectorVM::FUserPtrHandler InstData(Context); + + // Inputs + VectorVM::FExternalFuncInputHandler SamplePositionXParam(Context); + VectorVM::FExternalFuncInputHandler SamplePositionYParam(Context); + VectorVM::FExternalFuncInputHandler SamplePositionZParam(Context); + + // Outputs... + VectorVM::FExternalFuncRegisterHandler OutScalarFieldParam(Context); + + const bool HasValidArrays = InstData && InstData->FieldSystemBuffer && InstData->FieldSystemBuffer->AssetArrays && + (InstData->FieldSystemBuffer->AssetArrays->ScalarFieldDatas.Num() != 0); + if (HasValidArrays) + { + const FVector MinBounds = InstData->FieldSystemBuffer->AssetArrays->MinBounds; + const FVector MaxBounds = InstData->FieldSystemBuffer->AssetArrays->MaxBounds; + + const FIntVector FieldDimensions = InstData->FieldSystemBuffer->AssetArrays->FieldDimensions; + const int32 TypeSize = FieldDimensions.X * FieldDimensions.Y * FieldDimensions.Z; + + const FVector FieldSize(FieldDimensions.X, FieldDimensions.Y, FieldDimensions.Z * ScalarTypes.Num()); + const FVector BoundSize = MaxBounds - MinBounds; + const FVector InverseBounds = (BoundSize.X > 0.0 && BoundSize.Y > 0.0 && BoundSize.Z > 0.0) ? + FVector(1, 1, 1) / BoundSize : FVector(0, 0, 0); + + float* FieldData = &InstData->FieldSystemBuffer->AssetArrays->ScalarFieldDatas[0]; + + for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) + { + FVector SamplePoint = (FVector(SamplePositionXParam.Get(), + SamplePositionYParam.Get(), + SamplePositionZParam.Get()) - MinBounds) * InverseBounds; + + SamplePoint = FVector(FMath::Clamp(SamplePoint.X, 0.0f, 1.0f), + FMath::Clamp(SamplePoint.Y, 0.0f, 1.0f), + FMath::Clamp(SamplePoint.Z, 0.0f, 1.0f)); + + SamplePoint.Z = (ScalarTypes.Num() != 0) ? + (SamplePoint.Z * (1.0 - 1.0 / FieldDimensions.Z) + VectorIndex) / ScalarTypes.Num() : SamplePoint.Z; + + SamplePoint = SamplePoint * FieldSize; + + FVector IndexMin(FGenericPlatformMath::FloorToFloat(SamplePoint.X), + FGenericPlatformMath::FloorToFloat(SamplePoint.Y), + FGenericPlatformMath::FloorToFloat(SamplePoint.Z)); + + FVector IndexMax = IndexMin + FVector(1, 1, 1); + float V = 0.0; + if (IndexMin.X < FieldSize.X && IndexMin.Y < FieldSize.Y && IndexMin.Z < FieldSize.Z && + IndexMax.X < FieldSize.X && IndexMax.Y < FieldSize.Y && IndexMax.Z < FieldSize.Z) + { + + FVector SampleFraction = SamplePoint - IndexMin; + + float V000 = FieldData[int32(IndexMin.X + FieldSize.X * IndexMin.Y + + FieldSize.X * FieldSize.Y * IndexMin.Z)]; + float V100 = FieldData[int32(IndexMax.X + FieldSize.X * IndexMin.Y + + FieldSize.X * FieldSize.Y * IndexMin.Z)]; + float V010 = FieldData[int32(IndexMin.X + FieldSize.X * IndexMax.Y + + FieldSize.X * FieldSize.Y * IndexMin.Z)]; + float V110 = FieldData[int32(IndexMax.X + FieldSize.X * IndexMax.Y + + FieldSize.X * FieldSize.Y * IndexMin.Z)]; + float V001 = FieldData[int32(IndexMin.X + FieldSize.X * IndexMin.Y + + FieldSize.X * FieldSize.Y * IndexMax.Z)]; + float V101 = FieldData[int32(IndexMax.X + FieldSize.X * IndexMin.Y + + FieldSize.X * FieldSize.Y * IndexMax.Z)]; + float V011 = FieldData[int32(IndexMin.X + FieldSize.X * IndexMax.Y + + FieldSize.X * FieldSize.Y * IndexMax.Z)]; + float V111 = FieldData[int32(IndexMax.X + FieldSize.X * IndexMax.Y + + FieldSize.X * FieldSize.Y * IndexMax.Z)]; + + // Blend x-axis + float V00 = FMath::Lerp(V000, V100, SampleFraction.X); + float V01 = FMath::Lerp(V001, V101, SampleFraction.X); + float V10 = FMath::Lerp(V010, V110, SampleFraction.X); + float V11 = FMath::Lerp(V011, V111, SampleFraction.X); + + // Blend y-axis + float V0 = FMath::Lerp(V00, V10, SampleFraction.Y); + float V1 = FMath::Lerp(V01, V11, SampleFraction.Y); + + // Blend z-axis + V = FMath::Lerp(V0, V1, SampleFraction.Z); + + //UE_LOG(LogFieldSystem, Warning, TEXT("Instance Index = %d | Sample Position = %s | Sample Result = %s | Sample Index = %d"), + // InstanceIdx, *SamplePoint.ToString(), *V000.ToString(), int32(IndexMin.X + FieldSize.X * IndexMin.Y + + // FieldSize.X * FieldSize.Y * IndexMin.Z)); + } + + // Write final output... + *OutScalarFieldParam.GetDest() = V; + + SamplePositionXParam.Advance(); + SamplePositionXParam.Advance(); + SamplePositionXParam.Advance(); + + OutScalarFieldParam.Advance(); + } + } +} + + +void SampleIntegerField(FVectorVMContext& Context, const EFieldPhysicsType ScalarType, const int32 VectorIndex) +{ + VectorVM::FUserPtrHandler InstData(Context); + + // Inputs + VectorVM::FExternalFuncInputHandler SamplePositionXParam(Context); + VectorVM::FExternalFuncInputHandler SamplePositionYParam(Context); + VectorVM::FExternalFuncInputHandler SamplePositionZParam(Context); + + // Outputs... + VectorVM::FExternalFuncRegisterHandler OutIntegerFieldParam(Context); + + const bool HasValidArrays = InstData && InstData->FieldSystemBuffer && InstData->FieldSystemBuffer->AssetArrays && + (InstData->FieldSystemBuffer->AssetArrays->IntegerFieldDatas.Num() != 0); + if (HasValidArrays) + { + const FVector MinBounds = InstData->FieldSystemBuffer->AssetArrays->MinBounds; + const FVector MaxBounds = InstData->FieldSystemBuffer->AssetArrays->MaxBounds; + + const FIntVector FieldDimensions = InstData->FieldSystemBuffer->AssetArrays->FieldDimensions; + const int32 TypeSize = FieldDimensions.X * FieldDimensions.Y * FieldDimensions.Z; + + const FVector FieldSize(FieldDimensions.X, FieldDimensions.Y, FieldDimensions.Z * IntegerTypes.Num()); + const FVector BoundSize = MaxBounds - MinBounds; + const FVector InverseBounds = (BoundSize.X > 0.0 && BoundSize.Y > 0.0 && BoundSize.Z > 0.0) ? + FVector(1, 1, 1) / BoundSize : FVector(0, 0, 0); + + int32* FieldData = &InstData->FieldSystemBuffer->AssetArrays->IntegerFieldDatas[0]; + + for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) + { + FVector SamplePoint = (FVector(SamplePositionXParam.Get(), + SamplePositionYParam.Get(), + SamplePositionZParam.Get()) - MinBounds) * InverseBounds; + + SamplePoint = FVector(FMath::Clamp(SamplePoint.X, 0.0f, 1.0f), + FMath::Clamp(SamplePoint.Y, 0.0f, 1.0f), + FMath::Clamp(SamplePoint.Z, 0.0f, 1.0f)); + + SamplePoint.Z = (IntegerTypes.Num() != 0) ? + (SamplePoint.Z * (1.0 - 1.0 / FieldDimensions.Z) + VectorIndex) / IntegerTypes.Num() : SamplePoint.Z; + + SamplePoint = SamplePoint * FieldSize; + + FVector IndexMin(FGenericPlatformMath::FloorToFloat(SamplePoint.X), + FGenericPlatformMath::FloorToFloat(SamplePoint.Y), + FGenericPlatformMath::FloorToFloat(SamplePoint.Z)); + + FVector IndexMax = IndexMin + FVector(1, 1, 1); + float V = 0.0; + if (IndexMin.X < FieldSize.X && IndexMin.Y < FieldSize.Y && IndexMin.Z < FieldSize.Z && + IndexMax.X < FieldSize.X && IndexMax.Y < FieldSize.Y && IndexMax.Z < FieldSize.Z) + { + + FVector SampleFraction = SamplePoint - IndexMin; + + float V000 = FieldData[int32(IndexMin.X + FieldSize.X * IndexMin.Y + + FieldSize.X * FieldSize.Y * IndexMin.Z)]; + float V100 = FieldData[int32(IndexMax.X + FieldSize.X * IndexMin.Y + + FieldSize.X * FieldSize.Y * IndexMin.Z)]; + float V010 = FieldData[int32(IndexMin.X + FieldSize.X * IndexMax.Y + + FieldSize.X * FieldSize.Y * IndexMin.Z)]; + float V110 = FieldData[int32(IndexMax.X + FieldSize.X * IndexMax.Y + + FieldSize.X * FieldSize.Y * IndexMin.Z)]; + float V001 = FieldData[int32(IndexMin.X + FieldSize.X * IndexMin.Y + + FieldSize.X * FieldSize.Y * IndexMax.Z)]; + float V101 = FieldData[int32(IndexMax.X + FieldSize.X * IndexMin.Y + + FieldSize.X * FieldSize.Y * IndexMax.Z)]; + float V011 = FieldData[int32(IndexMin.X + FieldSize.X * IndexMax.Y + + FieldSize.X * FieldSize.Y * IndexMax.Z)]; + float V111 = FieldData[int32(IndexMax.X + FieldSize.X * IndexMax.Y + + FieldSize.X * FieldSize.Y * IndexMax.Z)]; + + // Blend x-axis + float V00 = FMath::Lerp(V000, V100, SampleFraction.X); + float V01 = FMath::Lerp(V001, V101, SampleFraction.X); + float V10 = FMath::Lerp(V010, V110, SampleFraction.X); + float V11 = FMath::Lerp(V011, V111, SampleFraction.X); + + // Blend y-axis + float V0 = FMath::Lerp(V00, V10, SampleFraction.Y); + float V1 = FMath::Lerp(V01, V11, SampleFraction.Y); + + // Blend z-axis + V = FMath::Lerp(V0, V1, SampleFraction.Z); + + //UE_LOG(LogFieldSystem, Warning, TEXT("Instance Index = %d | Sample Position = %s | Sample Result = %s | Sample Index = %d"), + // InstanceIdx, *SamplePoint.ToString(), *V000.ToString(), int32(IndexMin.X + FieldSize.X * IndexMin.Y + + // FieldSize.X * FieldSize.Y * IndexMin.Z)); + } + + // Write final output... + *OutIntegerFieldParam.GetDest() = V; + + SamplePositionXParam.Advance(); + SamplePositionXParam.Advance(); + SamplePositionXParam.Advance(); + + OutIntegerFieldParam.Advance(); + } + } +} + +void UNiagaraDataInterfaceFieldSystem::SampleLinearVelocity(FVectorVMContext& Context) +{ + SampleVectorField(Context, EFieldPhysicsType::Field_LinearVelocity, + EFieldVectorIndices::Vector_LinearVelocity); +} + +void UNiagaraDataInterfaceFieldSystem::SampleAngularVelocity(FVectorVMContext& Context) +{ + SampleVectorField(Context, EFieldPhysicsType::Field_AngularVelociy, + EFieldVectorIndices::Vector_AngularVelocity); +} + +void UNiagaraDataInterfaceFieldSystem::SampleLinearForce(FVectorVMContext& Context) +{ + SampleVectorField(Context, EFieldPhysicsType::Field_LinearForce, + EFieldVectorIndices::Vector_LinearForce); +} + +void UNiagaraDataInterfaceFieldSystem::SampleAngularTorque(FVectorVMContext& Context) +{ + SampleVectorField(Context, EFieldPhysicsType::Field_AngularTorque, + EFieldVectorIndices::Vector_AngularTorque); +} + +void UNiagaraDataInterfaceFieldSystem::SamplePositionTarget(FVectorVMContext& Context) +{ + SampleVectorField(Context, EFieldPhysicsType::Field_PositionStatic, + EFieldVectorIndices::Vector_PositionTarget); +} + +void UNiagaraDataInterfaceFieldSystem::SampleExternalClusterStrain(FVectorVMContext& Context) +{ + SampleScalarField(Context, EFieldPhysicsType::Field_ExternalClusterStrain, + EFieldScalarIndices::Scalar_ExternalClusterStrain); +} + +void UNiagaraDataInterfaceFieldSystem::SampleInternalClusterStrain(FVectorVMContext& Context) +{ + SampleScalarField(Context, EFieldPhysicsType::Field_InternalClusterStrain, + EFieldScalarIndices::Scalar_InternalClusterStrain); +} + +void UNiagaraDataInterfaceFieldSystem::SampleSleepingThreshold(FVectorVMContext& Context) +{ + SampleScalarField(Context, EFieldPhysicsType::Field_SleepingThreshold, + EFieldScalarIndices::Scalar_SleepingThreshold); +} + +void UNiagaraDataInterfaceFieldSystem::SampleDisableThreshold(FVectorVMContext& Context) +{ + SampleScalarField(Context, EFieldPhysicsType::Field_DisableThreshold, + EFieldScalarIndices::Scalar_DisableThreshold); +} + +void UNiagaraDataInterfaceFieldSystem::SampleFieldKill(FVectorVMContext& Context) +{ + SampleScalarField(Context, EFieldPhysicsType::Field_Kill, + EFieldScalarIndices::Scalar_Kill); +} + +void UNiagaraDataInterfaceFieldSystem::SampleDynamicConstraint(FVectorVMContext& Context) +{ + SampleScalarField(Context, EFieldPhysicsType::Field_DynamicConstraint, + EFieldScalarIndices::Scalar_DynamicConstraint); +} + +void UNiagaraDataInterfaceFieldSystem::SampleDynamicState(FVectorVMContext& Context) +{ + SampleIntegerField(Context, EFieldPhysicsType::Field_DynamicState, + EFieldIntegerIndices::Integer_DynamicState); +} + +void UNiagaraDataInterfaceFieldSystem::SampleCollisionGroup(FVectorVMContext& Context) +{ + SampleIntegerField(Context, EFieldPhysicsType::Field_CollisionGroup, + EFieldIntegerIndices::Integer_CollisionGroup); +} + +void UNiagaraDataInterfaceFieldSystem::SamplePositionStatic(FVectorVMContext& Context) +{ + SampleIntegerField(Context, EFieldPhysicsType::Field_PositionStatic, + EFieldIntegerIndices::Integer_PositionStatic); +} + +void UNiagaraDataInterfaceFieldSystem::SamplePositionAnimated(FVectorVMContext& Context) +{ + SampleIntegerField(Context, EFieldPhysicsType::Field_PositionAnimated, + EFieldIntegerIndices::Integer_PositionAnimated); +} + +void UNiagaraDataInterfaceFieldSystem::SampleActivateDisabled(FVectorVMContext& Context) +{ + SampleIntegerField(Context, EFieldPhysicsType::Field_ActivateDisabled, + EFieldIntegerIndices::Integer_ActivateDisabled); +} + +bool UNiagaraDataInterfaceFieldSystem::GetFunctionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, const FNiagaraDataInterfaceGeneratedFunction& FunctionInfo, int FunctionInstanceIndex, FString& OutHLSL) +{ + FNDIFieldSystemParametersName ParamNames(ParamInfo.DataInterfaceHLSLSymbol); + + TMap ArgsSample = { + {TEXT("InstanceFunctionName"), FunctionInfo.InstanceName}, + {TEXT("FieldSystemContextName"), TEXT("DIFieldSystem_MAKE_CONTEXT(") + ParamInfo.DataInterfaceHLSLSymbol + TEXT(")")}, + }; + + if (FunctionInfo.DefinitionName == SampleLinearVelocityName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float3 OutLinearVelocity) + { + {FieldSystemContextName} + OutLinearVelocity = DIFieldSystem_SampleFieldVector(DIContext,SamplePosition,LINEAR_VELOCITY,VECTOR_LINEARVELOCITY); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SampleLinearForceName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float3 OutLinearForce) + { + {FieldSystemContextName} + OutLinearForce = DIFieldSystem_SampleFieldVector(DIContext,SamplePosition,LINEAR_FORCE,VECTOR_LINEARFORCE); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SampleAngularVelocityName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float3 OutAngularVelocity) + { + {FieldSystemContextName} + OutAngularVelocity = DIFieldSystem_SampleFieldVector(DIContext,SamplePosition,ANGULAR_VELOCITY,VECTOR_ANGULARVELOCITY); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SampleAngularTorqueName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float3 OutAngularTorque) + { + {FieldSystemContextName} + OutAngularTorque = DIFieldSystem_SampleFieldVector(DIContext,SamplePosition,ANGULAR_TORQUE,VECTOR_ANGULARTORQUE); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SamplePositionTargetName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float3 OutPositionTarget) + { + {FieldSystemContextName} + OutPositionTorque = DIFieldSystem_SampleFieldVector(DIContext,SamplePosition,POSITION_TARGET,VECTOR_POSITIONTARGET); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SampleExternalClusterStrainName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float OutExternalClusterStrain) + { + {FieldSystemContextName} + OutExternalClusterStrain = DIFieldSystem_SampleFieldScalar(DIContext,SamplePosition,EXTERNAL_CLUSTER_STRAIN,SCALAR_EXTERNALCLUSTERSTRAIN); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SampleFieldKillName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float OutFieldKill) + { + {FieldSystemContextName} + OutFieldKill = DIFieldSystem_SampleFieldScalar(DIContext,SamplePosition,FIELD_KILL,SCALAR_FIELDKILL); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SampleSleepingThresholdName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float OutSleepingThreshold) + { + {FieldSystemContextName} + OutSleepingThreshold = DIFieldSystem_SampleFieldScalar(DIContext,SamplePosition,SLEEPING_THRESHOLD,SCALAR_SLEEPINGTHRESHOLD); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SampleDisableThresholdName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float OutDisableThreshold) + { + {FieldSystemContextName} + OutSleepingThreshold = DIFieldSystem_SampleFieldScalar(DIContext,SamplePosition,DISABLE_THRESHOLD,SCALAR_DISABLETHRESHOLD); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SampleInternalClusterStrainName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float OutInternalClusterStrain) + { + {FieldSystemContextName} + OutInternalClusterStrain = DIFieldSystem_SampleFieldScalar(DIContext,SamplePosition,INTERNAL_CLUSTER_STRAIN,SCALAR_INTERNALCLUSTERSTRAIN); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SampleDynamicConstraintName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float OutDynamicConstraint) + { + {FieldSystemContextName} + OutDynamicConstraint = DIFieldSystem_SampleFieldScalar(DIContext,SamplePosition,DYNAMIC_CONSTRAINT,SCALAR_DYNAMICCONSTRAINT); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SampleDynamicStateName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out int OutDynamicState) + { + {FieldSystemContextName} + OutDynamicState = DIFieldSystem_SampleFieldInteger(DIContext,SamplePosition,DYNAMIC_STATE,INTEGER_DYNAMICSTATE); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SampleActivateDisabledName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out int OutActivateDisabled) + { + {FieldSystemContextName} + OutActivateDisabled = DIFieldSystem_SampleFieldInteger(DIContext,SamplePosition,ACTIVATE_DISABLED,INTEGER_ACTIVATEDISABLED); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SampleCollisionGroupName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out int OutCollisionGroup) + { + {FieldSystemContextName} + OutCollisionGroup = DIFieldSystem_SampleFieldInteger(DIContext,SamplePosition,COLLISION_GROUP,INTEGER_COLLISIONGROUP); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SamplePositionAnimatedName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out int OutPositionAnimated) + { + {FieldSystemContextName} + OutPositionAnimated = DIFieldSystem_SampleFieldInteger(DIContext,SamplePosition,POSITION_ANIMATED,INTEGER_POSITIONANIMATED); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == SamplePositionStaticName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out int OutPositionStatic) + { + {FieldSystemContextName} + OutPositionStatic = DIFieldSystem_SampleFieldInteger(DIContext,SamplePosition,POSITION_STATIC,INTEGER_POSITIONSTATIC); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == GetFieldDimensionsName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float3 OutFieldDimensions) + { + {FieldSystemContextName} + OutFieldDimensions = DIContext.FieldDimensions); + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + else if (FunctionInfo.DefinitionName == GetFieldBoundsName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {InstanceFunctionName}(in float3 SamplePosition, out float3 OutMinBounds, out float3 OutMaxBounds) + { + {FieldSystemContextName} + OutMinBounds = DIContext.MinBounds; + OutMaxBounds = DICOntext.MaxBounds; + } + )"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + return true; + } + + OutHLSL += TEXT("\n"); + return false; +} + +void UNiagaraDataInterfaceFieldSystem::GetCommonHLSL(FString& OutHLSL) +{ + OutHLSL += TEXT("#include \"/Plugin/Experimental/ChaosNiagara/NiagaraDataInterfaceFieldSystem.ush\"\n"); +} + +void UNiagaraDataInterfaceFieldSystem::GetParameterDefinitionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) +{ + OutHLSL += TEXT("DIFieldSystem_DECLARE_CONSTANTS(") + ParamInfo.DataInterfaceHLSLSymbol + TEXT(")\n"); +} + +void UNiagaraDataInterfaceFieldSystem::ProvidePerInstanceDataForRenderThread(void* DataForRenderThread, void* PerInstanceData, const FNiagaraSystemInstanceID& SystemInstance) +{ + FNDIFieldSystemData* GameThreadData = static_cast(PerInstanceData); + FNDIFieldSystemData* RenderThreadData = static_cast(DataForRenderThread); + + if (GameThreadData != nullptr && RenderThreadData != nullptr) + { + RenderThreadData->FieldSystemBuffer = GameThreadData->FieldSystemBuffer; + } + check(Proxy); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosTireConfig.cpp b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosTireConfig.cpp deleted file mode 100644 index f281a975f501..000000000000 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosTireConfig.cpp +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "ChaosTireConfig.h" -#include "EngineDefines.h" -#include "PhysicalMaterials/PhysicalMaterial.h" - -#include "ChaosVehicleManager.h" - -TArray> UChaosTireConfig::AllTireConfigs; - -UChaosTireConfig::UChaosTireConfig() -{ - // Property initialization - FrictionScale = 1.0f; -} - -void UChaosTireConfig::SetFrictionScale(float NewFrictionScale) -{ - if (NewFrictionScale != FrictionScale) - { - FrictionScale = NewFrictionScale; - - NotifyTireFrictionUpdated(); - } -} - -void UChaosTireConfig::SetPerMaterialFrictionScale(UPhysicalMaterial* PhysicalMaterial, float NewFrictionScale) -{ - // See if we already have an entry for this material - bool bFoundEntry = false; - for (FTireFrictionPerMaterial MatFriction : TireFrictionScales) - { - if (MatFriction.PhysicalMaterial == PhysicalMaterial) - { - // We do, update it - MatFriction.FrictionScale = NewFrictionScale; - bFoundEntry = true; - break; - } - } - - // We don't have an entry, add one - if (!bFoundEntry) - { - FTireFrictionPerMaterial MatFriction; - MatFriction.PhysicalMaterial = PhysicalMaterial; - MatFriction.FrictionScale = NewFrictionScale; - TireFrictionScales.Add(MatFriction); - } - - // Update friction table - NotifyTireFrictionUpdated(); -} - - -void UChaosTireConfig::PostInitProperties() -{ - if (!HasAnyFlags(RF_ClassDefaultObject)) - { - // Set our TireConfigID - either by finding an available slot or creating a new one - int32 TireConfigIndex = AllTireConfigs.Find(NULL); - - if (TireConfigIndex == INDEX_NONE) - { - TireConfigIndex = AllTireConfigs.Add(this); - } - else - { - AllTireConfigs[TireConfigIndex] = this; - } - - TireConfigID = (int32)TireConfigIndex; - - NotifyTireFrictionUpdated(); - } - - Super::PostInitProperties(); -} - -void UChaosTireConfig::BeginDestroy() -{ - if (!HasAnyFlags(RF_ClassDefaultObject)) - { - // free our TireTypeID - check(AllTireConfigs.IsValidIndex(TireConfigID)); - check(AllTireConfigs[TireConfigID] == this); - AllTireConfigs[TireConfigID] = NULL; - - NotifyTireFrictionUpdated(); - } - - Super::BeginDestroy(); -} - -void UChaosTireConfig::NotifyTireFrictionUpdated() -{ -// FChaosVehicleManager::UpdateTireFrictionTable(); -} - -#if WITH_EDITOR -void UChaosTireConfig::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - NotifyTireFrictionUpdated(); -} -#endif //WITH_EDITOR - -float UChaosTireConfig::GetTireFriction(UPhysicalMaterial* PhysicalMaterial) -{ - // Get friction from tire config - float Friction = (PhysicalMaterial != nullptr) ? PhysicalMaterial->Friction : 1.f; - - // Scale by tire config scale - Friction *= FrictionScale; - - // See if we have a material-specific scale as well - for (FTireFrictionPerMaterial MatFriction : TireFrictionScales) - { - if (MatFriction.PhysicalMaterial == PhysicalMaterial) - { - Friction *= MatFriction.FrictionScale; - break; - } - } - - return Friction; -} - - diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleManager.cpp b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleManager.cpp index 114d6cb4cb0b..3ef124459af0 100644 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleManager.cpp +++ b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleManager.cpp @@ -2,12 +2,10 @@ #include "ChaosVehicleManager.h" #include "UObject/UObjectIterator.h" -#include "ChaosTireConfig.h" #include "PhysicalMaterials/PhysicalMaterial.h" #include "Physics/PhysicsFiltering.h" #include "Physics/PhysicsInterfaceCore.h" -#include "VehicleContactModification.h" DECLARE_STATS_GROUP(TEXT("ChaosVehicleManager"), STATGROUP_ChaosVehicleManager, STATGROUP_Advanced); @@ -17,9 +15,6 @@ DECLARE_CYCLE_STAT(TEXT("TickVehicles"), STAT_ChaosVehicleManager_TickVehicles, DECLARE_CYCLE_STAT(TEXT("VehicleManagerUpdate"), STAT_ChaosVehicleManager_Update, STATGROUP_ChaosVehicleManager); DECLARE_CYCLE_STAT(TEXT("PretickVehicles"), STAT_ChaosVehicleManager_PretickVehicles, STATGROUP_Physics); -int GSlowFrameRate = 0; -FAutoConsoleVariableRef CVarChaosVehiclesFlowFrameRate(TEXT("p.Vehicle.SlowFrameRate"), GSlowFrameRate, TEXT("Enable/Disable Debug Slowing of the frame rate to under 30 FPS.")); - TMap FChaosVehicleManager::SceneToVehicleManagerMap; uint32 FChaosVehicleManager::VehicleSetupTag = 0; @@ -35,10 +30,6 @@ FChaosVehicleManager::FChaosVehicleManager(FPhysScene* PhysScene) // Add to Scene-To-Manager map FChaosVehicleManager::SceneToVehicleManagerMap.Add(PhysScene, this); - -#if WITH_CHAOS - FPhysScene::CollisionModifierCallback = FVehicleContactModificationFactory::Create(); -#endif } void FChaosVehicleManager::DetachFromPhysScene(FPhysScene* PhysScene) @@ -100,17 +91,10 @@ void FChaosVehicleManager::Update(FPhysScene* PhysScene, float DeltaTime) return; } - // Debug slowing of frame rate so we can check handling is not affected - if (GSlowFrameRate) - { - FPlatformProcess::Sleep(1.f / (float)GSlowFrameRate); - } - // Suspension raycasts { //SCOPE_CYCLE_COUNTER(STAT_ChaosVehicleManager_VehicleSuspensionRaycasts); - // #todo: batch all the vehicle raycasts here - // #todo: possibly cache raycasts. Seen x4 factor improvement in scene query perf in previous games + // possibly batch all the vehicle raycasts? } // Tick vehicles diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleMovementComponent.cpp b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleMovementComponent.cpp index 28a954d6d320..c363a148c756 100644 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleMovementComponent.cpp +++ b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleMovementComponent.cpp @@ -20,7 +20,6 @@ #include "Physics/PhysicsInterfaceUtils.h" #include "GameFramework/PawnMovementComponent.h" #include "Logging/MessageLog.h" -#include "ChaosTireConfig.h" #include "DisplayDebugHelpers.h" #include "ChaosVehicleManager.h" @@ -47,19 +46,25 @@ FAutoConsoleVariableRef CVarChaosVehiclesShowCOM(TEXT("p.Vehicle.ShowCOM"), GVeh FAutoConsoleVariableRef CVarChaosVehiclesShowModelAxis(TEXT("p.Vehicle.ShowModelOrigin"), GVehicleDebugParams.ShowModelOrigin, TEXT("Enable/Disable Model Origin Visualisation.")); FAutoConsoleVariableRef CVarChaosVehiclesShowAllForces(TEXT("p.Vehicle.ShowAllForces"), GVehicleDebugParams.ShowAllForces, TEXT("Enable/Disable Force Visualisation.")); FAutoConsoleVariableRef CVarChaosVehiclesAerofoilForces(TEXT("p.Vehicle.ShowAerofoilForces"), GVehicleDebugParams.ShowAerofoilForces, TEXT("Enable/Disable Aerofoil Force Visualisation.")); -FAutoConsoleVariableRef CVarChaosVehiclesAerofoilSurface(TEXT("p.Vehicle.ShowAerofoilSurface"), GVehicleDebugParams.ShowAerofoilSurface, TEXT("Enable/Disable Aerofoil Surface Visualisation.")); +FAutoConsoleVariableRef CVarChaosVehiclesAerofoilSurface(TEXT("p.Vehicle.ShowAerofoilSurface"), GVehicleDebugParams.ShowAerofoilSurface, TEXT("Enable/Disable a very approximate visualisation of where the Aerofoil surface is located and its orientation.")); FAutoConsoleVariableRef CVarChaosVehiclesDisableTorqueControl(TEXT("p.Vehicle.DisableTorqueControl"), GVehicleDebugParams.DisableTorqueControl, TEXT("Enable/Disable Direct Torque Control.")); FAutoConsoleVariableRef CVarChaosVehiclesDisableStabilizeControl(TEXT("p.Vehicle.DisableStabilizeControl"), GVehicleDebugParams.DisableStabilizeControl, TEXT("Enable/Disable Position Stabilization Control.")); FAutoConsoleVariableRef CVarChaosVehiclesDisableAerodynamics(TEXT("p.Vehicle.DisableAerodynamics"), GVehicleDebugParams.DisableAerodynamics, TEXT("Enable/Disable Aerodynamic Forces Drag/Downforce.")); +FAutoConsoleVariableRef CVarChaosVehiclesDisableAerofoils(TEXT("p.Vehicle.DisableAerofoils"), GVehicleDebugParams.DisableAerofoils, TEXT("Enable/Disable Aerofoil Forces.")); +FAutoConsoleVariableRef CVarChaosVehiclesDisableThrusters(TEXT("p.Vehicle.DisableThrusters"), GVehicleDebugParams.DisableThrusters, TEXT("Enable/Disable Thruster Forces.")); FAutoConsoleVariableRef CVarChaosVehiclesBatchQueries(TEXT("p.Vehicle.BatchQueries"), GVehicleDebugParams.BatchQueries, TEXT("Enable/Disable Batching Of Suspension Raycasts.")); FAutoConsoleVariableRef CVarChaosVehiclesForceDebugScaling(TEXT("p.Vehicle.SetForceDebugScaling"), GVehicleDebugParams.ForceDebugScaling, TEXT("Set Scaling For Force Visualisation.")); -FAutoConsoleVariableRef CVarChaosVehiclesPersistDebugLinesTime(TEXT("p.Vehicle.SetPersistDebugLinesTime"), GVehicleDebugParams.PersistDebugLinesTime, TEXT("Set Debug Line Persistence Time For Force Visualisation.")); +FAutoConsoleVariableRef CVarChaosVehiclesSleepCounterThreshold(TEXT("p.Vehicle.SleepCounterThreshold"), GVehicleDebugParams.SleepCounterThreshold, TEXT("Set The Sleep Counter Iteration Threshold.")); void FVehicleState::CaptureState(FBodyInstance* TargetInstance, float GravityZ, float DeltaTime) { if (TargetInstance) { + VehicleUpAxis = VehicleWorldTransform.GetUnitAxis(EAxis::Z); + VehicleForwardAxis = VehicleWorldTransform.GetUnitAxis(EAxis::X); + VehicleRightAxis = VehicleWorldTransform.GetUnitAxis(EAxis::Y); + VehicleWorldTransform = TargetInstance->GetUnrealWorldTransform(); VehicleWorldVelocity = TargetInstance->GetUnrealWorldVelocity(); VehicleWorldAngularVelocity = TargetInstance->GetUnrealWorldAngularVelocityInRadians(); @@ -67,20 +72,12 @@ void FVehicleState::CaptureState(FBodyInstance* TargetInstance, float GravityZ, WorldVelocityNormal = VehicleWorldVelocity.GetSafeNormal(); VehicleLocalVelocity = VehicleWorldTransform.InverseTransformVector(VehicleWorldVelocity); - LocalAcceleration = (VehicleLocalVelocity - LastFrameVehicleLocalVelocity) / DeltaTime; LocalGForce = LocalAcceleration / FMath::Abs(GravityZ); LastFrameVehicleLocalVelocity = VehicleLocalVelocity; - - VehicleUpAxis = VehicleWorldTransform.GetUnitAxis(EAxis::Z); - VehicleForwardAxis = VehicleWorldTransform.GetUnitAxis(EAxis::X); - VehicleRightAxis = VehicleWorldTransform.GetUnitAxis(EAxis::Y); - ForwardSpeed = FVector::DotProduct(VehicleWorldVelocity, VehicleForwardAxis); ForwardsAcceleration = LocalAcceleration.X; - - // bVehicleInAir is determined elsewhere } } @@ -94,8 +91,12 @@ UChaosVehicleMovementComponent::UChaosVehicleMovementComponent(const FObjectInit DragCoefficient = 0.3f; DownforceCoefficient = 0.3f; InertiaTensorScale = FVector( 1.0f, 1.0f, 1.0f ); + SleepThreshold = 10.0f; + SleepSlopeLimit = 0.866f; // 30 degrees, Cos(30) TorqueControl.InitDefaults(); + TargetRotationControl.InitDefaults(); + StabilizeControl.InitDefaults(); AngErrorAccumulator = 0.0f; @@ -106,16 +107,16 @@ UChaosVehicleMovementComponent::UChaosVehicleMovementComponent(const FObjectInit ThrottleInputRate.FallRate = 10.0f; BrakeInputRate.RiseRate = 6.0f; BrakeInputRate.FallRate = 10.0f; + SteeringInputRate.RiseRate = 2.5f; + SteeringInputRate.FallRate = 5.0f; + HandbrakeInputRate.RiseRate = 12.0f; + HandbrakeInputRate.FallRate = 12.0f; PitchInputRate.RiseRate = 6.0f; PitchInputRate.FallRate = 10.0f; RollInputRate.RiseRate = 6.0f; RollInputRate.FallRate = 10.0f; YawInputRate.RiseRate = 6.0f; YawInputRate.FallRate = 10.0f; - HandbrakeInputRate.RiseRate = 12.0f; - HandbrakeInputRate.FallRate = 12.0f; - SteeringInputRate.RiseRate = 2.5f; - SteeringInputRate.FallRate = 5.0f; SetIsReplicatedByDefault(true); @@ -136,8 +137,6 @@ void UChaosVehicleMovementComponent::PostEditChangeProperty(FPropertyChangedEven { Super::PostEditChangeProperty(PropertyChangedEvent); - CopyToSolverSafeContactStaticData(); - // Trigger a runtime rebuild of the Chaos vehicle FChaosVehicleManager::VehicleSetupTag++; } @@ -208,13 +207,6 @@ bool UChaosVehicleMovementComponent::CanCreateVehicle() const return false; } - // #Note: As of 22/06/2020 BodyInstance is no longer valid at this point in the Init process - //if (UpdatedPrimitive->GetBodyInstance() == NULL) - //{ - // UE_LOG(LogVehicle, Warning, TEXT("Can't create vehicle %s (%s). UpdatedComponent has not initialized its rigid body actor."), *ActorName, *GetPathName()); - // return false; - //} - return true; } @@ -233,7 +225,7 @@ void UChaosVehicleMovementComponent::OnCreatePhysicsState() if (PhysScene && FChaosVehicleManager::GetVehicleManagerFromScene(PhysScene)) { - //FixupSkeletalMesh(); + FixupSkeletalMesh(); CreateVehicle(); if (PVehicle) @@ -247,23 +239,9 @@ void UChaosVehicleMovementComponent::OnCreatePhysicsState() FBodyInstance* BodyInstance = nullptr; if (USkeletalMeshComponent* SkeletalMesh = GetSkeletalMesh()) { - // #todo: should we touch these SkeletalMesh->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered; - SkeletalMesh->SetNotifyRigidBodyCollision(true); - SkeletalMesh->SetEnablePhysicsBlending(false); //?? - BodyInstance = &SkeletalMesh->BodyInstance; } -// else if (UStaticMeshComponent* StaticMesh = GetStaticMesh()) -// { -// BodyInstance = &StaticMesh->BodyInstance; -// } - - // #todo: should we touch these - //BodyInstance.bStartAwake = false; - //BodyInstance.bGenerateWakeEvents = true; - //BodyInstance.bContactModification = true; // #todo: put this on a param - expose implementation - //BodyInstance.bUseCCD = true; // #todo: put this on a param } void UChaosVehicleMovementComponent::OnDestroyPhysicsState() @@ -303,11 +281,6 @@ void UChaosVehicleMovementComponent::PreTick(float DeltaTime) void UChaosVehicleMovementComponent::TickVehicle(float DeltaTime) { - //if (AvoidanceLockTimer > 0.0f) - //{ - // AvoidanceLockTimer -= DeltaTime; - //} - // movement updates and replication FBodyInstance* TargetInstance = GetBodyInstance(); if (PVehicle && UpdatedComponent && TargetInstance) @@ -315,36 +288,18 @@ void UChaosVehicleMovementComponent::TickVehicle(float DeltaTime) APawn* MyOwner = Cast(UpdatedComponent->GetOwner()); if (MyOwner) { - // Wake if control input pressed - #note : some other input might wake the vehicle also - if (!TargetInstance->IsInstanceAwake() && RawThrottleInput > SMALL_NUMBER) - { - VehicleState.bSleeping = false; - TargetInstance->WakeInstance(); - } + ProcessSleeping(); if (!VehicleState.bSleeping) { UpdateSimulation(DeltaTime); } - - //// Sleep - possibly more aggressive than the standard physics one - //bool bAutoSleepEnabled = true; - //float LinearThreshold = 0.001f; - //float AngularThreshold = 0.001f; - //if (bAutoSleepEnabled && !VehicleState.bSleeping && RawBrakeInput == 1.f && !VehicleState.bVehicleInAir) - //{ - // if (TargetInstance - // && TargetInstance->GetUnrealWorldVelocity().SizeSquared() < LinearThreshold - // && TargetInstance->GetUnrealWorldAngularVelocityInRadians().SizeSquared() < AngularThreshold) - // { - // TargetInstance->PutInstanceToSleep(); - // VehicleState.bSleeping = true; - // } - //} } } +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) DrawDebug3D(); +#endif } void UChaosVehicleMovementComponent::StopMovementImmediately() @@ -505,13 +460,13 @@ float UChaosVehicleMovementComponent::CalcBrakeInput() // if vehicle is moving forwards, then press brake if (VehicleState.ForwardSpeed > WrongDirectionThreshold) { - NewBrakeInput = 1.0f; // Seems a bit severe to have 0 or 1 braking. Better control can be had by allowing continuous brake input values + NewBrakeInput = 1.0f; } } // if player isn't pressing forward or backwards... else { - if (VehicleState.ForwardSpeed < StopThreshold && VehicleState.ForwardSpeed > -StopThreshold) //auto break + if (VehicleState.ForwardSpeed < StopThreshold && VehicleState.ForwardSpeed > -StopThreshold) //auto brake { NewBrakeInput = 1.f; } @@ -530,7 +485,7 @@ float UChaosVehicleMovementComponent::CalcBrakeInput() // if player isn't pressing forward or backwards... if (RawBrakeInput < SMALL_NUMBER && RawThrottleInput < SMALL_NUMBER) { - if (VehicleState.ForwardSpeed < StopThreshold && VehicleState.ForwardSpeed > -StopThreshold) //auto break + if (VehicleState.ForwardSpeed < StopThreshold && VehicleState.ForwardSpeed > -StopThreshold) //auto brake { NewBrakeInput = 1.f; FBodyInstance* TargetInstance = GetBodyInstance(); @@ -622,22 +577,35 @@ void UChaosVehicleMovementComponent::UpdateState(float DeltaTime) // update input values AController* Controller = GetController(); - // TODO: IsLocallyControlled will fail if the owner is unpossessed (i.e. Controller == nullptr); + // IsLocallyControlled will fail if the owner is unpossessed (i.e. Controller == nullptr); // Should we remove input instead of relying on replicated state in that case? if (Controller && Controller->IsLocalController() && PVehicle) { - if (bReverseAsBrake && PVehicle->HasTransmission()) + if (PVehicle->HasTransmission()) { - //for reverse as state we want to automatically shift between reverse and first gear - if (FMath::Abs(GetForwardSpeed()) < WrongDirectionThreshold) //we only shift between reverse and first if the car is slow enough. + if (bReverseAsBrake) { - if (RawBrakeInput > KINDA_SMALL_NUMBER && PVehicle->GetTransmission().GetCurrentGear() >= 0 && PVehicle->GetTransmission().GetTargetGear() >= 0) + //for reverse as state we want to automatically shift between reverse and first gear + if (FMath::Abs(GetForwardSpeed()) < WrongDirectionThreshold) //we only shift between reverse and first if the car is slow enough. { - SetTargetGear(-1, false); + if (RawBrakeInput > KINDA_SMALL_NUMBER && PVehicle->GetTransmission().GetCurrentGear() >= 0 && PVehicle->GetTransmission().GetTargetGear() >= 0) + { + SetTargetGear(-1, false); + } + else if (RawThrottleInput > KINDA_SMALL_NUMBER && PVehicle->GetTransmission().GetCurrentGear() <= 0 && PVehicle->GetTransmission().GetTargetGear() <= 0) + { + SetTargetGear(1, false); + } } - else if (RawThrottleInput > KINDA_SMALL_NUMBER && PVehicle->GetTransmission().GetCurrentGear() <= 0 && PVehicle->GetTransmission().GetTargetGear() <= 0) + } + else + { + if (PVehicle->GetTransmission().Setup().TransmissionType == ETransmissionType::Automatic + && RawThrottleInput > KINDA_SMALL_NUMBER + && PVehicle->GetTransmission().GetCurrentGear() == 0 + && PVehicle->GetTransmission().GetTargetGear() == 0) { - SetTargetGear(1, false); + SetTargetGear(1, true); } } } @@ -685,9 +653,6 @@ void UChaosVehicleMovementComponent::UpdateSimulation(float DeltaTime) { VehicleState.CaptureState(TargetInstance, GetGravityZ(), DeltaTime); - // #todo: param to say use own gravity or not - //AddForce(GetGravity(), true, true); - ApplyAerodynamics(DeltaTime); ApplyAerofoilForces(DeltaTime); ApplyThrustForces(DeltaTime); @@ -703,15 +668,15 @@ void UChaosVehicleMovementComponent::ApplyInput(float DeltaTime) FAerofoil& Aerofoil = PVehicle->GetAerofoil(AerofoilIdx); switch (Aerofoil.Setup().Type) { - case Chaos::EAerofoilType::Rudder: + case Chaos::EAerofoilType::Rudder: Aerofoil.SetControlSurface(-YawInput); break; - case Chaos::EAerofoilType::Elevator: + case Chaos::EAerofoilType::Elevator: Aerofoil.SetControlSurface(PitchInput); break; - case Chaos::EAerofoilType::Wing: + case Chaos::EAerofoilType::Wing: if (Aerofoil.Setup().Offset.Y < 0.0f) { Aerofoil.SetControlSurface(RollInput); @@ -723,6 +688,52 @@ void UChaosVehicleMovementComponent::ApplyInput(float DeltaTime) break; } } + + for (int Thrusterdx = 0; Thrusterdx < Thrusters.Num(); Thrusterdx++) + { + FSimpleThrustSim& Thruster = PVehicle->GetThruster(Thrusterdx); + + Thruster.SetThrottle(ThrottleInput); + + switch (Thruster.Setup().Type) + { + case Chaos::EThrustType::HelicopterRotor: + { + Thruster.SetPitch(PitchInput); + Thruster.SetRoll(RollInput); + } + break; + + case Chaos::EThrustType::Rudder: + { + Thruster.SetYaw(-YawInput - SteeringInput); + } + break; + + case Chaos::EThrustType::Elevator: + { + Thruster.SetPitch(PitchInput); + } + break; + + case Chaos::EThrustType::Wing: + { + if (Thruster.Setup().Offset.Y < 0.0f) + { + Thruster.SetRoll(RollInput); + } + else + { + Thruster.SetRoll(-RollInput); + } + } + break; + + + } + + } + } @@ -732,7 +743,7 @@ void UChaosVehicleMovementComponent::ApplyAerodynamics(float DeltaTime) { // This force applied all the time whether the vehicle is on the ground or not Chaos::FSimpleAerodynamicsSim& PAerodynamics = PVehicle->GetAerodynamics(); - FVector LocalDragLiftForce = /*MToCm*/(PAerodynamics.GetCombinedForces(CmToM(VehicleState.ForwardSpeed))) * 100.0f; + FVector LocalDragLiftForce = (PAerodynamics.GetCombinedForces(CmToM(VehicleState.ForwardSpeed))) * MToCmScaling(); FVector WorldLiftDragForce = VehicleState.VehicleWorldTransform.TransformVector(LocalDragLiftForce); AddForce(WorldLiftDragForce); } @@ -740,6 +751,9 @@ void UChaosVehicleMovementComponent::ApplyAerodynamics(float DeltaTime) void UChaosVehicleMovementComponent::ApplyAerofoilForces(float DeltaTime) { + if (GVehicleDebugParams.DisableAerofoils || GetBodyInstance() == nullptr) + return; + TArray VelocityLocal; TArray VelocityWorld; VelocityLocal.SetNum(PVehicle->Aerofoils.Num()); @@ -747,10 +761,10 @@ void UChaosVehicleMovementComponent::ApplyAerofoilForces(float DeltaTime) float Altitude = VehicleState.VehicleWorldTransform.GetLocation().Z; - // Work out velocity at each aerofoil before applying any forces + // Work out velocity at each aerofoil before applying any forces so there's no bias on the first ones processed for (int AerofoilIdx = 0; AerofoilIdx < PVehicle->Aerofoils.Num(); AerofoilIdx++) { - FVector WorldLocation = VehicleState.VehicleWorldTransform.TransformPosition(PVehicle->GetAerofoil(AerofoilIdx).Setup().Offset * 100.0f); + FVector WorldLocation = VehicleState.VehicleWorldTransform.TransformPosition(PVehicle->GetAerofoil(AerofoilIdx).Setup().Offset * MToCmScaling()); VelocityWorld[AerofoilIdx] = GetBodyInstance()->GetUnrealWorldVelocityAtPoint(WorldLocation); VelocityLocal[AerofoilIdx] = VehicleState.VehicleWorldTransform.InverseTransformVector(VelocityWorld[AerofoilIdx]); } @@ -759,12 +773,13 @@ void UChaosVehicleMovementComponent::ApplyAerofoilForces(float DeltaTime) { Chaos::FAerofoil& Aerofoil = PVehicle->GetAerofoil(AerofoilIdx); - FVector Force = Aerofoil.GetForce(VehicleState.VehicleWorldTransform, VelocityLocal[AerofoilIdx] * 0.01f, CmToM(Altitude), DeltaTime); + FVector LocalForce = Aerofoil.GetForce(VehicleState.VehicleWorldTransform, VelocityLocal[AerofoilIdx] * CmToMScaling(), CmToM(Altitude), DeltaTime); - FVector WorldForce = VehicleState.VehicleWorldTransform.TransformVector(Force); - FVector WorldLocation = VehicleState.VehicleWorldTransform.TransformPosition(Aerofoil.GetCenterOfLiftOffset() * 100.0f); - AddForceAtPosition(WorldForce * 100.0f, WorldLocation); + FVector WorldForce = VehicleState.VehicleWorldTransform.TransformVector(LocalForce); + FVector WorldLocation = VehicleState.VehicleWorldTransform.TransformPosition(Aerofoil.GetCenterOfLiftOffset() * MToCmScaling()); + AddForceAtPosition(WorldForce * MToCmScaling(), WorldLocation); +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FVector WorldAxis = VehicleState.VehicleWorldTransform.TransformVector(FVector::CrossProduct(FVector(1,0,0), Aerofoil.Setup().UpAxis)); if (GVehicleDebugParams.ShowAerofoilSurface) { @@ -774,6 +789,7 @@ void UChaosVehicleMovementComponent::ApplyAerofoilForces(float DeltaTime) { DrawDebugLine(GetWorld(), WorldLocation, WorldLocation + WorldForce * GVehicleDebugParams.ForceDebugScaling, FColor::Green, false, -1.f, 0, 16.f); } +#endif } } @@ -781,15 +797,15 @@ void UChaosVehicleMovementComponent::ApplyAerofoilForces(float DeltaTime) void UChaosVehicleMovementComponent::ApplyThrustForces(float DeltaTime) { + if (GVehicleDebugParams.DisableThrusters || GetBodyInstance() == nullptr) + return; + for (int ThrusterIdx = 0; ThrusterIdx < PVehicle->Thrusters.Num(); ThrusterIdx++) { Chaos::FSimpleThrustSim& Thruster = PVehicle->GetThruster(ThrusterIdx); FVector COM_Offset = GetBodyInstance()->GetMassSpaceLocal().GetLocation(); COM_Offset.Z = 0.0f; - Thruster.SetPitch(PitchInput); - Thruster.SetRoll(RollInput); - Thruster.SetThrottle(ThrottleInput); Thruster.SetWorldVelocity(VehicleState.VehicleWorldVelocity); Thruster.Simulate(DeltaTime); @@ -808,7 +824,7 @@ void UChaosVehicleMovementComponent::ApplyTorqueControl(float DeltaTime) if (!GVehicleDebugParams.DisableTorqueControl && TargetInstance) { FVector TotalTorque = FVector::ZeroVector; - if (TargetRotationControl.Enabled/*&& VehicleState.bVehicleInAir*/) + if (TargetRotationControl.Enabled) { auto ComputeTorque = [](const FVector& TargetUp, const FVector& CurrentUp, const FVector& AngVelocityWorld, float Stiffness, float Damping, float MaxAccel) -> FVector { @@ -889,7 +905,6 @@ void UChaosVehicleMovementComponent::ApplyTorqueControl(float DeltaTime) // try to cancel out velocity on Z axis FVector CorrectionalForce = FVector::ZeroVector; { - static float UpDownMultiplier = 4.0f; bool MaintainAltitude = true; if (MaintainAltitude) { @@ -898,47 +913,56 @@ void UChaosVehicleMovementComponent::ApplyTorqueControl(float DeltaTime) } // try to cancel out velocity on X/Y plane - if (FMath::Abs(RollInput) < SMALL_NUMBER && FMath::Abs(PitchInput) < SMALL_NUMBER) + // #todo: Will break helicopter setup??if (FMath::Abs(RollInput) < SMALL_NUMBER && FMath::Abs(PitchInput) < SMALL_NUMBER) { - static float PositionMultiplier = 8.0f; CorrectionalForce.X = -StabilizeControl.PositionHoldXY * VehicleState.VehicleWorldVelocity.X / DeltaTime; CorrectionalForce.Y = -StabilizeControl.PositionHoldXY * VehicleState.VehicleWorldVelocity.Y / DeltaTime; } AddForce(CorrectionalForce); } - - //// maintain specified roll angle - //{ - // // desired Roll and actual roll then try to match it - // static float Scaling = 40.f; - // static float Scaling2 = 0.6f; - // float CurrentRoll = FVehicleUtility::RollFromRightVectorRadians(VehicleState.VehicleRightAxis); - // float DesiredRoll = -RawSteeringInput /*-VehicleState.LocalGForce.Y*/ * Scaling2; - // FVector CorrectRollTorque((DesiredRoll - CurrentRoll) * Scaling * DeltaTime, 0.f, 0.f); - // CorrectRollTorque = VehicleState.VehicleWorldTransform.TransformVector(CorrectRollTorque); - //// TargetInstance->AddTorqueInRadians(CorrectRollTorque, /*bAllowSubstepping=*/true, /*bAccelChange=*/true); - // TargetInstance->SetAngularVelocityInRadians(CorrectRollTorque, true, false); - //} } - -void UChaosVehicleMovementComponent::CopyToSolverSafeContactStaticData() +void UChaosVehicleMovementComponent::ProcessSleeping() { - //if (GetPhysicsVehicleConfigs()) - //{ - // SolverSafeContactData.ContactModificationOffset = GetPhysicsVehicleConfigs()->ContactModificationOffset; - // SolverSafeContactData.VehicleFloorFriction = GetPhysicsVehicleConfigs()->VehicleFloorFriction; - // SolverSafeContactData.VehicleSideScrapeFriction = GetPhysicsVehicleConfigs()->VehicleSideScrapeFriction; - // SolverSafeContactData.VehicleSideScrapeMaxCosAngle = GetPhysicsVehicleConfigs()->VehicleSideScrapeMaxCosAngle; - //} - //else - //{ - // SolverSafeContactData.ContactModificationOffset = 10.f; - // SolverSafeContactData.VehicleFloorFriction = 0.f; - // SolverSafeContactData.VehicleSideScrapeFriction = 0.1f; - //} -} + FBodyInstance* TargetInstance = GetBodyInstance(); + if (TargetInstance) + { + bool PrevSleeping = VehicleState.bSleeping; + VehicleState.bSleeping = !TargetInstance->IsInstanceAwake(); + // The physics system has woken vehicle up due to a collision or something + if (PrevSleeping && !VehicleState.bSleeping) + { + VehicleState.SleepCounter = 0; + } + + bool ControlInputPressed = (RawThrottleInput >= SMALL_NUMBER) || (RawBrakeInput >= SMALL_NUMBER) || (FMath::Abs(RawSteeringInput) > SMALL_NUMBER); + + // Wake if control input pressed + if (VehicleState.bSleeping && (ControlInputPressed || !VehicleState.bAllWheelsOnGround)) + { + VehicleState.bSleeping = false; + VehicleState.SleepCounter = 0; + TargetInstance->WakeInstance(); + } + else if (!VehicleState.bSleeping && !ControlInputPressed && VehicleState.bAllWheelsOnGround && (VehicleState.VehicleUpAxis.Z > SleepSlopeLimit)) + { + float SpeedSqr = TargetInstance->GetUnrealWorldVelocity().SizeSquared(); + if (SpeedSqr < (SleepThreshold* SleepThreshold)) + { + if (VehicleState.SleepCounter < GVehicleDebugParams.SleepCounterThreshold) + { + VehicleState.SleepCounter++; + } + else + { + VehicleState.bSleeping = true; + TargetInstance->PutInstanceToSleep(); + } + } + } + } +} /// @cond DOXYGEN_WARNINGS @@ -1054,9 +1078,6 @@ void UChaosVehicleMovementComponent::CreateVehicle() check(UpdatedComponent); if (ensure(UpdatedPrimitive != nullptr)) { - // The underlying code has changed - BodyInstance is not valid at this time - //check(UpdatedPrimitive->GetBodyInstance()->IsDynamic()); - // Low level physics representation PVehicle = MakeUnique(); @@ -1087,7 +1108,6 @@ void UChaosVehicleMovementComponent::SetupVehicle() Chaos::FSimpleThrustSim ThrustSim(&ThrustSetup.GetPhysicsThrusterConfig(*this)); PVehicle->Thrusters.Add(ThrustSim); } - } void UChaosVehicleMovementComponent::PostSetupVehicle() @@ -1121,21 +1141,9 @@ void UChaosVehicleMovementComponent::UpdateMassProperties(FBodyInstance* BodyIns FPhysicsInterface::SetMassSpaceInertiaTensor_AssumesLocked(Actor, InertiaTensor); FPhysicsInterface::SetMass_AssumesLocked(Actor, this->Mass); - - //FTransform COMTransform = FPhysicsInterface::GetComTransformLocal_AssumesLocked(Actor); - //COMTransform.GetLocation() += CenterOfMassShift; - //FPhysicsInterface::SetComLocalPose_AssumesLocked(Actor, COMTransform); }); } - // const PxVec3 PCOMOffset = U2PVector(GetLocalCOM()); - // PVehicleActor->setCMassLocalPose(PxTransform(PCOMOffset, PxQuat(physx::PxIdentity))); //ignore the mass reference frame. TODO: expose this to the user - - // //if (PVehicle) - // //{ - // // PxVehicleWheelsSimData& WheelData = PVehicle->mWheelsSimData; - // // SetupWheelMassProperties_AssumesLocked(WheelData.getNbWheels(), &WheelData, PVehicleActor); - // //} } void UChaosVehicleMovementComponent::ComputeConstants() @@ -1163,7 +1171,7 @@ void UChaosVehicleMovementComponent::ShowDebugInfo(AHUD* HUD, UCanvas* Canvas, c void UChaosVehicleMovementComponent::DrawDebug(UCanvas* Canvas, float& YL, float& YPos) { -#if WITH_EDITOR +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FBodyInstance* TargetInstance = GetBodyInstance(); if (!PVehicle.IsValid() || TargetInstance == nullptr) { @@ -1227,16 +1235,6 @@ void UChaosVehicleMovementComponent::DrawDebug3D() #endif } - -//void UVehicleMovementComponent::CopyToSolverSafeContactDynamicData() -//{ -// // Copy to solver inputs -// SolverSafeContactData.IgnoredBuildingActors = IgnoredBuildingActors; -// SolverSafeContactData.LocallyIgnoredBuildingActors = LocallyIgnoredBuildingActors; -// SolverSafeContactData.bSkipRotations = ShouldSkipContactRotations(); -//} - - /// @cond DOXYGEN_WARNINGS void UChaosVehicleMovementComponent::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const @@ -1272,7 +1270,7 @@ void UChaosVehicleMovementComponent::AddForce(const FVector& Force, bool bAllowS { FVector Position = VehicleState.VehicleWorldCOM; DrawDebugDirectionalArrow(GetWorld(), Position, Position + Force * GVehicleDebugParams.ForceDebugScaling - , 20.f, FColor::Blue, false, GVehicleDebugParams.PersistDebugLinesTime, 0, 2.f); + , 20.f, FColor::Blue, false, 0, 0, 2.f); } #endif } @@ -1286,7 +1284,7 @@ void UChaosVehicleMovementComponent::AddForceAtPosition(const FVector& Force, co if (GVehicleDebugParams.ShowAllForces) { DrawDebugDirectionalArrow(GetWorld(), Position, Position + Force * GVehicleDebugParams.ForceDebugScaling - , 20.f, FColor::Blue, false, GVehicleDebugParams.PersistDebugLinesTime, 0, 2.f); + , 20.f, FColor::Blue, false, 0, 0, 2.f); } #endif } @@ -1301,7 +1299,7 @@ void UChaosVehicleMovementComponent::AddImpulse(const FVector& Impulse, bool bVe { FVector Position = VehicleState.VehicleWorldCOM; DrawDebugDirectionalArrow(GetWorld(), Position, Position + Impulse * GVehicleDebugParams.ForceDebugScaling - , 20.f, FColor::Red, false, GVehicleDebugParams.PersistDebugLinesTime, 0, 2.f); + , 20.f, FColor::Red, false, 0, 0, 2.f); } #endif } @@ -1315,7 +1313,7 @@ void UChaosVehicleMovementComponent::AddImpulseAtPosition(const FVector& Impulse if (GVehicleDebugParams.ShowAllForces) { DrawDebugDirectionalArrow(GetWorld(), Position, Position + Impulse * GVehicleDebugParams.ForceDebugScaling - , 20.f, FColor::Red, false, GVehicleDebugParams.PersistDebugLinesTime, 0, 2.f); + , 20.f, FColor::Red, false, 0, 0, 2.f); } #endif diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleWheel.cpp b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleWheel.cpp index 4d212051ad60..133e89595d62 100644 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleWheel.cpp +++ b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleWheel.cpp @@ -7,7 +7,6 @@ #include "Engine/StaticMesh.h" #include "Vehicles/TireType.h" #include "GameFramework/PawnMovementComponent.h" -#include "ChaosTireConfig.h" #include "ChaosVehicleManager.h" #include "ChaosWheeledVehicleMovementComponent.h" diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosWheeledVehicleMovementComponent.cpp b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosWheeledVehicleMovementComponent.cpp index b7ad8b33091d..cd7d15afefdc 100644 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosWheeledVehicleMovementComponent.cpp +++ b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosWheeledVehicleMovementComponent.cpp @@ -15,7 +15,7 @@ #include "SuspensionUtility.h" #include "SteeringUtility.h" -#if WITH_EDITOR +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) #include "CanvasItem.h" #include "Engine/Canvas.h" #endif @@ -42,7 +42,6 @@ FAutoConsoleVariableRef CVarChaosVehiclesShowSuspensionForces(TEXT("p.Vehicle.Sh FAutoConsoleVariableRef CVarChaosVehiclesDisableSuspensionForces(TEXT("p.Vehicle.DisableSuspensionForces"), GWheeledVehicleDebugParams.DisableSuspensionForces, TEXT("Enable/Disable Suspension Forces.")); FAutoConsoleVariableRef CVarChaosVehiclesDisableFrictionForces(TEXT("p.Vehicle.DisableFrictionForces"), GWheeledVehicleDebugParams.DisableFrictionForces, TEXT("Enable/Disable Wheel Friction Forces.")); FAutoConsoleVariableRef CVarChaosVehiclesDisableRollbarForces(TEXT("p.Vehicle.DisableRollbarForces"), GWheeledVehicleDebugParams.DisableRollbarForces, TEXT("Enable/Disable Rollbar Forces.")); -FAutoConsoleVariableRef CVarChaosVehiclesApplyWheelForcetoSurface(TEXT("p.Vehicle.ApplyWheelForcetoSurface"), GWheeledVehicleDebugParams.ApplyWheelForcetoSurface, TEXT("Enable/Disable Apply Wheel Force To Underlying Surface.")); FAutoConsoleVariableRef CVarChaosVehiclesThrottleOverride(TEXT("p.Vehicle.ThrottleOverride"), GWheeledVehicleDebugParams.ThrottleOverride, TEXT("Hard code throttle input on.")); FAutoConsoleVariableRef CVarChaosVehiclesSteeringOverride(TEXT("p.Vehicle.SteeringOverride"), GWheeledVehicleDebugParams.SteeringOverride, TEXT("Hard code steering input on.")); @@ -79,7 +78,7 @@ UChaosWheeledVehicleMovementComponent::UChaosWheeledVehicleMovementComponent(con TransmissionSetup.InitDefaults(); SteeringSetup.InitDefaults(); - // possible to switch whole systems off + // It's possible to switch whole systems off if they are not required bMechanicalSimEnabled = true; bSuspensionEnabled = true; bWheelFrictionEnabled = true; @@ -102,22 +101,62 @@ void UChaosWheeledVehicleMovementComponent::PostEditChangeProperty(struct FPrope Super::PostEditChangeProperty(PropertyChangedEvent); const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None; - // #todo: use or remove - //if (PropertyName == TEXT("SteeringCurve")) - //{ - // // make sure values are capped between 0 and 1 - // TArray SteerKeys = SteeringSetup.SteeringCurve.GetRichCurve()->GetCopyOfKeys(); - // for (int32 KeyIdx = 0; KeyIdx < SteerKeys.Num(); ++KeyIdx) - // { - // float NewValue = FMath::Clamp(SteerKeys[KeyIdx].Value, 0.f, 1.f); - // SteeringSetup.SteeringCurve.GetRichCurve()->UpdateOrAddKey(SteerKeys[KeyIdx].Time, NewValue); - // } - //} - RecalculateAxles(); } #endif +void UChaosWheeledVehicleMovementComponent::FixupSkeletalMesh() +{ + Super::FixupSkeletalMesh(); + + if (USkeletalMeshComponent* Mesh = Cast(GetMesh())) + { + if (UPhysicsAsset* PhysicsAsset = Mesh->GetPhysicsAsset()) + { + for (int32 WheelIdx = 0; WheelIdx < WheelSetups.Num(); ++WheelIdx) + { + FChaosWheelSetup& WheelSetup = WheelSetups[WheelIdx]; + if (WheelSetup.BoneName != NAME_None) + { + int32 BodySetupIdx = PhysicsAsset->FindBodyIndex(WheelSetup.BoneName); + + if (BodySetupIdx >= 0 && (BodySetupIdx < Mesh->Bodies.Num())) + { + FBodyInstance* BodyInstanceWheel = Mesh->Bodies[BodySetupIdx]; + BodyInstanceWheel->SetResponseToAllChannels(ECR_Ignore); //turn off collision for wheel automatically + + if (UBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[BodySetupIdx]) + { + + { + BodyInstanceWheel->SetInstanceSimulatePhysics(false); + } + + bool DeleteOriginalWheelConstraints = true; + if (DeleteOriginalWheelConstraints) + { + //and get rid of constraints on the wheels. TODO: right now we remove all wheel constraints, we probably only want to remove parent constraints + TArray WheelConstraints; + PhysicsAsset->BodyFindConstraints(BodySetupIdx, WheelConstraints); + for (int32 ConstraintIdx = 0; ConstraintIdx < WheelConstraints.Num(); ++ConstraintIdx) + { + FConstraintInstance* ConInst = Mesh->Constraints[WheelConstraints[ConstraintIdx]]; + ConInst->TermConstraint(); + } + } + } + } + } + } + } + + Mesh->KinematicBonesUpdateType = EKinematicBonesUpdateToPhysics::SkipSimulatingBones; + + } + +} + + bool UChaosWheeledVehicleMovementComponent::CanCreateVehicle() const { if (!Super::CanCreateVehicle()) @@ -201,18 +240,12 @@ void UChaosWheeledVehicleMovementComponent::TickVehicle(float DeltaTime) { Super::TickVehicle(DeltaTime); - ////if (AvoidanceLockTimer > 0.0f) - ////{ - //// AvoidanceLockTimer -= DeltaTime; - ////} - // update wheels for (int32 i = 0; i < Wheels.Num(); i++) { UChaosVehicleWheel* VehicleWheel = Wheels[i]; Wheels[i]->Tick(DeltaTime); } - } void UChaosWheeledVehicleMovementComponent::NextDebugPage() @@ -238,7 +271,6 @@ void UChaosWheeledVehicleMovementComponent::PrevDebugPage() } - // Setup void UChaosWheeledVehicleMovementComponent::ComputeConstants() { @@ -367,132 +399,6 @@ void UChaosWheeledVehicleMovementComponent::SetupVehicleShapes() return; } - //static PxMaterial* WheelMaterial = GPhysXSDK->createMaterial(0.0f, 0.0f, 0.0f); - //FBodyInstance* TargetInstance = UpdatedPrimitive->GetBodyInstance(); - - //FPhysicsCommand::ExecuteWrite(TargetInstance->ActorHandle, [&](const FPhysicsActorHandle& Actor) - // { - // PxRigidActor* PActor = FPhysicsInterface::GetPxRigidActor_AssumesLocked(Actor); - // if (!PActor) - // { - // return; - // } - - // if (PxRigidDynamic* PVehicleActor = PActor->is()) - // { - // // Add wheel shapes to actor - for (int32 WheelIdx = 0; WheelIdx < WheelSetups.Num(); ++WheelIdx) - { - FChaosWheelSetup& WheelSetup = WheelSetups[WheelIdx]; - UChaosVehicleWheel* Wheel = WheelSetup.WheelClass.GetDefaultObject(); - check(Wheel); - - const FVector WheelOffset = GetWheelRestingPosition(WheelSetup); - - //GetSkeletalMesh()->GetBodyInstance(WheelSetup.BoneName)); - // - //UPhysicalMaterial* SimplePhysMat = GetBodyInstance()->GetSimplePhysicalMaterial(); - //TArray ComplexPhysMats; - //TArray ComplexPhysMatMasks; - //ComplexPhysMats = GetBodyInstance()->GetComplexPhysicalMaterials(ComplexPhysMatMasks); - //FBodyCollisionData BodyCollisionData; - //GetBodyInstance()->BuildBodyFilterData(BodyCollisionData.CollisionFilterData); - - //GetBodyInstance()->BodySetup->AddShapesToRigidActor_AssumesLocked(GetBodyInstance(), GetBodyInstance()->Scale3D - // , SimplePhysMat, ComplexPhysMats, ComplexPhysMatMasks, BodyCollisionData, FTransform::Identity); - - //FCollisionShape::MakeSphere(SphereRadius); - //FPhysicsInterface::AttachShape() - //PWheelShape = GPhysXSDK->createShape(PxSphereGeometry(Wheel->WheelRadius), *WheelMaterial, /*bIsExclusive=*/true); - //PWheelShape->setLocalPose(PLocalPose); - //PVehicleActor->attachShape(*PWheelShape); - //PWheelShape->release(); - - // const PxTransform PLocalPose = PxTransform(U2PVector(WheelOffset)); - // PxShape* PWheelShape = NULL; - - // // Prepare shape - // const UBodySetup* WheelBodySetup = nullptr; - // FVector MeshScaleV(1.f, 1.f, 1.f); - // if (Wheel->bDontCreateShape) - // { - // //don't create shape so grab it directly from the bodies associated with the vehicle - // if (USkinnedMeshComponent* SkinnedMesh = GetMesh()) - // { - // if (const FBodyInstance* WheelBI = SkinnedMesh->GetBodyInstance(WheelSetup.BoneName)) - // { - // WheelBodySetup = WheelBI->BodySetup.Get(); - // } - // } - - // } - // else if (Wheel->CollisionMesh && Wheel->CollisionMesh->BodySetup) - // { - // WheelBodySetup = Wheel->CollisionMesh->BodySetup; - - // FBoxSphereBounds MeshBounds = Wheel->CollisionMesh->GetBounds(); - // if (Wheel->bAutoAdjustCollisionSize) - // { - // MeshScaleV.X = Wheel->WheelRadius / MeshBounds.BoxExtent.X; - // MeshScaleV.Y = Wheel->WheelWidth / MeshBounds.BoxExtent.Y; - // MeshScaleV.Z = Wheel->WheelRadius / MeshBounds.BoxExtent.Z; - // } - // } - - // if (WheelBodySetup) - // { - // PxMeshScale MeshScale(U2PVector(UpdatedComponent->GetRelativeScale3D() * MeshScaleV), PxQuat(physx::PxIdentity)); - - // if (WheelBodySetup->AggGeom.ConvexElems.Num() == 1) - // { - // PxConvexMesh* ConvexMesh = WheelBodySetup->AggGeom.ConvexElems[0].GetConvexMesh(); - // PWheelShape = GPhysXSDK->createShape(PxConvexMeshGeometry(ConvexMesh, MeshScale), *WheelMaterial, /*bIsExclusive=*/true); - // PVehicleActor->attachShape(*PWheelShape); - // PWheelShape->release(); - // } - // else if (WheelBodySetup->TriMeshes.Num()) - // { - // PxTriangleMesh* TriMesh = WheelBodySetup->TriMeshes[0]; - - // // No eSIMULATION_SHAPE flag for wheels - // PWheelShape = GPhysXSDK->createShape(PxTriangleMeshGeometry(TriMesh, MeshScale), *WheelMaterial, /*bIsExclusive=*/true, PxShapeFlag::eSCENE_QUERY_SHAPE | PxShapeFlag::eVISUALIZATION); - // PWheelShape->setLocalPose(PLocalPose); - // PVehicleActor->attachShape(*PWheelShape); - // PWheelShape->release(); - // } - // } - - // if (!PWheelShape) - // { - // //fallback onto simple spheres - // PWheelShape = GPhysXSDK->createShape(PxSphereGeometry(Wheel->WheelRadius), *WheelMaterial, /*bIsExclusive=*/true); - // PWheelShape->setLocalPose(PLocalPose); - // PVehicleActor->attachShape(*PWheelShape); - // PWheelShape->release(); - // } - - // Init filter data - //FCollisionResponseContainer CollisionResponse; - //CollisionResponse.SetAllChannels(ECR_Ignore); - - //FCollisionFilterData WheelQueryFilterData, DummySimData; - //CreateShapeFilterData(ECC_Vehicle, FMaskFilter(0), UpdatedComponent->GetOwner()->GetUniqueID(), CollisionResponse, UpdatedComponent->GetUniqueID(), 0, WheelQueryFilterData, DummySimData, false, false, false); - - //if (Wheel->SweepType != EWheelSweepType::Complex) - //{ - // WheelQueryFilterData.Word3 |= EPDF_SimpleCollision; - //} - - //if (Wheel->SweepType != EWheelSweepType::Simple) - //{ - // WheelQueryFilterData.Word3 |= EPDF_ComplexCollision; - //} - - //// Give suspension raycasts the same group ID as the chassis so that they don't hit each other - //PWheelShape->setQueryFilterData(U2PFilterData(WheelQueryFilterData)); - } - // } - // }); } void UChaosWheeledVehicleMovementComponent::SetupSuspension() @@ -529,7 +435,7 @@ void UChaosWheeledVehicleMovementComponent::SetupSuspension() auto& Susp = PVehicle->Suspension[SpringIdx]; float NaturalFrequency = FSuspensionUtility::ComputeNaturalFrequency(Susp.Setup().SpringRate, OutSprungMasses[SpringIdx]); float Damping = FSuspensionUtility::ComputeDamping(Susp.Setup().SpringRate, OutSprungMasses[SpringIdx], Susp.Setup().DampingRatio); - //UE_LOG(LogChaos, Warning, TEXT("OutNaturalFrequency %.1f Hz (@1.0) DampingRate %.1f"), NaturalFrequency / (2.0f * PI), Damping); + UE_LOG(LogChaos, Verbose, TEXT("Spring %d: OutNaturalFrequency %.1f Hz (@1.0) DampingRate %.1f"), SpringIdx, NaturalFrequency / (2.0f * PI), Damping); PVehicle->Suspension[SpringIdx].AccessSetup().ReboundDamping = Damping; PVehicle->Suspension[SpringIdx].AccessSetup().CompressionDamping = Damping; @@ -567,6 +473,7 @@ FVector UChaosWheeledVehicleMovementComponent::GetWheelRestingPosition(const FCh } // Update + void UChaosWheeledVehicleMovementComponent::UpdateSimulation(float DeltaTime) { // Inherit common vehicle simulation stages ApplyAerodynamics, ApplyTorqueControl, etc @@ -577,14 +484,14 @@ void UChaosWheeledVehicleMovementComponent::UpdateSimulation(float DeltaTime) if (CanSimulate() && TargetInstance) { // sanity check that everything is setup ok - check(Wheels.Num() == PVehicle->Suspension.Num()); - check(Wheels.Num() == PVehicle->Wheels.Num()); - check(WheelState.LocalWheelVelocity.Num() == Wheels.Num()); - check(WheelState.WheelWorldLocation.Num() == Wheels.Num()); - check(WheelState.WorldWheelVelocity.Num() == Wheels.Num()); + ensure(Wheels.Num() == PVehicle->Suspension.Num()); + ensure(Wheels.Num() == PVehicle->Wheels.Num()); + ensure(WheelState.LocalWheelVelocity.Num() == Wheels.Num()); + ensure(WheelState.WheelWorldLocation.Num() == Wheels.Num()); + ensure(WheelState.WorldWheelVelocity.Num() == Wheels.Num()); /////////////////////////////////////////////////////////////////////// - // Cache useful state so we are not re-calculating the same transforms + // Cache useful state so we are not re-calculating the same data for (int WheelIdx = 0; WheelIdx < Wheels.Num(); WheelIdx++) { @@ -610,6 +517,7 @@ void UChaosWheeledVehicleMovementComponent::UpdateSimulation(float DeltaTime) // Wheel and Vehicle in air state VehicleState.bVehicleInAir = true; + VehicleState.NumWheelsOnGround = 0; for (int WheelIdx = 0; WheelIdx < Wheels.Num(); WheelIdx++) { // tell systems who care that wheel is touching the ground @@ -619,8 +527,10 @@ void UChaosWheeledVehicleMovementComponent::UpdateSimulation(float DeltaTime) if (PVehicle->Wheels[WheelIdx].InContact()) { VehicleState.bVehicleInAir = false; + VehicleState.NumWheelsOnGround++; } } + VehicleState.bAllWheelsOnGround = (VehicleState.NumWheelsOnGround == Wheels.Num()); /////////////////////////////////////////////////////////////////////// // Input @@ -778,15 +688,15 @@ void UChaosWheeledVehicleMovementComponent::PerformSuspensionTraces(const TArray { case ESweepShape::Spherecast: { - //float WheelRadius = PVehicle->Wheels[WheelIdx].GetEffectiveRadius(); // or wheel width - float WheelRadius = PVehicle->Wheels[WheelIdx].Setup().WheelWidth * 0.5f; // or wheel width + //float Radius = PVehicle->Wheels[WheelIdx].GetEffectiveRadius(); // or wheel width + float Radius = PVehicle->Wheels[WheelIdx].Setup().WheelWidth * 0.5f; // or wheel width FVector VehicleUpAxis = GetOwner()->GetTransform().GetUnitAxis(EAxis::Z); GetWorld()->SweepSingleByChannel(HitResult - , TraceStart + VehicleUpAxis * WheelRadius - , TraceEnd + VehicleUpAxis * WheelRadius + , TraceStart + VehicleUpAxis * Radius + , TraceEnd + VehicleUpAxis * Radius , FQuat::Identity, SpringCollisionChannel - , FCollisionShape::MakeSphere(WheelRadius), TraceParams + , FCollisionShape::MakeSphere(Radius), TraceParams , FCollisionResponseParams::DefaultResponseParam); } break; @@ -843,6 +753,10 @@ void UChaosWheeledVehicleMovementComponent::ApplyWheelFrictionForces(float Delta FMatrix Mat(GroundXVector, GroundYVector, GroundZVector, VehicleState.VehicleWorldTransform.GetLocation()); FVector FrictionForceVector = Mat.TransformVector(FrictionForceLocal); + check(PWheel.InContact()); + AddForceAtPosition(FrictionForceVector, WheelState.WheelWorldLocation[WheelIdx]); + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (GWheeledVehicleDebugParams.ShowWheelForces) { // show longitudinal drive force @@ -852,7 +766,6 @@ void UChaosWheeledVehicleMovementComponent::ApplyWheelFrictionForces(float Delta , WheelState.WheelWorldLocation[WheelIdx] + FrictionForceVector * 0.001f , FColor::Yellow, false, -1.0f, 0, 2); - DrawDebugLine(GetWorld() , WheelState.WheelWorldLocation[WheelIdx] , WheelState.WheelWorldLocation[WheelIdx] + GroundZVector * 100.f @@ -861,32 +774,8 @@ void UChaosWheeledVehicleMovementComponent::ApplyWheelFrictionForces(float Delta } } +#endif - check(PWheel.InContact()); - AddForceAtPosition(FrictionForceVector, WheelState.WheelWorldLocation[WheelIdx]); - - if (GWheeledVehicleDebugParams.ApplyWheelForcetoSurface) - { - //// #todo: trying this - bit of a special case for the brick assets - //const FHitResult& Hit = Wheels[WheelIdx]->HitResult; - //if (Hit.GetComponent() && Hit.GetComponent()->IsAnyRigidBodyAwake()) - //{ - // //FVector Location = Hit.GetComponent()->GetComponentLocation(); - // //FVector Location = Hit.ImpactPoint - // //Hit.GetComponent()->AddForceAtLocation(-FrictionForceVector, Location); - - // FVector SpeedDiffLocal(PWheel.GetRoadSpeed() - PWheel.GetWheelGroundSpeed(), 0.f, 0.f); - // FVector SpeedDiffWorld = Mat.TransformVector(SpeedDiffLocal); - - // Hit.GetComponent()->AddImpulse(SpeedDiffWorld, NAME_None, true); - - // //DrawDebugLine(GetWorld() - // // , Hit.Location - // // , Hit.Location - FrictionForceVector * 0.001f - // // , FColor::Yellow, true, 1.0f, 0, 2); - - //} - } } else { @@ -919,7 +808,6 @@ void UChaosWheeledVehicleMovementComponent::ApplySuspensionForces(float DeltaTim { NewDesiredLength = HitResult.Distance; - // #todo: is this actually correct?? SuspensionMovePosition = -FVector::DotProduct(WheelState.WheelWorldLocation[WheelIdx] - HitResult.ImpactPoint, VehicleState.VehicleUpAxis) + Wheel->WheelRadius; PSuspension.SetSuspensionLength(NewDesiredLength, PWheel.Setup().WheelRadius); @@ -930,13 +818,19 @@ void UChaosWheeledVehicleMovementComponent::ApplySuspensionForces(float DeltaTim FVector GroundZVector = HitResult.Normal; - FVector SuspensionForceVector = VehicleState.VehicleUpAxis /*GroundZVector*/ * ForceMagnitude; + FVector SuspensionForceVector = VehicleState.VehicleUpAxis * ForceMagnitude; FVector SusApplicationPoint = WheelState.WheelWorldLocation[WheelIdx] + PVehicle->Suspension[WheelIdx].Setup().SuspensionForceOffset; check(PWheel.InContact()); AddForceAtPosition(SuspensionForceVector, SusApplicationPoint); + ForceMagnitude = PSuspension.Setup().WheelLoadRatio * ForceMagnitude + (1.f - PSuspension.Setup().WheelLoadRatio) * PSuspension.Setup().RestingForce; + PWheel.SetWheelLoadForce(ForceMagnitude); + PWheel.SetMassPerWheel(TargetInstance->GetBodyMass() / PVehicle->Wheels.Num()); + SusForces[WheelIdx] = ForceMagnitude; + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (GWheeledVehicleDebugParams.ShowSuspensionForces) { DrawDebugLine(GetWorld() @@ -949,11 +843,7 @@ void UChaosWheeledVehicleMovementComponent::ApplySuspensionForces(float DeltaTim , SusApplicationPoint + GroundZVector * 140.f , FColor::Yellow, false, -1.0f, 0, 5); } - - ForceMagnitude = PSuspension.Setup().WheelLoadRatio * ForceMagnitude + (1.f - PSuspension.Setup().WheelLoadRatio) * PSuspension.Setup().RestingForce; - PWheel.SetWheelLoadForce(ForceMagnitude); - PWheel.SetMassPerWheel(TargetInstance->GetBodyMass() / PVehicle->Wheels.Num()); - SusForces[WheelIdx] = ForceMagnitude; +#endif } @@ -1099,7 +989,6 @@ void UChaosWheeledVehicleMovementComponent::ProcessMechanicalSimulation(float De auto& PEngine = PVehicle->GetEngine(); auto& PTransmission = PVehicle->GetTransmission(); - // #todo: driven wheel(s) RPM float WheelRPM = 0; for (int I=0; IWheels.Num(); I++) { @@ -1112,7 +1001,6 @@ void UChaosWheeledVehicleMovementComponent::ProcessMechanicalSimulation(float De PEngine.SetEngineRPM(PTransmission.IsOutOfGear() , PTransmission.GetEngineRPMFromWheelRPM(WheelRPM)); PEngine.Simulate(DeltaTime); - // SET SPEED FROM A DRIVEN WHEEL!!! - average all driven wheel speeds?? No notion of a differential as yet PTransmission.SetEngineRPM(PEngine.GetEngineRPM()); // needs engine RPM to decide when to change gear (automatic gearbox) PTransmission.SetAllowedToChangeGear(!VehicleState.bVehicleInAir && !IsWheelSpinning()); float GearRatio = PTransmission.GetGearRatio(PTransmission.GetCurrentGear()); @@ -1179,13 +1067,6 @@ float UChaosWheeledVehicleMovementComponent::GetEngineMaxRotationSpeed() const return MaxEngineRPM; } -float UChaosWheeledVehicleMovementComponent::GetMaxSpringForce() const -{ - // #todo: - check(false); - return 0.0f; -} - bool UChaosWheeledVehicleMovementComponent::IsWheelSpinning() const { for (auto& Wheel : PVehicle->Wheels) @@ -1232,15 +1113,15 @@ FVector2D UChaosWheeledVehicleMovementComponent::CalculateWheelLayoutDimensions( // Debug void UChaosWheeledVehicleMovementComponent::DrawDebug(UCanvas* Canvas, float& YL, float& YPos) { -#if WITH_EDITOR +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) Super::DrawDebug(Canvas, YL, YPos); FChaosVehicleManager* MyVehicleManager = FChaosVehicleManager::GetVehicleManagerFromScene(GetWorld()->GetPhysicsScene()); FBodyInstance* TargetInstance = GetBodyInstance(); - // #todo: Should use Pawn->Controller->IsLocalPlayerController() or something? - if (!PVehicle.IsValid() || TargetInstance == nullptr || MyVehicleManager == nullptr /*|| GetOwnerRole() != ROLE_Authority*/) + // #todo: is this rendering multiple times in multiplayer + if (!PVehicle.IsValid() || TargetInstance == nullptr || MyVehicleManager == nullptr) { return; } @@ -1328,7 +1209,7 @@ void UChaosWheeledVehicleMovementComponent::DrawDebug(UCanvas* Canvas, float& YL { FVector2D MaxSize = GetWheelLayoutDimensions(); - // Draw a top down representation of the wheels in position, with the directionl forces being shown + // Draw a top down representation of the wheels in position, with the direction forces being shown for (int32 WheelIdx = 0; WheelIdx < WheelSetups.Num(); ++WheelIdx) { @@ -1414,14 +1295,7 @@ void UChaosWheeledVehicleMovementComponent::DrawDebug(UCanvas* Canvas, float& YL , FString::Printf(TEXT("Friction %d"), ContactMat->Friction) , WheelDrawPosition.X, WheelDrawPosition.Y-95.f); } - - - // ground velocity - // wheel ground velocity - // Sx - // Accel Torque - // Brake Torque - + } } @@ -1474,10 +1348,10 @@ void UChaosWheeledVehicleMovementComponent::DrawDebug(UCanvas* Canvas, float& YL float SteerAngle = DegToRad(PWheel.GetSteeringAngle()); FVector2D Tire = FVector2D(FMath::Sin(SteerAngle), -FMath::Cos(SteerAngle)) * 30.0f; FVector2D WPt = WheelDrawPosition; + DrawLine2D(Canvas, WPt - Tire, WPt + Tire, FColor::Black, 8); if (SteeringSetup.SteeringType == ESteeringType::Ackermann) { - DrawLine2D(Canvas, WPt - Tire, WPt + Tire, FColor::Black, 8); FVector2D C1, P, C2; PSteering.Ackermann.GetLeftHingeLocations(C1, P, C2); @@ -1509,72 +1383,6 @@ void UChaosWheeledVehicleMovementComponent::DrawDebug(UCanvas* Canvas, float& YL } - // draw longitudinal friction slip curve for each wheel - //if (DebugPage == EDebugPages::FrictionPage) - //{ - // for (int WheelIdx=0; WheelIdx < PVehicle->Wheels.Num(); WheelIdx++) - // { - // int GraphWidth = 100; int GraphHeight = 60; int Spacing = 35; - // int GraphXPos = 500 + (GraphWidth+Spacing) * (int)(WheelIdx % 2); - // int GraphYPos = 50 + (GraphHeight + Spacing) * (int)(WheelIdx / 2); - // float XSample = PVehicle->Wheels[WheelIdx].GetNormalizedLongitudinalSlip(); - // - // FVector2D CurrentValue(XSample, Chaos::FSimpleWheelSim::GetNormalisedFrictionFromSlipAngle(XSample)); - // Canvas->DrawDebugGraph(FString::Printf(TEXT("Longitudinal Slip Graph [%d]"), WheelIdx) - // , CurrentValue.X, CurrentValue.Y - // , GraphXPos, GraphYPos - // , GraphWidth, GraphHeight - // , FVector2D(0, 1), FVector2D(1, 0)); - - // float Step = 0.02f; - // FVector2D LastPoint; - // for (float X = 0; X < 1.0f; X += Step) - // { - // float Y = Chaos::FSimpleWheelSim::GetNormalisedFrictionFromSlipAngle(X); - // FVector2D NextPoint(GraphXPos + GraphWidth * X, GraphYPos + GraphHeight - GraphHeight * Y); - // if (X > SMALL_NUMBER) - // { - // DrawLine2D(Canvas, LastPoint, NextPoint, FColor::Cyan); - // } - // LastPoint = NextPoint; - // } - // } - - //} - - //// draw lateral friction slip curve for each wheel - //if (DebugPage == EDebugPages::FrictionPage) - //{ - // for (int WheelIdx = 0; WheelIdx < PVehicle->Wheels.Num(); WheelIdx++) - // { - // int GraphWidth = 100; int GraphHeight = 60; int Spacing = 35; - // int GraphXPos = 500 + (GraphWidth + Spacing) * (int)(WheelIdx % 2); - // int GraphYPos = 350 + (GraphHeight + Spacing) * (int)(WheelIdx / 2); - // float XSample = PVehicle->Wheels[WheelIdx].GetNormalizedLateralSlip(); - - // FVector2D CurrentValue(XSample, Chaos::FSimpleWheelSim::GetNormalisedFrictionFromSlipAngle(XSample)); - // Canvas->DrawDebugGraph(FString::Printf(TEXT("Lateral Slip Graph [%d]"), WheelIdx) - // , CurrentValue.X, CurrentValue.Y - // , GraphXPos, GraphYPos - // , GraphWidth, GraphHeight - // , FVector2D(0, 1), FVector2D(1, 0)); - - // float Step = 0.02f; - // FVector2D LastPoint; - // for (float X = 0; X < 1.0f; X += Step) - // { - // float Y = Chaos::FSimpleWheelSim::GetNormalisedFrictionFromSlipAngle(X); - // FVector2D NextPoint(GraphXPos + GraphWidth * X, GraphYPos + GraphHeight - GraphHeight * Y); - // if (X > SMALL_NUMBER) - // { - // DrawLine2D(Canvas, LastPoint, NextPoint, FColor::Cyan); - // } - // LastPoint = NextPoint; - // } - // } - - //} - // draw engine torque curve - just putting engine under transmission if (DebugPage == EDebugPages::TransmissionPage && PVehicle->HasTransmission()) { @@ -1724,6 +1532,8 @@ void UChaosWheeledVehicleMovementComponent::DrawDebug(UCanvas* Canvas, float& YL void UChaosWheeledVehicleMovementComponent::DrawDebug3D() { +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + Super::DrawDebug3D(); FBodyInstance* TargetInstance = GetBodyInstance(); @@ -1761,20 +1571,6 @@ void UChaosWheeledVehicleMovementComponent::DrawDebug3D() FVector End2 = WorldLocation + WorldDirection * PSuspension.Setup().SuspensionMaxDrop; DrawDebugLine(GetWorld(), Start2 + VehicleRightAxis, End2 + VehicleRightAxis, FColor::Yellow, false, -1.f, 0, 3.f); - - - //FVector Up(0.f, 0.f, -PSuspension.Setup().SuspensionMaxRaise); - //FVector Down(0.f, 0.f, PSuspension.Setup().SuspensionMaxDrop); - - //Up = BodyTransform.TransformVector(Up); - //Down = BodyTransform.TransformVector(Down); - - //FVector Position = PSuspension.GetLocalRestingPosition(); - //Position = BodyTransform.TransformPosition(Position); - - //DrawDebugLine(GetWorld(), Position + Up + VehicleRightAxis, Position + Down + VehicleRightAxis, FColor::Orange, false, -1.f, 0, 3.f); - - } } @@ -1809,9 +1605,10 @@ void UChaosWheeledVehicleMovementComponent::DrawDebug3D() DrawDebugLine(GetWorld(), TraceEnd, TraceEnd + VehicleRightAxis, FColor::White, false, -1.f, 0, 1.f); } } +#endif } -#if WITH_EDITOR +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) float UChaosWheeledVehicleMovementComponent::CalcDialAngle(float CurrentValue, float MaxValue) { diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/SimpleCarActor.cpp b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/SimpleCarActor.cpp deleted file mode 100644 index 40c27d8b2121..000000000000 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/SimpleCarActor.cpp +++ /dev/null @@ -1,168 +0,0 @@ -//// Copyright Epic Games, Inc. All Rights Reserved. -// -#include "SimpleCarActor.h" -//#include "Components/SkeletalMeshComponent.h" -//#include "Engine/CollisionProfile.h" -//#include "WheeledVehicleComponent.h" -//#include "AxleVehicleMovementComponent.h" -//#include "DisplayDebugHelpers.h" -// -//PRAGMA_DISABLE_OPTIMIZATION -// -//FName ASimpleCarActor::VehicleMeshComponentName(TEXT("VehicleMesh")); -// -//ASimpleCarActor::ASimpleCarActor(const FObjectInitializer& ObjectInitializer) -// : Super(ObjectInitializer) -//{ -//// UE_LOG(LogChaos, Log, TEXT("ASimpleCarActor::ASimpleCarActor()")); -// -// Mesh = CreateDefaultSubobject(VehicleMeshComponentName); -//// Mesh->SetCollisionProfileName(UCollisionProfile::Vehicle_ProfileName); -// Mesh->BodyInstance.bSimulatePhysics = true; // TEMP -// Mesh->BodyInstance.bNotifyRigidBodyCollision = true; -// Mesh->BodyInstance.bUseCCD = true; -// Mesh->bBlendPhysics = true; -// Mesh->SetGenerateOverlapEvents(true); -// Mesh->SetCanEverAffectNavigation(false); -// RootComponent = Mesh; -// -// // Base classes: -// PrimaryActorTick.bCanEverTick = true; -// PrimaryActorTick.bAllowTickOnDedicatedServer = false; -// -// //FieldSystemComponent = CreateDefaultSubobject(TEXT("FieldSystemComponent")); -// //RootComponent = FieldSystemComponent; -// const float Gravity = 9.8f; -// float MaxSimTime = 15.0f; -// float DeltaTime = 1.f / 30.f; -// float SimulatedTime = 0.f; -// float VehicleSpeedMPH = 30.f; -// float VehicleMass = 1300.f; -// MassPerWheel = 1300.f / 4.f; -// -// CoreWheel.Add(FSimpleWheelConfig()); -// Wheel.Add(&CoreWheel[CoreWheel.Num()-1]); -// // Wheel[0].Initialize(); -// -// Wheel[0].SetWheelLoadForce(MassPerWheel * Gravity); -// -// // Road speed -// V.X = MPHToMS(VehicleSpeedMPH); -// -// // wheel rolling speed matches road speed -// Wheel[0].SetMatchingSpeed(V.X); -// -//// auto& Mesh = GetMesh(); -// -// -// //if (USkinnedMeshComponent* SkinnedMesh = GetMesh()) -// //{ -// // if (const FBodyInstance* WheelBI = SkinnedMesh->GetBodyInstance(WheelSetup.BoneName)) -// // { -// // int fred = 0; -// // fred++; -// // } -// //} -//} -// -// -//void ASimpleCarActor::PostInitializeComponents() -//{ -// Super::PostInitializeComponents(); -// -// int FLIndex = Mesh->GetBoneIndex("F_L_wheelJNT"); -// int FRIndex = Mesh->GetBoneIndex("F_R_wheelJNT"); -// int BLIndex = Mesh->GetBoneIndex("B_L_wheelJNT"); -// int BRIndex = Mesh->GetBoneIndex("B_R_wheelJNT"); -// -// FVector FLOffset = Mesh->GetBoneLocation("F_L_wheelJNT", EBoneSpaces::ComponentSpace); // local -// FVector FROffset = Mesh->GetBoneLocation("F_R_wheelJNT", EBoneSpaces::ComponentSpace); -// FVector BLOffset = Mesh->GetBoneLocation("B_L_wheelJNT", EBoneSpaces::ComponentSpace); -// FVector BROffset = Mesh->GetBoneLocation("B_R_wheelJNT", EBoneSpaces::ComponentSpace); -// -// FLTrans = Mesh->GetBoneTransform(FLIndex); -// FRTrans = Mesh->GetBoneTransform(FRIndex); -// BLTrans = Mesh->GetBoneTransform(BLIndex); -// BRTrans = Mesh->GetBoneTransform(BRIndex); -// //FVector FLOffset2 = Mesh->GetBoneLocation("F_L_wheelJNT", EBoneSpaces::WorldSpace); // world -// //FVector FROffset2 = Mesh->GetBoneLocation("F_R_wheelJNT", EBoneSpaces::WorldSpace); -// //FVector BLOffset2 = Mesh->GetBoneLocation("B_L_wheelJNT", EBoneSpaces::WorldSpace); -// //FVector BROffset2 = Mesh->GetBoneLocation("B_R_wheelJNT", EBoneSpaces::WorldSpace); -// -// //auto* Root = GetRootComponent(); -// //auto Matrix = Mesh->GetBoneMatrix(0); -// //int Index = Mesh->GetBoneIndex("F_R_wheelJNT"); -// //const FBodyInstance* WheelBIT = Mesh->GetBodyInstance("F_R_wheelJNT"); -// -// //TArray SkeletalMeshComponents; -// //GetComponents(SkeletalMeshComponents); -// -// //for (USkeletalMeshComponent* SKMeshCmp : SkeletalMeshComponents) -// //{ -// // TArray Names = SKMeshCmp->GetAllSocketNames(); -// // int fred = 0; -// // fred++; -// -// // //const FVector BonePosition = SKMeshCmp->GetBoneIndex("F_R_wheelJNT");// .GetOrigin()* Mesh->GetRelativeScale3D(); -// // int BI = SKMeshCmp->GetBoneIndex("F_R_wheelJNT"); -// -// // if (const FBodyInstance* WheelBI = SKMeshCmp->GetBodyInstance("F_R_wheelJNT")) -// // { -// // fred++; -// // } -// //} -//} -// -////bool ASimpleCarActor::IsLevelBoundsRelevant() const -////{ -//// return false; -////} -// -///** -// * ticks the actor -// * @param DeltaTime The time slice of this tick -// * @param TickType The type of tick that is happening -// * @param ThisTickFunction The tick function that is firing, useful for getting the completion handle -// */ -//void ASimpleCarActor::TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction) -//{ -// Super::TickActor(DeltaTime, TickType, ThisTickFunction); -///* -// float DT = FMath::Min(DeltaTime, (1.f / 30.f)); -// -// //FTransform MyTransform = GetWorldTransform(); -// FTransform Transform = this->GetActorTransform(); -// FVector P = this->GetActorLocation() * 0.01f; -// -// //FVector& P = MyTransform.GetLocation(); -// Wheel[0].SetBrakeTorque(650); -// -// // rolling speed matches road speed -// Wheel[0].SetRoadSpeed(V); -// -// Wheel[0].Simulate(DT); -// -// // deceleration from brake -// V.X += DT * Wheel[0].GetForceFromFriction() / MassPerWheel; -// if (V.X < 0.f) -// { -// V.X = 0.f; -// } -// -// FMatrix Mat = FLTrans.ToMatrixNoScale(); -// -// Mat.SetAxis(0, FVector(FMath::Cos(Wheel[0].AngularPosition), 0, -FMath::Sin(Wheel[0].AngularPosition))); -// Mat.SetAxis(2, FVector(FMath::Sin(Wheel[0].AngularPosition), 0, FMath::Cos(Wheel[0].AngularPosition))); -// -// FLTrans.SetFromMatrix(Mat); -// FRTrans.SetFromMatrix(Mat); -// -// this->SetActorTransform(Transform); -// P.X += V.X * DT; -// SetActorLocation(P*100.0f); -// -// */ -//} -// -// -//PRAGMA_ENABLE_OPTIMIZATION diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/VehicleContactModification.cpp b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/VehicleContactModification.cpp deleted file mode 100644 index 2c2653386aef..000000000000 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/VehicleContactModification.cpp +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "VehicleContactModification.h" - -#include "Components/PrimitiveComponent.h" -#include "Components/SkeletalMeshComponent.h" -#include "PhysicsEngine/BodyInstance.h" -#include "Engine/EngineTypes.h" -#include "Physics/PhysicsFiltering.h" -#include "Chaos/ParticleHandleFwd.h" -#include "ChaosVehicleMovementComponent.h" -#include "WheeledVehiclePawn.h" -#include "DrawDebugHelpers.h" - -#if WITH_CHAOS -#include "Chaos/ParticleHandle.h" -#include "Chaos/Particle/ParticleUtilities.h" -#include "Chaos/PBDCollisionConstraints.h" -#endif - -const bool ContactModificationEnabled = true; -const bool DrawDebugLinesEnabled = true; - -#if WITH_CHAOS - -// #todo: issue with Clang compiler linking - just disable for now -// Module.ChaosVehicles.cpp.obj : error LNK2019: unresolved external symbol "public: class Chaos::FPerShapeData const * __cdecl Chaos::TGeometryParticlesImp::GetImplicitShape(int,class Chaos::FImplicitObject const *)" -#define CONTACT_MOD_ENABLE 0 - -Chaos::FCollisionModifierCallback FVehicleContactModificationFactory::Create() -{ - - using EConstraintType = Chaos::FCollisionConstraintBase::FType; - - return [](Chaos::FPBDCollisionConstraintHandle* ConstraintHandle) - { - -#if CONTACT_MOD_ENABLE - - if (ContactModificationEnabled == 0) - { - return Chaos::ECollisionModifierResult::Unchanged; - } - - Chaos::FCollisionConstraintBase* ConstraintPtr = nullptr; - Chaos::FRigidBodyMultiPointContactConstraint* MultiPointContactConstraint = nullptr; - - if (ConstraintHandle->GetType() == EConstraintType::MultiPoint) - { - MultiPointContactConstraint = &ConstraintHandle->GetMultiPointContact(); - ConstraintPtr = MultiPointContactConstraint; - } - else if (ConstraintHandle->GetType() == EConstraintType::SinglePoint) - { - ConstraintPtr = &ConstraintHandle->GetPointContact(); - } - else if (ConstraintHandle->GetType() == EConstraintType::SinglePointSwept) - { - ConstraintPtr = &ConstraintHandle->GetSweptPointContact(); - } - else - { - return Chaos::ECollisionModifierResult::Unchanged; - } - - Chaos::FCollisionConstraintBase& Constraint = *ConstraintPtr; - - // Find out what channels the objects are in - const Chaos::FCollisionContact& Manifold = Constraint.GetManifold(); - Chaos::TGeometryParticleHandle* Particle0 = Constraint.Particle[0]; - Chaos::TGeometryParticleHandle* Particle1 = Constraint.Particle[1]; - const Chaos::FPerShapeData* Shape0 = Particle0->GetImplicitShape(Manifold.Implicit[0]); - const Chaos::FPerShapeData* Shape1 = Particle1->GetImplicitShape(Manifold.Implicit[1]); - if (!CHAOS_ENSURE(Shape0 != nullptr && Shape1 != nullptr)) - { - return Chaos::ECollisionModifierResult::Unchanged; - } - ECollisionChannel Channel0 = GetCollisionChannel(Shape0->GetSimData().Word3); - ECollisionChannel Channel1 = GetCollisionChannel(Shape1->GetSimData().Word3); - - if (Channel0 == Channel1) - { - // We only care about vehicles driving over static mesh. Don't bother correcting vehicle vehicle collision (Note this all assumes that only vehicles opt in for contact modification) - return Chaos::ECollisionModifierResult::Unchanged; - } - - // Make sure at least one of these is in the vehicle collision channel - if (!CHAOS_ENSURE(Channel0 == ECC_Vehicle || Channel1 == ECC_Vehicle)) - { - return Chaos::ECollisionModifierResult::Unchanged; - } - - //If all contacts are below ledge threshold we want to just drive over it - if (const Chaos::TGeometryParticleHandle * VehicleActor = Channel0 == ECC_Vehicle ? Particle0 : Particle1) - { - // NOTE: Accessing UObjects is very dangerous and is only safe because we are certain that GC cannot take place at the moment. - // Data accessed is specifically not touched while Physics is running, and the only safe data is a POD struct. Be careful. - // NOTE: We rely on the above guarantee still being true for Chaos - that is, that GC happens after TG_DuringPhysics. - - if (FBodyInstance* BI = FPhysicsUserData::Get(VehicleActor->UserData())) - { - if (UPrimitiveComponent* PrimComp = BI->OwnerComponent.Get()) - { - if (AWheeledVehiclePawn* Actor = Cast(PrimComp->GetOwner())) - { - if (UChaosVehicleMovementComponent* Vehicle = Cast(Actor->GetComponentByClass(UChaosVehicleMovementComponent::StaticClass()))) - { - const FSolverSafeContactData& ContactData = Vehicle->GetSolverSafeContactData(); - - const Chaos::TPBDRigidParticleHandle* RigidVehicleActor = VehicleActor->CastToRigidParticle(); - if (CHAOS_ENSURE(RigidVehicleActor)) - { - Chaos::FRigidTransform3 VehicleTM = Chaos::FParticleUtilitiesPQ::GetCoMWorldTransform(RigidVehicleActor); - const Chaos::FVec3 VehicleUp = VehicleTM.GetUnitAxis(EAxis::Z); - const auto ShapeToActor = Channel0 == ECC_Vehicle ? Constraint.ImplicitTransform[0] : Constraint.ImplicitTransform[1]; - const auto ShapeToWorld = ShapeToActor * VehicleTM; - - bool bChanges = false; - - // Number of points, use just one for single point contacts - const int32 NumPoints = MultiPointContactConstraint ? MultiPointContactConstraint->NumManifoldPoints() : 1; - - for (int32 ContactIdx = 0; ContactIdx < NumPoints; ++ContactIdx) - { - Chaos::FVec3 WorldContactPt; - - if (MultiPointContactConstraint) - { - WorldContactPt = MultiPointContactConstraint->GetManifoldPoint(ContactIdx); - } - else - { - WorldContactPt = Constraint.GetLocation(); - } - - const auto LocalPtOnVehicle = ShapeToWorld.InverseTransformPosition(WorldContactPt); - - bool bFloorFrictionApplied = false; - - if (LocalPtOnVehicle.Z <= ContactData.ContactModificationOffset) - { - Constraint.Manifold.Friction = ContactData.VehicleFloorFriction; - bFloorFrictionApplied = true; - bChanges = true; - - FVector OldManifoldNormal = Manifold.Normal; - - /////////////////////////////////////////////////////////////////////////// - // Travelling at speed, alter terrain normal so it's not killing speed, only - // preventing the vehicle chassis from pressing into the ground - const Chaos::FVec3 VehicleV = RigidVehicleActor->V(); - if (VehicleV.SizeSquared() > 1.0f) - { - FVector VDirectionNormal = VehicleV.GetSafeNormal(); - float KillAmount = FVector::DotProduct(Manifold.Normal, VDirectionNormal); - - Constraint.Manifold.Friction = 0.0f; // slips easily - Constraint.Manifold.Normal = FVector(0.f, 1.f, 0.f); - bChanges = true; - - if (false && KillAmount < 0.0f) - { - FVector KillVector = VDirectionNormal * KillAmount; - - Constraint.Manifold.Normal -= KillVector; - Constraint.Manifold.Normal.SafeNormalize(); - - - if (DrawDebugLinesEnabled) - { - FVector Start = WorldContactPt; - FVector End = WorldContactPt + OldManifoldNormal * 100; - DrawDebugLine(Vehicle->GetWorld(), Start, End, FColor::Red, true, 2.0f, 0, 2.f); - - FVector NewEnd = WorldContactPt + Constraint.Manifold.Normal * 100; - DrawDebugLine(Vehicle->GetWorld(), Start, NewEnd, FColor::Green, true, 2.0f, 0, 2.f); - } - - } - - } - - - - - // if (Manifold.Implicit[0]->GetType() == Chaos::ImplicitObjectType::Convex) //TODO: this is super hacky - //{ - // const float Separation = Manifold.Phi; - // const float VehicleMaxPenetration = 5.0f; //??? - // if (-Separation < VehicleMaxPenetration) - // { - // const auto UpNormal = ShapeToWorld.GetUnitAxis(EAxis::Z); - // const auto OriginalNormal = Manifold.Normal; - - // if (Chaos::FVec3::DotProduct(OriginalNormal, UpNormal) > 0.f) //don't try normal correction if it makes you lose the contact - // { - // Constraint.Manifold.Normal = UpNormal; - // } - - // if (DrawDebugLinesEnabled) - // { - // FVector Start = WorldContactPt; - // FVector End = WorldContactPt + Manifold.Normal * 100; - // DrawDebugLine(Vehicle->GetWorld(), Start, End, FColor::Red, true, 2.0f, 0, 2.f); - - // FVector NewEnd = WorldContactPt + UpNormal * 100; - // DrawDebugLine(Vehicle->GetWorld(), Start, NewEnd, FColor::Green, true, 2.0f, 0, 2.f); - // } - // } - //} - - } - - //if (!bFloorFrictionApplied && bVehicleIsDriving) - //{ - // //want to reduce friction on side of vehicle unless it's sideways on the ground - // Constraint.Manifold.Friction = ContactData.VehicleSideScrapeFriction; - // bChanges = true; - //} - } - - if (bChanges) - { - return Chaos::ECollisionModifierResult::Modified; - } - } // if (CHAOS_ENSURE(RigidVehicleActor)) - } - } - } - } - } -#endif - return Chaos::ECollisionModifierResult::Unchanged; - }; - - -} - - -#endif // WITH_CHAOS \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosTireConfig.h b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosTireConfig.h deleted file mode 100644 index 735ec9fb39d9..000000000000 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosTireConfig.h +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "Engine/DataAsset.h" -#include "ChaosTireConfig.generated.h" - -class UPhysicalMaterial; - -/** Allows overriding of friction of this tire config on a specific material */ -USTRUCT() -struct FTireFrictionPerMaterial -{ - GENERATED_USTRUCT_BODY() - - /** Physical material for friction scale */ - UPROPERTY(EditAnywhere, Category = FTireFrictionPerMaterial) - UPhysicalMaterial* PhysicalMaterial; - - /** Friction scale for this type of material */ - UPROPERTY(EditAnywhere, Category = FTireFrictionPerMaterial) - float FrictionScale; - - FTireFrictionPerMaterial() - : PhysicalMaterial(nullptr) - , FrictionScale(1.0f) - { - } -}; - -/** Represents a type of tire surface used to specify friction values against physical materials. */ -UCLASS() -class CHAOSVEHICLES_API UChaosTireConfig : public UDataAsset -{ - GENERATED_BODY() - -private: - - // Scale the tire friction for this tire type - UPROPERTY(EditAnywhere, Category = TireFrictionConfig) - float FrictionScale; - - /** Tire friction scales for specific physical materials. */ - UPROPERTY(EditAnywhere, Category = TireFrictionConfig) - TArray TireFrictionScales; - -private: - - // Tire config ID - uint32 TireConfigID; - -public: - - UChaosTireConfig(); - - // All loaded tire types - used to assign each tire type a unique TireConfigID - static TArray> AllTireConfigs; - - /** - * Getter for FrictionScale - */ - float GetFrictionScale() { return FrictionScale; } - - /** - * Setter for FrictionScale - */ - void SetFrictionScale(float NewFrictionScale); - - /** Set friction scaling for a particular material */ - void SetPerMaterialFrictionScale(UPhysicalMaterial* PhysicalMaterial, float NewFrictionScale); - - /** - * Getter for TireConfigID - */ - int32 GetTireConfigID() { return TireConfigID; } - - /** - * Called after the C++ constructor and after the properties have been initialized, but before the config has been loaded, etc. - * mainly this is to emulate some behavior of when the constructor was called after the properties were initialized. - */ - virtual void PostInitProperties() override; - - /** - * Called before destroying the object. This is called immediately upon deciding to destroy the object, to allow the object to begin an - * asynchronous cleanup process. - */ - virtual void BeginDestroy() override; - - /** Get the friction for this tire config on a particular physical material */ - float GetTireFriction(UPhysicalMaterial* PhysicalMaterial); - -#if WITH_EDITOR - - /** - * Respond to a property change in editor - */ - virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; - -#endif //WITH_EDITOR - -protected: - - /** - * Add this tire type to the TireConfigs array - */ - void NotifyTireFrictionUpdated(); - - -}; diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosVehicleMovementComponent.h b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosVehicleMovementComponent.h index ef4e65cac0a1..5e9ea08fb662 100644 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosVehicleMovementComponent.h +++ b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosVehicleMovementComponent.h @@ -34,23 +34,15 @@ struct FVehicleDebugParams bool DisableTorqueControl = false; bool DisableStabilizeControl = false; bool DisableAerodynamics = false; + bool DisableAerofoils = false; + bool DisableThrusters = false; bool BatchQueries = true; float ForceDebugScaling = 0.0006f; - float PersistDebugLinesTime = 0.01f; -}; - -// #todo: contact modification -struct FSolverSafeContactData -{ - /** This is read from off GT and needs to be completely thread safe. Modifying it in Update is safe because physx has not run yet. Constructor is also fine. Keep all data simple and thread safe like floats and ints */ - float ContactModificationOffset; - float VehicleFloorFriction; - float VehicleSideScrapeFriction; + float SleepCounterThreshold = 15; }; struct FBodyInstance; -// #todo: are these too wheeled vehicle specific? USTRUCT() struct CHAOSVEHICLES_API FVehicleReplicatedState { @@ -84,7 +76,7 @@ struct CHAOSVEHICLES_API FVehicleReplicatedState UPROPERTY() float HandbrakeInput; - // state replication: target gear #todo: or current gear? + // state replication: gear UPROPERTY() int32 TargetGear; @@ -254,13 +246,16 @@ struct FVehicleState , LastFrameVehicleLocalVelocity(FVector::ZeroVector) , ForwardSpeed(0.f) , ForwardsAcceleration(0.f) - , bVehicleInAir(false) + , NumWheelsOnGround(0) + , bAllWheelsOnGround(false) + , bVehicleInAir(true) , bSleeping(false) + , SleepCounter(0) { } - /** Cache some useful data */ + /** Cache some useful data at the start of the frame */ void CaptureState(FBodyInstance* TargetInstance, float GravityZ, float DeltaTime); FTransform VehicleWorldTransform; @@ -280,8 +275,11 @@ struct FVehicleState float ForwardSpeed; float ForwardsAcceleration; + int NumWheelsOnGround; + bool bAllWheelsOnGround; bool bVehicleInAir; bool bSleeping; + int SleepCounter; }; USTRUCT() @@ -320,9 +318,9 @@ UENUM() enum class EVehicleAerofoilType : uint8 { Fixed = 0, - Wing, - Rudder, - Elevator + Wing, // affected by Roll input + Rudder, // affected by steering/yaw input + Elevator // affected by Pitch input }; @@ -330,8 +328,10 @@ UENUM() enum class EVehicleThrustType : uint8 { Fixed = 0, - HelicopterRotor, // affected by pitch/roll inputs - Rudder // affected by steering/yaw input + Wing, // affected by Roll input + Rudder, // affected by steering/yaw input + Elevator, // affected by Pitch input +// HelicopterRotor, // affected by pitch/roll inputs }; @@ -490,7 +490,6 @@ public: UPROPERTY(EditAnywhere, Category = VehicleSetup, meta = (ClampMin = "0.01", UIMin = "0.01")) float Mass; - //#todo: entering area directly might be better for general shapes that are not boxes /** Chassis width used for drag force computation (cm)*/ UPROPERTY(EditAnywhere, Category = VehicleSetup, meta = (ClampMin = "0.01", UIMin = "0.01")) float ChassisWidth; @@ -519,6 +518,14 @@ public: UPROPERTY(EditAnywhere, Category=VehicleSetup, AdvancedDisplay) FVector InertiaTensorScale; + /** Option to apply some aggressive sleep logic, larger number is more agressive, 0 disables */ + UPROPERTY(EditAnywhere, Category = VehicleSetup) + float SleepThreshold; + + /** Option to apply some aggressive sleep logic if slopes up Z is less than this value, i.e value = Cos(SlopeAngle) so 0.866 will sleep up to 30 degree slopes */ + UPROPERTY(EditAnywhere, Category = VehicleSetup, meta = (ClampMin = "0.01", UIMin = "0.01", ClampMax = "1.0", UIMax = "1.0")) + float SleepSlopeLimit; + /** Optional aerofoil setup - can be used for car spoilers or aircraft wings/elevator/rudder */ UPROPERTY(EditAnywhere, Category = AerofoilSetup) TArray Aerofoils; @@ -696,10 +703,6 @@ public: return PVehicle; } - //const FSolverSafeContactData& GetSolverSafeContactData() const - //{ - // return SolverSafeContactData; - //} protected: // replicated state of vehicle @@ -867,11 +870,8 @@ protected: /** Apply direct control over vehicle body rotation */ virtual void ApplyTorqueControl(float DeltaTime); - /** Apply on ground control torque to vehicle body */ - //virtual void ApplyGroundControl(float DeltaTime); - - // #todo: use this properly - void CopyToSolverSafeContactStaticData(); + /** Option to aggressively sleep the vehicle */ + virtual void ProcessSleeping(); /** Pass current state to server */ UFUNCTION(reliable, server, WithValidation) @@ -896,6 +896,9 @@ protected: /** Create and setup the Chaos vehicle */ virtual void CreateVehicle(); + /** Skeletal mesh needs some special handling in the vehicle case */ + virtual void FixupSkeletalMesh() {} + /** Allocate and setup the Chaos vehicle */ virtual void SetupVehicle(); @@ -954,10 +957,6 @@ private: UPROPERTY(transient, Replicated) AController* OverrideController; - // #todo: contact modification - //FSolverSafeContactData SolverSafeContactData; - - // #todo: generic/common setup into seperate class, no individual params in main class? const Chaos::FSimpleAerodynamicsConfig& GetAerodynamicsConfig() { FillAerodynamicsSetup(); @@ -966,7 +965,6 @@ private: void FillAerodynamicsSetup() { - // #todo PAerodynamicsSetup.DragCoefficient = this->DragCoefficient; PAerodynamicsSetup.DownforceCoefficient = this->DownforceCoefficient; PAerodynamicsSetup.AreaMetresSquared = Cm2ToM2(this->DragArea); diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosVehicleWheel.h b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosVehicleWheel.h index 03d02aa1845f..d8a5a8e2b177 100644 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosVehicleWheel.h +++ b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosVehicleWheel.h @@ -149,10 +149,6 @@ using namespace Chaos; UPROPERTY(EditAnywhere, Category = Wheel) bool bTractionControlEnabled; - /** Tire type for the wheel. Determines friction */ - UPROPERTY(EditAnywhere, Category = Tire) - class UChaosTireConfig* TireConfig; - ///** Max normalized tire load at which the tire can deliver no more lateral stiffness no matter how much extra load is applied to the tire. */ //UPROPERTY(EditAnywhere, Category = Tire, meta = (ClampMin = "0.01", UIMin = "0.01")) //float LatStiffMaxLoad; @@ -238,7 +234,6 @@ using namespace Chaos; UPROPERTY(EditAnywhere, Category = Brakes) float MaxHandBrakeTorque; - //#todo: make sure everything from here down is actually implemented /** The vehicle that owns us */ UPROPERTY(transient) class UChaosWheeledVehicleMovementComponent* VehicleSim; @@ -383,8 +378,6 @@ using namespace Chaos; // These are calculated later from the PSuspensionConfig.DampingRatio // PSuspensionConfig.ReboundDamping // PSuspensionConfig.CompressionDamping - - // SuspensionConfig.Swaybar; #todo: best way to configure this? Not yet implemented } Chaos::FSimpleWheelConfig PWheelConfig; diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosWheeledVehicleMovementComponent.h b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosWheeledVehicleMovementComponent.h index 5a5d8742a705..0945f718cdd0 100644 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosWheeledVehicleMovementComponent.h +++ b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/ChaosWheeledVehicleMovementComponent.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "Physics/PhysicsInterfaceCore.h" #include "UObject/ObjectMacros.h" #include "ChaosVehicleMovementComponent.h" #include "Curves/CurveFloat.h" @@ -24,7 +25,6 @@ struct FWheeledVehicleDebugParams bool DisableSuspensionForces = false; bool DisableFrictionForces = false; bool DisableRollbarForces = false; - bool ApplyWheelForcetoSurface = true; float ThrottleOverride = 0.f; float SteeringOverride = 0.f; @@ -130,9 +130,6 @@ struct FVehicleEngineConfig UPROPERTY(EditAnywhere, Category = Setup, meta = (ClampMin = "0.01", UIMin = "0.01")) float EngineRevDownRate; - /** Find the peak torque produced by the TorqueCurve */ - //float FindPeakTorque() const; - const Chaos::FSimpleEngineConfig& GetPhysicsEngineConfig() { FillEngineSetup(); @@ -141,8 +138,6 @@ struct FVehicleEngineConfig void InitDefaults() { - //TorqueCurve.GetRichCurve().AddKey(0.0f, 1.0f); - //TorqueCurve.GetRichCurve().AddKey(1.0f, 0.0f); MaxTorque = 300.0f; MaxRPM = 4500.0f; EngineIdleRPM = 1200.0f; @@ -156,8 +151,6 @@ private: void FillEngineSetup() { - // #todo: better Chaos torque curve representation - // The source curve does not need to be normalized, however we are normalizing it when it is passed on, // since it's the MaxRPM and MaxTorque values that determine the range of RPM and Torque PEngineConfig.TorqueCurve.Empty(); @@ -252,9 +245,6 @@ struct FVehicleTransmissionConfig GearChangeTime = 0.4f; TransmissionEfficiency = 0.9f; - - // #todo: probably need something like this - // NeutralGearUpRatio } private: @@ -398,6 +388,7 @@ struct FWheelState Trace.SetNum(NumWheels); } + /** Commonly used Wheel state - evaluated once used wherever required for that frame */ void CaptureState(int WheelIdx, const FVector& WheelOffset, const FBodyInstance* TargetInstance); TArray WheelWorldLocation; /** Current Location Of Wheels In World Coordinates */ @@ -452,8 +443,6 @@ class CHAOSVEHICLES_API UChaosWheeledVehicleMovementComponent : public UChaosVeh UFUNCTION(BlueprintCallable, Category = "Game|Components|ChaosWheeledVehicleMovement") float GetEngineMaxRotationSpeed() const; - float GetMaxSpringForce() const; //?? - ////////////////////////////////////////////////////////////////////////// // Public @@ -512,6 +501,9 @@ protected: /** Re-Compute any runtime constants values that rely on setup data */ virtual void ComputeConstants() override; + /** Skeletal mesh needs some special handling in the vehicle case */ + virtual void FixupSkeletalMesh(); + /** Allocate and setup the Chaos vehicle */ virtual void SetupVehicle() override; @@ -578,7 +570,7 @@ protected: FVector2D CalculateWheelLayoutDimensions(); bool IsWheelSpinning() const; -#if WITH_EDITOR +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) float CalcDialAngle(float CurrentValue, float MaxValue); void DrawDial(UCanvas* Canvas, FVector2D Pos, float Radius, float CurrentValue, float MaxValue); #endif diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/SimpleCarActor.h b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/SimpleCarActor.h deleted file mode 100644 index 141242af6725..000000000000 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/SimpleCarActor.h +++ /dev/null @@ -1,229 +0,0 @@ -//// Copyright Epic Games, Inc. All Rights Reserved. -// -#pragma once -// -//#include "Components/PrimitiveComponent.h" -//#include "CoreMinimal.h" -//#include "GameFramework/Pawn.h" -// -//#include "WheelSystem.h" -//#include "TransmissionSystem.h" -// -//#include "SimpleCarActor.generated.h" -// -//using namespace Chaos; -// -//UENUM() -//enum class EVehicleTransmissionType : uint8 -//{ -// Manual, -// Automatic -//}; -// -//USTRUCT(Blueprintable) -//struct FEngineConfig -//{ -// GENERATED_BODY() -// -// UPROPERTY(EditAnywhere, Category = Vehicle) -// UCurveFloat* TorqueCurve; -// -// UPROPERTY(EditAnywhere, Category = Engine, meta = (UIMin = "0.0", UIMax = "20000.0")) -// float MaxRPM; -// -// UPROPERTY(EditAnywhere, Category = Engine, meta = (UIMin = "0.0", UIMax = "1.0")) -// float EngineBrakingEffect; -//}; -// -//USTRUCT(Blueprintable) -//struct FTransConfig -//{ -// GENERATED_BODY() -// -// UPROPERTY(EditAnywhere, Category = Transmission) -// TArray ForwardRatios; // Gear ratios for forward gears -// -// UPROPERTY(EditAnywhere, Category = Transmission) -// TArray ReverseRatios; // Gear ratios for reverse Gear(s) -// -// UPROPERTY(EditAnywhere, Category = Transmission) -// float FinalDriveRatio; // Final drive ratio [1.0 for arcade vehicles] -// -// //float ClutchTorque; -// //float TransmissionLoss; -// -// UPROPERTY(EditAnywhere, Category = Transmission) -// uint32 ChangeUpRPM; // [RPM or % max RPM?] -// -// UPROPERTY(EditAnywhere, Category = Transmission) -// uint32 ChangeDownRPM; // [RPM or % max RPM?] -// -// UPROPERTY(EditAnywhere, Category = Transmission) -// float GearChangeTime; // [sec] -// -// UPROPERTY(EditAnywhere, Category = Transmission) -// EVehicleTransmissionType TransmissionType; // Specify Automatic or Manual transmission -// -// UPROPERTY(EditAnywhere, Category = Transmission) -// bool AutoReverse; // Arcade handling - holding Brake switches into reverse after vehicle has stopped -// -//}; -// -// -//USTRUCT(Blueprintable) -//struct FWheelConfig -//{ -// GENERATED_BODY() -// -// UPROPERTY(EditAnywhere, Category = Wheel) -// FVector Offset; -// -// UPROPERTY(EditAnywhere, Category = Wheel) -// float WheelMass; -// -// UPROPERTY(EditAnywhere, Category = Wheel) -// float MaxSteeringAngle; -// -// UPROPERTY(EditAnywhere, Category = Wheel) -// float MaxBrakeTorque; -// -// UPROPERTY(EditAnywhere, Category = Wheel) -// float MaxHandbrakeTorque; -// -// UPROPERTY(EditAnywhere, Category = Wheel) -// bool AbsEnabled; -// -// UPROPERTY(EditAnywhere, Category = Wheel) -// bool HandbrakeEnabled; -// -// UPROPERTY(EditAnywhere, Category = Wheel) -// bool SteeringEnabled; -// -// UPROPERTY(EditAnywhere, Category = Wheel) -// bool EngineEnabled; -// -// UPROPERTY(EditAnywhere, Category = Wheel) -// bool SingleWheelOnAxle; -// -// -//}; -// -// -//USTRUCT(Blueprintable) -//struct FSuspensionConfig -//{ -// GENERATED_BODY() -// -// UPROPERTY(EditAnywhere, Category = Suspension) -// FVector MaxLength; -// -// UPROPERTY(EditAnywhere, Category = Suspension) -// FVector MinLength; -// -// UPROPERTY(EditAnywhere, Category = Suspension) -// FVector Force; -// -// UPROPERTY(EditAnywhere, Category = Suspension) -// FVector CompressionDamping; -// -// UPROPERTY(EditAnywhere, Category = Suspension) -// FVector ReboundDamping; -// -// UPROPERTY(EditAnywhere, Category = Suspension) -// FVector Swaybar; -// -// UPROPERTY(EditAnywhere, Category = Suspension) -// FVector CastorAngle; -// -// UPROPERTY(EditAnywhere, Category = Suspension) -// FVector CamberAngle; -// -// UPROPERTY(EditAnywhere, Category = Suspension) -// FVector ToeOutAngle; -// -//}; -// -//USTRUCT(Blueprintable) -//struct FAxleConfig -//{ -// GENERATED_BODY() -// -// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Vehicle) -// FWheelConfig Wheel; -// -// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Vehicle) -// FSuspensionConfig Suspension; -//}; -// -// -// -//UCLASS(meta = (BlueprintSpawnableComponent)) -//class CHAOSVEHICLES_API ASimpleCarActor : public APawn -//{ -// GENERATED_BODY() -//public: -// ASimpleCarActor(const FObjectInitializer& ObjectInitializer); -// -// // Begin AActor interface. -// virtual void PostInitializeComponents() override; -// //virtual bool IsLevelBoundsRelevant() const override { return false; } -// -// /** -// * ticks the actor -// * @param DeltaTime The time slice of this tick -// * @param TickType The type of tick that is happening -// * @param ThisTickFunction The tick function that is firing, useful for getting the completion handle -// */ -// virtual void TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction) override; -// // End AActor interface. -// -// /** The main skeletal mesh associated with this Vehicle */ -// UPROPERTY(Category = Vehicle, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) -// class USkeletalMeshComponent* Mesh; -// -// /** vehicle simulation component */ -// UPROPERTY(Category = Vehicle, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) -// class UVehicleMovementComponent* VehicleMovement; -// -// /** Name of the MeshComponent. Use this name if you want to prevent creation of the component (with ObjectInitializer.DoNotCreateDefaultSubobject). */ -// static FName VehicleMeshComponentName; -// -// /** Name of the VehicleMovement. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ -// static FName VehicleMovementComponentName; -// -// /** Util to get the wheeled vehicle movement component */ -// class UVehicleMovementComponent* GetVehicleMovementComponent() const; -// -// /** Returns Mesh subobject **/ -// class USkeletalMeshComponent* GetMesh() const { return Mesh; } -// /** Returns VehicleMovement subobject **/ -// class UVehicleMovementComponent* GetVehicleMovement() const { return VehicleMovement; } -// -// -// -// -// TArray Wheel; -// //FTransmissionDynamicData Transmission; -// -// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Vehicle) -// FEngineConfig EngineSetup; -// -// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Vehicle) -// FTransConfig TransmissionSetup; -// -// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Vehicle) -// TArray Axle; -// -// // Temp -// float MassPerWheel; -// FVector V; -// -// FTransform FLTrans; -// FTransform FRTrans; -// FTransform BLTrans; -// FTransform BRTrans; -// -// TArray CoreWheel; -// -// -//}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/VehicleContactModification.h b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/VehicleContactModification.h deleted file mode 100644 index 2c8a2e01f8b9..000000000000 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Public/VehicleContactModification.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "PhysicsPublic.h" -#include "Chaos/CollisionResolutionTypes.h" - -#if WITH_CHAOS - -class FVehicleContactModificationFactory -{ -public: - static Chaos::FCollisionModifierCallback Create(); -}; - -#endif \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehiclesEditor/Private/ChaosVehiclesEditorCommands.cpp b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehiclesEditor/Private/ChaosVehiclesEditorCommands.cpp index 3ec8810be673..7e76f8854c60 100644 --- a/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehiclesEditor/Private/ChaosVehiclesEditorCommands.cpp +++ b/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehiclesEditor/Private/ChaosVehiclesEditorCommands.cpp @@ -6,7 +6,6 @@ #include "Engine/World.h" #include "EngineUtils.h" #include "ChaosVehicles.h" -#include "SimpleCarActor.h" #include "Logging/LogMacros.h" #include "Editor.h" diff --git a/Engine/Plugins/Experimental/ControlRig/Source/ControlRig/Private/Sequencer/MovieSceneControlRigParameterTemplate.cpp b/Engine/Plugins/Experimental/ControlRig/Source/ControlRig/Private/Sequencer/MovieSceneControlRigParameterTemplate.cpp index ebadf865bbba..2119f1871931 100644 --- a/Engine/Plugins/Experimental/ControlRig/Source/ControlRig/Private/Sequencer/MovieSceneControlRigParameterTemplate.cpp +++ b/Engine/Plugins/Experimental/ControlRig/Source/ControlRig/Private/Sequencer/MovieSceneControlRigParameterTemplate.cpp @@ -15,6 +15,7 @@ #include "Evaluation/Blending/BlendableTokenStack.h" #include "Evaluation/Blending/MovieSceneBlendingActuatorID.h" #include "TransformNoScale.h" +#include "SkeletalMeshRestoreState.h" //#include "Particles/ParticleSystemComponent.h" @@ -416,17 +417,12 @@ struct FControlRigParameterPreAnimatedTokenProducer : IMovieScenePreAnimatedToke { FToken(FMovieSceneSequenceIDRef InSequenceID) : SequenceID(InSequenceID) -#if WITH_EDITOR - ,bUpdateAnimationInEditor(true) -#endif { } void SetSkelMesh(USkeletalMeshComponent* InComponent) { -#if WITH_EDITOR - bUpdateAnimationInEditor = InComponent->GetUpdateAnimationInEditor(); -#endif + SkeletalMeshRestoreState.SaveState(InComponent); } virtual void RestoreState(UObject& InObject, IMovieScenePlayer& Player) override @@ -438,9 +434,7 @@ struct FControlRigParameterPreAnimatedTokenProducer : IMovieScenePreAnimatedToke { if (USkeletalMeshComponent* SkeletalMeshComponent = Cast(ControlRig->GetObjectBinding()->GetBoundObject())) { -#if WITH_EDITOR - SkeletalMeshComponent->SetUpdateAnimationInEditor(bUpdateAnimationInEditor); -#endif + SkeletalMeshRestoreState.RestoreState(SkeletalMeshComponent); } FControlRigBindingHelper::UnBindFromSequencerInstance(ControlRig); for (TNameAndValue& Value : ScalarValues) @@ -493,9 +487,8 @@ struct FControlRigParameterPreAnimatedTokenProducer : IMovieScenePreAnimatedToke TArray< TNameAndValue > VectorValues; TArray< TNameAndValue > Vector2DValues; TArray< TNameAndValue > TransformValues; -#if WITH_EDITOR - bool bUpdateAnimationInEditor; -#endif + FSkeletalMeshRestoreState SkeletalMeshRestoreState; + }; diff --git a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigDeveloper/Private/ControlRigBlueprint.cpp b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigDeveloper/Private/ControlRigBlueprint.cpp index 83596928deb8..6ff70934b0cf 100644 --- a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigDeveloper/Private/ControlRigBlueprint.cpp +++ b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigDeveloper/Private/ControlRigBlueprint.cpp @@ -75,6 +75,8 @@ void UControlRigBlueprint::InitializeModelIfRequired() return true; }); + Controller->RemoveStaleNodes(); + for (int32 i = 0; i < UbergraphPages.Num(); ++i) { if (UControlRigGraph* Graph = Cast(UbergraphPages[i])) diff --git a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.cpp b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.cpp index 8c97f6e7d693..570fb1bcb7f9 100644 --- a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.cpp +++ b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.cpp @@ -94,6 +94,7 @@ FControlRigEditor::FControlRigEditor() , NodeDetailStruct(nullptr) , NodeDetailName(NAME_None) , bExecutionControlRig(true) + , LastDebuggedRig() { } @@ -861,6 +862,8 @@ void FControlRigEditor::Compile() { DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC() + LastDebuggedRig.Empty(); + FString LastDebuggedObjectName = GetCustomDebugObjectLabel(GetBlueprintObj()->GetObjectBeingDebugged()); GetBlueprintObj()->SetObjectBeingDebugged(nullptr); @@ -948,6 +951,18 @@ void FControlRigEditor::Compile() // FStatsHierarchical::DumpMeasurements(LogForMeasurements); } +void FControlRigEditor::SaveAsset_Execute() +{ + LastDebuggedRig = GetCustomDebugObjectLabel(GetBlueprintObj()->GetObjectBeingDebugged()); + FBlueprintEditor::SaveAsset_Execute(); +} + +void FControlRigEditor::SaveAssetAs_Execute() +{ + LastDebuggedRig = GetCustomDebugObjectLabel(GetBlueprintObj()->GetObjectBeingDebugged()); + FBlueprintEditor::SaveAssetAs_Execute(); +} + FName FControlRigEditor::GetToolkitFName() const { return FName("ControlRigEditor"); @@ -1541,6 +1556,22 @@ void FControlRigEditor::OnBlueprintChangedImpl(UBlueprint* InBlueprint, bool bIs if(bIsJustBeingCompiled) { UpdateControlRig(); + + if (!LastDebuggedRig.IsEmpty()) + { + TArray DebugList; + GetCustomDebugObjects(DebugList); + + for (const FCustomDebugObject& DebugObject : DebugList) + { + if (DebugObject.NameOverride == LastDebuggedRig) + { + GetBlueprintObj()->SetObjectBeingDebugged(DebugObject.Object); + LastDebuggedRig.Empty(); + break; + } + } + } } } } diff --git a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.h b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.h index ce8f41cb23a2..2521a363fa70 100644 --- a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.h +++ b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.h @@ -161,6 +161,8 @@ protected: virtual void CreateDefaultCommands() override; virtual void OnCreateGraphEditorCommands(TSharedPtr GraphEditorCommandsList); virtual void Compile() override; + virtual void SaveAsset_Execute() override; + virtual void SaveAssetAs_Execute() override; virtual bool IsInAScriptingMode() const override { return true; } virtual void CreateDefaultTabContents(const TArray& InBlueprints) override; virtual bool IsSectionVisible(NodeSectionID::Type InSectionID) const override; @@ -336,6 +338,8 @@ protected: void OnPinControlNameListChanged(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo); void OnPinControlNameListComboBox(const TArray>* InNameList); + FString LastDebuggedRig; + friend class FControlRigEditorMode; friend class SControlRigStackView; friend class SRigHierarchy; diff --git a/Engine/Plugins/Experimental/ForwardingChannels/ForwardingChannels.uplugin b/Engine/Plugins/Experimental/ForwardingChannels/ForwardingChannels.uplugin deleted file mode 100644 index 9316986a92a2..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/ForwardingChannels.uplugin +++ /dev/null @@ -1,20 +0,0 @@ -{ - "FileVersion": 3, - - "FriendlyName": "Network Forwarding Channels Plugin", - "Version": 1, - "VersionName": "1.0", - "Description": "Networking Data Channels that can be used for Forward data from a single Channel on a Server Connection to multiple Channels across multiple Client Connections.", - "Category": "Voice", - "CreatedBy": "Epic Games, Inc.", - "CreatedByURL": "http://epicgames.com", - "EnabledByDefault": false, - - "Modules": [ - { - "Name": "ForwardingChannels", - "Type": "Runtime", - "LoadingPhase": "Default" - } - ] -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/ForwardingChannels.Build.cs b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/ForwardingChannels.Build.cs deleted file mode 100644 index fb8c47e56414..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/ForwardingChannels.Build.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; - -public class ForwardingChannels : ModuleRules -{ - public ForwardingChannels(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - - PublicDependencyModuleNames.AddRange( - new string[] { - "Core", - "CoreUObject", - "Engine", - } - ); - } -} diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannel.cpp b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannel.cpp deleted file mode 100644 index 29ab5ccdce37..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannel.cpp +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "ForwardingChannel.h" -#include "ForwardingGroup.h" -#include "ForwardingChannelsSubsystem.h" -#include "Net/DataBunch.h" -#include "Engine/NetConnection.h" - -namespace ForwardingChannels -{ - TSharedPtr FForwardingChannel::CreateChannel( - const FCreateChannelParams& Params, - UForwardingChannelsSubsystem* Subsystem) - { - TSharedPtr ForwardingChannel; - - if (Params.GroupName != NAME_None && Subsystem != nullptr) - { - TSharedPtr Group = Subsystem->GetOrCreateForwardingGroup(Params.GroupName); - if (Group.IsValid()) - { - TSharedRef Channel = MakeShareable(new FForwardingChannel(Params, Group.ToSharedRef())); - if (Group->RegisterChannel(*Channel)) - { - ForwardingChannel = Channel; - } - } - } - - return ForwardingChannel; - } - - FForwardingChannel::FForwardingChannel( - const FCreateChannelParams& Params, - const TSharedRef& InGroup) - - : bIsServerChannel(Params.bIsServer) - , bIsPeerChannel(Params.bIsPeer) - , Reliability(Params.Reliability) - , ResendExpiration(Params.ResendExpiration) - , Group(InGroup) - { - } - - FForwardingChannel::~FForwardingChannel() - { - Group->UnregisterChannel(*this); - } - - void FForwardingChannel::OnSubsystemDeinitialized() - { - ToSend.Empty(); - UnreliableResends.Empty(); - } - - void FForwardingChannel::FlushPackets( - ForwardingChannels::FSendPacketType SendPacket, - const double CurrentTime, - const int32 LastAckedPacket) - { - if (!Group->IsSubsystemInitialized()) - { - return; - } - if (!ensureMsgf(SendPacket, TEXT("SendPacket must be provided!"))) - { - return; - } - - const double LocalResendExpiration = ResendExpiration; - const bool bUsingReliability = (GetReliability() != EChannelReliability::None); - TArray NewlyPendingPackets; - - auto HasPacketExpired = [LocalResendExpiration, CurrentTime](const FPendingPacket& ToCheck) - { - return LocalResendExpiration > 0.f && ToCheck.InitiallySent > 0.f && ((CurrentTime - ToCheck.InitiallySent) > LocalResendExpiration); - }; - - // Returns True if we can try another packet or False if we need to stop. - auto TrySendPacket = [&SendPacket, &NewlyPendingPackets, bUsingReliability, CurrentTime](FPendingPacket&& Packet) - { - const FSendPacketReturnType Return = SendPacket(Packet.Packet); - - if (ESendPacketResult::BadPacket != Return.Result) - { - // Regardless of whether or not this bunch was sent, if we're using custom reliability - // go ahead and track the send time of the bunch and put it into the correct queue. - // We can skip this if the packet was sent reliably already. - if (bUsingReliability && !Return.bSentReliably) - { - if (Packet.InitiallySent == 0) - { - Packet.InitiallySent = CurrentTime; - } - - Packet.bWasNakd = false; - NewlyPendingPackets.Emplace(MoveTemp(Packet)); - } - - // Something went wrong trying to send. Assume no future sends will succeed this tick, so we're done for now. - if (ESendPacketResult::Saturated == Return.Result) - { - return false; - } - } - - return true; - }; - - - // TODO: This might be cleaner if we use TResizableCircularQueue, and don't worry about evicting - // expired packets until we attempt to send them. The downside being that we may hold onto - // packet memory longer than necessary if the sender is saturated. - // However, popping items from a TResizableCircularQueue doesn't destruct the item being popped - // and all references are const. - - { - int32 Index = 0; - for (; Index < ToSend.Num(); ++Index) - { - if (!TrySendPacket(FPendingPacket{ ToSend[Index] })) - { - // If the connection is saturated, or we can't send anymore packets this flush, - // then we're done. - break; - } - } - - ToSend.RemoveAt(0, Index); - } - - if (bUsingReliability) - { - int32 Index = 0; - for (; Index < UnreliableResends.Num(); ++Index) - { - FPendingPacket& Packet = UnreliableResends[Index]; - if (HasPacketExpired(Packet) || (!Packet.bWasNakd && Packet.PacketRange.Last <= LastAckedPacket)) - { - // Packet has expired or was Ackd, we don't need to resend or track it anymore. - continue; - } - - if (!Packet.bWasNakd) - { - // If we reach a packet that hasn't been NAKd or if we fail to send the - // latest packet, we're done for now. - break; - } - - if (!TrySendPacket(MoveTemp(Packet))) - { - // In this case, we will have put the packet into the NewlyPendingPackets list already, - // so make sure we handle that. - ++Index; - break; - } - } - - // Remove any packets that we don't need to process anymore, - // but go ahead and add in any packets that we'll need to try again later. - const bool bAllowShrinking = (NewlyPendingPackets.Num() == 0); - UnreliableResends.RemoveAt(0, Index, bAllowShrinking); - UnreliableResends.Append(NewlyPendingPackets); - } - } - - void FForwardingChannel::ReceivedNak(int32 NakPacketId) - { - if (!Group->IsSubsystemInitialized() || - EChannelReliability::None == GetReliability()) - { - return; - } - - for (int32 i = 0; i < UnreliableResends.Num(); ++i) - { - FPendingPacket& Packet = UnreliableResends[i]; - - // Skipped passed any already NAKd packets - if (Packet.bWasNakd) - { - continue; - } - - // NAKs should be handled in order, so treat this as an implicit ACK - // because it means we must have received all the packets up to this one. - if (NakPacketId > Packet.PacketRange.Last) - { - UnreliableResends.RemoveAt(i, 1); - continue; - } - - if (Packet.PacketRange.InRange(NakPacketId)) - { - Packet.bWasNakd = true; - } - else - { - // We've gone beyond the NAKd packet, so we're done. - break; - } - } - } - - void FForwardingChannel::QueuePacketUnchecked(const TSharedRef& PacketToSend) - { - ToSend.Add(PacketToSend); - } - - void FForwardingChannel::QueuePacketsUnchecked(const TArray>& PacketsToSend) - { - ToSend.Append(PacketsToSend); - } - - void FPacketHelper::QueuePacketUnchecked(FForwardingChannel& Channel, const TSharedRef& PacketToSend) - { - Channel.QueuePacketUnchecked(PacketToSend); - } - - void FPacketHelper::QueuePacketsUnchecked(FForwardingChannel& Channel, const TArray>& PacketsToSend) - { - Channel.QueuePacketsUnchecked(PacketsToSend); - } - - bool FForwardingChannel::IsGroupInitialized() const - { - return Group->IsSubsystemInitialized(); - } -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannelsModule.cpp b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannelsModule.cpp deleted file mode 100644 index b092f53f0a02..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannelsModule.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "ForwardingChannelsModule.h" - -IMPLEMENT_MODULE(FForwardingChannelsModule, ForwardingChannels) - -void FForwardingChannelsModule::StartupModule() -{ -} - -void FForwardingChannelsModule::ShutdownModule() -{ -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannelsModule.h b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannelsModule.h deleted file mode 100644 index 6ab96fff6349..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannelsModule.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "Modules/ModuleManager.h" -#include "Net/UnrealNetwork.h" -#include "Tickable.h" - -class IOnlineSubsystemUtils; - -class FForwardingChannelsModule : public IModuleInterface -{ -public: - - FForwardingChannelsModule() = default; - virtual ~FForwardingChannelsModule() = default; - - // IModuleInterface - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - static inline bool IsAvailable() - { - return FModuleManager::Get().IsModuleLoaded(GetModuleName()); - } - -protected: - - static FName GetModuleName() - { - static FName ModuleName = FName(TEXT("ForwardingChannels")); - return ModuleName; - } -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannelsSubsystem.cpp b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannelsSubsystem.cpp deleted file mode 100644 index 8b5b0ce63842..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingChannelsSubsystem.cpp +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "ForwardingChannelsSubsystem.h" -#include "ForwardingChannel.h" -#include "ForwardingGroup.h" -#include "Engine/GameInstance.h" -#include "EngineLogs.h" - -namespace ForwardingChannelsSubsystemPrivate -{ - template - void ForEachFactory(TArray>& ForwardingChannelFactories, const FunctorType& Functor) - { - for (auto It = ForwardingChannelFactories.CreateIterator(); It; ++It) - { - TScriptInterface& Factory = *It; - - if (!Factory) - { - It.RemoveCurrent(); - continue; - } - - Functor(*Factory); - } - } -} - -void UForwardingChannelsSubsystem::Initialize(FSubsystemCollectionBase& Collection) -{ - bIsInitialized = true; -} - -void UForwardingChannelsSubsystem::Deinitialize() -{ - using namespace ForwardingChannels; - - for (TPair> NameAndGroup : ChannelGroupsByName) - { - TSharedPtr Group = NameAndGroup.Value.Pin(); - if (Group.IsValid()) - { - Group->OnSubsystemDeinitialized(); - } - } - - ChannelGroupsByName.Empty(); - bIsInitialized = false; -} - -TSharedPtr UForwardingChannelsSubsystem::CreateChannel(const ForwardingChannels::FCreateChannelParams& Params) -{ - using namespace ForwardingChannels; - - TSharedPtr CreatedChannel; - - if (!bIsInitialized) - { - UE_LOG(LogNet, Warning, - TEXT("UForwardingChannelsSubsystem::CreateChannel: Unable to create channel while subsystem is uninitialized. Group Name = %s"), - *Params.GroupName.ToString()); - } - else if (Params.GroupName == NAME_None) - { - UE_LOG(LogNet, Warning, - TEXT("UForwardingChannelsSubsystem::CreateChannel: Must specify valid Group Name.")); - } - else - { - CreatedChannel = FForwardingChannel::CreateChannel(Params, this); - } - - return CreatedChannel; -} - -TSharedPtr UForwardingChannelsSubsystem::GetOrCreateForwardingGroup(const FName GroupName) -{ - using namespace ForwardingChannels; - - TSharedPtr Group; - - if (!bIsInitialized) - { - UE_LOG(LogNet, Warning, - TEXT("UForwardingChannelsSubsystem::GetOrCreateForwardingGroup: Unable to create group while subsystem is uninitialized. Group Name = %s"), - *GroupName.ToString()); - } - else if (GroupName == NAME_None) - { - UE_LOG(LogNet, Warning, - TEXT("UForwardingChannelsSubsystem::GetOrCreateForwardingGroup: Must specify valid Group Name.")); - } - else - { - Group = ChannelGroupsByName.FindRef(GroupName).Pin(); - if (!Group.IsValid()) - { - Group = MakeShared(GroupName); - ChannelGroupsByName.Add(GroupName, Group); - } - } - - return Group; -} - - -void UForwardingChannelsSubsystem::RegisterForwardingChannelFactory(TScriptInterface InFactory) -{ - if (!bIsInitialized || !InFactory) - { - return; - } - - ForwardingChannelFactories.AddUnique(InFactory); -} - -void UForwardingChannelsSubsystem::UnregisterForwardingChannelFactory(TScriptInterface InFactory) -{ - // Explicitly ignoring bIsInitialized here, because we don't clear the ForwardingChannelFactories group - // in Deinitialize. - ForwardingChannelFactories.Remove(InFactory); -} - -void UForwardingChannelsSubsystem::CreateForwardingChannels(class UNetConnection* InNetConnection) -{ - if (!bIsInitialized) - { - return; - } - - ForwardingChannelsSubsystemPrivate::ForEachFactory(ForwardingChannelFactories, [InNetConnection](IForwardingChannelFactory& Factory) - { - Factory.CreateForwardingChannel(InNetConnection); - } - ); -} - -void UForwardingChannelsSubsystem::SetAcceptClientPackets(bool bShouldAcceptClientPackets) -{ - if (!bIsInitialized) - { - return; - } - - ForwardingChannelsSubsystemPrivate::ForEachFactory(ForwardingChannelFactories, [bShouldAcceptClientPackets](IForwardingChannelFactory& Factory) - { - Factory.SetAcceptClientPackets(bShouldAcceptClientPackets); - } - ); -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingGroup.cpp b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingGroup.cpp deleted file mode 100644 index 8926550029a0..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Private/ForwardingGroup.cpp +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "ForwardingGroup.h" -#include "ForwardingChannel.h" -#include "EngineLogs.h" - -namespace ForwardingChannels -{ - FForwardingGroup::FForwardingGroup(const FName InGroupName) - : bIsSubsystemInitialized(true) - , GroupName(InGroupName) - , ServerChannel(nullptr) - { - } - - bool FForwardingGroup::RegisterChannel(FForwardingChannel& InChannel) - { - if (IsSubsystemInitialized()) - { - if (&InChannel.GetGroup().Get() == this) - { - if (InChannel.IsServerChannel()) - { - - if (nullptr != ServerChannel && ServerChannel != &InChannel) - { - UE_LOG(LogNet, Warning, - TEXT("FForwardingGroup::RegisterChannel: Registering new server channel when another is already registered. GroupName=%s"), - *GroupName.ToString()); - } - - ServerChannel = &InChannel; - return true; - } - else - { - ClientChannels.AddUnique(&InChannel); - return true; - } - } - else - { - UE_LOG(LogNet, Warning, - TEXT("FForwardingGroup::RegisterChannel: Unable to register channel for a different group. This Group=%s, Channels Group=%s"), - *GroupName.ToString(), *InChannel.GetGroup()->GetName().ToString()); - } - } - else - { - UE_LOG(LogNet, Warning, - TEXT("FForwardingGroup::RegisterChannel: Unable to register channel while subsystem is uninitialized. This Group=%s"), - *GroupName.ToString()); - } - - return false; - } - - void FForwardingGroup::UnregisterChannel(const FForwardingChannel& InChannel) - { - if (&InChannel.GetGroup().Get() == this) - { - if (InChannel.IsServerChannel()) - { - if (&InChannel == ServerChannel) - { - ServerChannel = nullptr; - } - else - { - UE_LOG(LogNet, Warning, - TEXT("FForwardingGroup::UnregisterChannel: Attempted to unregister non-associated server channel. This Group=%s, Channels Group=%s"), - *GroupName.ToString(), - *InChannel.GetGroup()->GetName().ToString()); - } - } - else - { - const int32 NumRemoved = ClientChannels.RemoveSingle(const_cast(&InChannel)); - if (NumRemoved != 1) - { - UE_LOG(LogNet, Warning, - TEXT("FForwardingGroup::UnregisterChannel: Failed to find channel being unregistered. This Group=%s, Channels Group=%s"), - *GroupName.ToString(), - *InChannel.GetGroup()->GetName().ToString()); - } - } - } - else - { - UE_LOG(LogNet, Warning, - TEXT("FForwardingGroup::RegisterChannel: Unable to unregister channel for a different group. This Group=%s, Channels Group=%s"), - *GroupName.ToString(), - *InChannel.GetGroup()->GetName().ToString()); - } - } - - void FForwardingGroup::OnSubsystemDeinitialized() - { - if (ServerChannel) - { - ServerChannel->OnSubsystemDeinitialized(); - } - - for (FForwardingChannel* ClientChannel : ClientChannels) - { - ClientChannel->OnSubsystemDeinitialized(); - } - - bIsSubsystemInitialized = false; - } - - void FForwardingGroup::ForwardPacket(const TSharedRef PacketToSend, FFilterChannelType Filter) - { - if (!IsSubsystemInitialized()) - { - return; - } - - if (!Filter) - { - // Queue up the packet to be sent the next time we tick. - for (FForwardingChannel* ClientChannel : ClientChannels) - { - FPacketHelper::QueuePacketUnchecked(*ClientChannel, PacketToSend); - } - } - else - { - // Queue up the packet to be sent the next time we tick. - for (FForwardingChannel* ClientChannel : ClientChannels) - { - if (Filter(*ClientChannel)) - { - FPacketHelper::QueuePacketUnchecked(*ClientChannel, PacketToSend); - } - } - } - } - - void FForwardingGroup::ForwardPackets(const TArray>& PacketsToSend, FFilterChannelType Filter) - { - if (!IsSubsystemInitialized() || PacketsToSend.Num() <= 0) - { - return; - } - - if (!Filter) - { - // Queue up the packet to be sent the next time we tick. - for (FForwardingChannel* ClientChannel : ClientChannels) - { - FPacketHelper::QueuePacketsUnchecked(*ClientChannel, PacketsToSend); - } - } - else - { - // Queue up the packet to be sent the next time we tick. - for (FForwardingChannel* ClientChannel : ClientChannels) - { - if (Filter(*ClientChannel)) - { - FPacketHelper::QueuePacketsUnchecked(*ClientChannel, PacketsToSend); - } - } - } - } - - void FForwardingGroup::QueuePacketOnServer(const TSharedRef PacketToSend, FFilterChannelType Filter) - { - if (!IsSubsystemInitialized() || nullptr == ServerChannel) - { - return; - } - - if (!Filter || Filter(*ServerChannel)) - { - FPacketHelper::QueuePacketUnchecked(*ServerChannel, PacketToSend); - } - } - - void FForwardingGroup::QueuePacketsOnServer(const TArray>& PacketsToSend, FFilterChannelType Filter) - { - if (!IsSubsystemInitialized() || PacketsToSend.Num() <= 0 || nullptr == ServerChannel) - { - return; - } - - if (!Filter || Filter(*ServerChannel)) - { - FPacketHelper::QueuePacketsUnchecked(*ServerChannel, PacketsToSend); - } - } - - bool FForwardingGroup::IsServerChannelAvailable() const - { - return ServerChannel != nullptr; - } -} diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannel.h b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannel.h deleted file mode 100644 index bb523107dae3..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannel.h +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "ForwardingChannelsFwd.h" -#include "UObject/CoreNet.h" -#include "Templates/Function.h" - -namespace ForwardingChannels -{ - /** Optional reliability offered by Forwarding Channels. */ - enum class EChannelReliability : uint8 - { - None, //! No reliability. Anything that is dropped is gone for good. - ResendOnNak //! We will redundantly send packets any time they are NAK'd, until the packet expires. - }; - - /** Possible results from a call to a FSendPacketType function. */ - enum class ESendPacketResult : uint8 - { - Success, //! The packet was sent successfully. - Saturated, //! The packet was unable to be sent, but may succeed if we try again later. - BadPacket, //! The packet couldn't be serialized or sent, and will never succeed. - }; - - /** Return struct used by FSendPacketType function. */ - struct FSendPacketReturnType - { - /** The result of trying to send the packet. */ - ESendPacketResult Result = ESendPacketResult::Success; - - /** Should be valid if Result == ESendPacketResult::Success */ - FPacketIdRange PacketRange; - - /** Whether or not the packet was sent reliably. If it was, we can ignore our custom reliability stuff (if it is enabled). */ - bool bSentReliably = false; - }; - - /** Function that will be passed into FForwardingChannel::FlushForwardedPackets, in order to send them. */ - using FSendPacketType = TUniqueFunction)>; - - /** Parameters used to create FForwardingChannels.*/ - struct FCreateChannelParams - { - FCreateChannelParams(const FName InGroupName) : - GroupName(InGroupName) - { - ensure(GroupName != NAME_None); - } - - /** Whether or not this channel is sending data to / receiving data from the server. */ - bool bIsServer = false; - - //~ If desired, bIsPeer and additional information specific to channel groups could - //~ be added by having some sort of Per Group Descriptor type (e.g., FPerGroupChannelData) - //~ that could be subclassed similar to FForwardingPacket. - - /** - * Whether or not this channel can communicate directly with other clients, and not just the server. - * Only meaningful for client channels. - */ - bool bIsPeer = false; - - /** The type of reliability to use. */ - EChannelReliability Reliability = EChannelReliability::None; - - /** When using custom reliability, how long we'll hold onto packets before evicting them (if not Acked). */ - float ResendExpiration = 0.5f; - - /** Name of the group this channel is associated with. */ - const FName GroupName; - }; - - /** - * A forwarding channel can be used to help marshal data from servers to clients - * across multiple server boundaries. - * - * In Forwarding Channel parlance, a "Server Channel" is a channel that is receiving - * data *from* a Server, and a Client Channel is a channel that should send data to - * clients. There is a one to many relationship between Server Channels (one) to - * Client Channels (many). - * - * Forwarding Channels don't concern themselves with how data is sent, received serialized, - * or otherwise processed. They are primarily concerned with grouping and queueing data to - * be sent, and forwarding data when desired. - * - * There are also no restrictions on sending data from Clients to the Server. - * - * The typical way FForwardingChannels are used is by creating a new UChannel type that - * can handle the necessary data. When a new instace of the UChannel type is created, - * it would also create and maintain a reference to an FForwardingChannel. As data needs to be - * sent on the Channel, the data is wrapped in a FForwardingPacket subclass and queued on the - * associated FForwardingChannel. Once per frame in UChannel::Tick (or more frequently if needed), - * FForwadingPacket::FlushPackets can be called in order to send any of the queued packets. - * If Forwarding Reliability is being used (see EChannelReliability), you'd also set up - * UChannel::ReceivedNak to call FForwardingChannel::ReceivedNak. - * - * However, there's no requirement to use UChannels and FForwardingChannel (and other Forwading - * types) try to make as few assumptions about the underlying protocols and data as possible. - * - * See FForwardingGroup for methods used to actually Forward packets to all available - * client channels. - */ - class FForwardingChannel - { - private: - - explicit FForwardingChannel( - const FCreateChannelParams& Params, - const TSharedRef& InGroup); - - public: - - //~ This is purposefully not exposed. - //~ Use UForwardingChannelSubsystem::CreateChannel instead. - static TSharedPtr CreateChannel( - const FCreateChannelParams& Params, - UForwardingChannelsSubsystem* Subsystem); - - FORWARDINGCHANNELS_API ~FForwardingChannel(); - - /** - * Notify the channel that a NAK was received so it can handle resends if necessary. - * - * @param NakPacketId The packet that was NAKed. - */ - FORWARDINGCHANNELS_API void ReceivedNak(int32 NakPacketId); - - /** - * Send any packets that have been added to this channel. - * This will include forwarded packets or packets added directly to this channel. - * - * @param SendPacket Function to send packets. - * @param CurrentTime Arbitrary time of the send (in Seconds). - * @param LastAckedPacket The last packet that we know was acked. - */ - FORWARDINGCHANNELS_API void FlushPackets( - FSendPacketType SendPacket, - const double CurrentTime, - const int32 LastAckedPacket); - - /** - * Queue up a packet on this channel. - * - * @param PacketToSend The packet to be forwarded. - */ - template - void QueuePacket(const TSharedRef& PacketToSend) - { - if (!IsGroupInitialized()) - { - return; - } - - ToSend.Add(PacketToSend); - } - - /** - * Queue packets on this channel. - * - * @param PacketsToSend The packets to be forwarded. - */ - template - void QueuePackets(const TArray, OtherAllocatorType>& PacketsToSend) - { - if (!IsGroupInitialized()) - { - return; - } - - ToSend.Append(PacketsToSend); - } - - void OnSubsystemDeinitialized(); - - bool IsServerChannel() const - { - return bIsServerChannel; - } - - bool IsPeerChannel() const - { - return bIsPeerChannel; - } - - EChannelReliability GetReliability() const - { - return Reliability; - } - - TSharedRef GetGroup() const - { - return Group; - } - - FORWARDINGCHANNELS_API bool IsGroupInitialized() const; - - private: - - void QueuePacketUnchecked(const TSharedRef& PacketToSend); - void QueuePacketsUnchecked(const TArray>& PacketsToSend); - - const bool bIsServerChannel; - const bool bIsPeerChannel; - const EChannelReliability Reliability; - const float ResendExpiration; - const TSharedRef Group; - - struct FPendingPacket - { - TSharedRef Packet; - FPacketIdRange PacketRange; - double InitiallySent = 0.f; - bool bWasNakd = false; - }; - - TArray> ToSend; - TArray UnreliableResends; - - friend class FPacketHelper; - }; - - class FPacketHelper - { - private: - - static void QueuePacketUnchecked(FForwardingChannel& Channel, const TSharedRef& PacketToSend); - static void QueuePacketsUnchecked(FForwardingChannel& Channel, const TArray>& PacketsToSend); - friend class FForwardingGroup; - }; -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelFactory.h b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelFactory.h deleted file mode 100644 index e71cd75b5eed..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelFactory.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Interface.h" -#include "ForwardingChannelFactory.generated.h" - -UINTERFACE(MinimalApi, meta = (CannotImplementInterfaceInBlueprint)) -class UForwardingChannelFactory : public UInterface -{ - GENERATED_BODY() - -}; - -/** - * Forwarding Channel Factories provide provide a simple way to create all - * necessary Forwarding Channels for a given connection / network interface type. - * - * Typically, each FForwardingGroup would have its own Factory Type, but that's - * not a requirement. - */ -class IForwardingChannelFactory -{ - GENERATED_BODY() - -public: - - /** - * Create a forwarding channel for the given UNetConnection if necessary. - * Factories may ignore this request if they are not configured properly - * or are otherwise disabled. - * - * @param InNetConnection The connection that will own the UChannel that wraps - * the Forwarding Channel. - */ - virtual void CreateForwardingChannel(class UNetConnection* InNetConnection) = 0; - - /** - * Whether or not channels created by this factory should accept incoming client - * packets. This can be useful, for example, if you want to allow only certain - * servers to accept client packets and then to replicate them down to additional - * clients. - * - * @param bAcceptClientPackets Whether or not we should accept client packets. - */ - virtual void SetAcceptClientPackets(bool bShouldAcceptClientPackets) = 0; -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsFwd.h b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsFwd.h deleted file mode 100644 index b2a027cd965c..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsFwd.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" - -class UForwardingChannelsSubsystem; - -namespace ForwardingChannels -{ - class FForwardingGroup; - class FForwardingChannel; - class FForwardingPacket; - struct FCreateChannelParams; - enum class EChannelReliability : uint8; -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsSubsystem.h b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsSubsystem.h deleted file mode 100644 index 4c4dbb01f48a..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsSubsystem.h +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "ForwardingChannelsFwd.h" -#include "Subsystems/GameInstanceSubsystem.h" -#include "ForwardingChannelFactory.h" -#include "UObject/ScriptInterface.h" -#include "ForwardingChannelsSubsystem.generated.h" - -/** - * Used to create / manage Forwarding Channels and Groups that can be used - * to help send packets between multiple servers and clients. - */ -UCLASS(DisplayName = "Forwarding Channels Subsystem", Transient) -class FORWARDINGCHANNELS_API UForwardingChannelsSubsystem : public UGameInstanceSubsystem -{ - GENERATED_BODY() - -public: - - //~ Begin USubsystem Interface - virtual void Initialize(FSubsystemCollectionBase& Collection) override; - virtual void Deinitialize() override; - //~ End USubsystem Interface - - /** - * Create a new Forwarding channel with the given parameters. - * The channel will be registered with the appropriate FForwardingGroup (creating it if necessary). - * The channel will be unregistered from the FForwardingGroup upon destruction. - * - * @param Params Params to use for creation. - * - * @return The newly created FForwardingChannel. May be null if creation of the Channel or Group failed. - */ - TSharedPtr CreateChannel(const ForwardingChannels::FCreateChannelParams& Params); - - /** - * Find or create the specified FForwardingGroup. - * - * @param GroupName The name of the group to find or create. - * - * @return The found (or created) FForwardingGroup. May be null if a group didn't already exist and we failed - to create one. - */ - TSharedPtr GetOrCreateForwardingGroup(const FName GroupName); - - /** - * Registers the given Forwarding Channel Factory so it can receive callbacks to create - * forwarding channels when necessary. - * - * @param InFactory the Factory to register. - */ - void RegisterForwardingChannelFactory(TScriptInterface InFactory); - - /** - * Unregisters the given Forwarding Channel Factory. - * - * @param InFactory the Factory to register. - */ - void UnregisterForwardingChannelFactory(TScriptInterface InFactory); - - /** - * Request that all registered Forwarding Channel Factories create forwarding channels / groups - * that will be owned by the given Net Connection. - * - * This is typically called on servers to create UChannels, and clients will create the necessary - * forwarding channels when they receive the UChannel open notification. - * - * Even though a factory is registered, there's no guarantee it will create a forwarding channel. - * - * @param InNetConnection The Net Connection that will own the UChannel for the Forwarding Channel. - */ - void CreateForwardingChannels(class UNetConnection* InNetConnection); - - /** - * Request that all registered Forwarding Channel Factories accept or ignore client packets. - * @see IForwardingChannelFactory::SetAcceptClientPackets. - * - * @param bool bShouldAcceptClientPackets - */ - void SetAcceptClientPackets(bool bShouldAcceptClientPackets); - -private: - - UPROPERTY() - TArray> ForwardingChannelFactories; - - TMap> ChannelGroupsByName; - bool bIsInitialized = false; -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsUtils.h b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsUtils.h deleted file mode 100644 index 72098e3681f0..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsUtils.h +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "ForwardingChannel.h" -#include "ForwardingGroup.h" -#include "Engine/Channel.h" -#include "Engine/NetConnection.h" -#include "Engine/World.h" -#include "Engine/GameInstance.h" -#include "ForwardingChannelsSubsystem.h" - -namespace ForwardingChannels -{ - //~ This file defines common default implementations for various forwarding channel functions, - //~ as well as other utilities. - //~ These implementations should be changed sparingly! - //~ If there's a new use case or new options are needed, it's always safer (and likely faster) - //~ to just write custom implementations for that case. - - /** - * Create a default Filter that prevents forwarding to a channel that received the packet - * and peer connections. - */ - static FFilterChannelType CreateDefaultForwardingFilter(FForwardingChannel& FromChannel) - { - return [&FromChannel](const ForwardingChannels::FForwardingChannel& ToChannel) - { - return (&ToChannel != &FromChannel) && (!ToChannel.IsPeerChannel()); - }; - } - - /** - * Convenience method to create a forwarding channel for a UChannel. - * - * @param Channel The UChannel that will be associated with the Forwarding Channel. - * @param Params Custom Forwarding Channel Params. This is typically constructed with - * the ChName of the channel as the Forwarding Group Name. - * - * @return The Forwarding Channel if it was created successfully. May return nullptr. - */ - static TSharedPtr CreateDefaultForwardingChannel(UChannel& Channel, FCreateChannelParams Params) - { - UNetConnection* Connection = Channel.Connection; - if (Connection && Connection->Driver && Connection->Driver->World) - { - if (UGameInstance * GameInstance = Connection->Driver->World->GetGameInstance()) - { - if (UForwardingChannelsSubsystem * ForwardingChannelsSubsystem = GameInstance->GetSubsystem()) - { - Params.bIsServer = !Connection->Driver->IsServer(); - return ForwardingChannelsSubsystem->CreateChannel(Params); - } - } - } - - return nullptr; - } - - /** Simple default options for CreateDefaultSendPacket. */ - enum class EDefaultSendPacketFlags : uint8 - { - None = 0, - AllowMerging = 1 << 0, //! Sends should allow bunches to be merged. - IgnoreSaturation = 1 << 1, //! Sends should ignore saturation checks (and instead rely on SendBunch failing). - }; - ENUM_CLASS_FLAGS(EDefaultSendPacketFlags); - - /** Templated function reference that should be used to determine whether or not a packet is reliable. */ - template - using TIsPacketReliable = TFunctionRef; - - /** Templated function reference that should be used to write a packet to an FOutBunch. */ - template - using TWritePacket = TFunctionRef; - - /** - * Creates a simle default implementation for FSendPacketType. - * This will check saturation, create a bunch, serialize the packet into the bunch, - * and attempt to send it on the given channel. - * - * @param Channel The channel to send on. - * @param SendFlags Flags used to customize send behavior. - * @param IsPacketReliable Function that can be used to determine if a packet is reliable. - * @param WritePacket Function that can be used to write a packet to an Out Bunch. - */ - template - static void DefaultFlushPacketsForChannel( - UChannel& Channel, - FForwardingChannel& ForwardingChannel, - const EDefaultSendPacketFlags SendFlags, - TIsPacketReliable IsPacketReliable, - TWritePacket WritePacket) - { - const bool bIgnoreSaturation = EnumHasAnyFlags(SendFlags, EDefaultSendPacketFlags::IgnoreSaturation); - const bool bAllowMerging = EnumHasAnyFlags(SendFlags, EDefaultSendPacketFlags::AllowMerging); - if (UNetConnection* Connection = Channel.Connection) - { - FSendPacketType SendPacket = [&Channel, &IsPacketReliable, &WritePacket, Connection, bIgnoreSaturation, bAllowMerging](TSharedRef InPacket) - { - TSharedRef Packet = StaticCastSharedRef(InPacket); - - FSendPacketReturnType Result; - Result.bSentReliably = Channel.OpenAcked == false || IsPacketReliable(*Packet); - - if (!bIgnoreSaturation && !Connection->IsNetReady(0)) - { - Result.Result = ESendPacketResult::Saturated; - } - else - { - FOutBunch Bunch(&Channel, 0); - - // First send must be reliable as must any packet marked reliable - Bunch.bReliable = Result.bSentReliably; - - // Append the packet data (copies into the bunch) - WritePacket(Bunch, *Packet); - - // Don't submit the bunch if something went wrong - if (Bunch.IsError() == false) - { - // Submit the bunching with merging on - Result.PacketRange = Channel.SendBunch(&Bunch, 1); - } - else - { - UE_LOG(LogNet, Warning, TEXT("Bunch error: Channel = %s"), *Channel.Describe()); - Result.Result = ESendPacketResult::BadPacket; - } - } - - return Result; - }; - - const int32 LastAckedPacket = Connection->OutAckPacketId; - const double CurrentTime = Connection->Driver->LastTickDispatchRealtime; - ForwardingChannel.FlushPackets(MoveTemp(SendPacket), LastAckedPacket, CurrentTime); - } - } -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingGroup.h b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingGroup.h deleted file mode 100644 index 7c7dd945e1c7..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingGroup.h +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "ForwardingChannelsFwd.h" -#include "CoreMinimal.h" -#include "Containers/Array.h" -#include "Containers/ArrayView.h" -#include "Templates/Function.h" - -namespace ForwardingChannels -{ - /** - * Function that can be used to filter out channels when forwarding packets. - * - * @param Channel The channel that may be filtered. - * - * @return True if the forwarded packets should be passed to the channel, false otherwise. - */ - using FFilterChannelType = TUniqueFunction; - - /** - * Forwarding groups track sets of Channels that will be used for data forwarding. - * There is a one to many relationship between Server Channels (one) and Client Channels (many). - */ - class FForwardingGroup - { - public: - explicit FForwardingGroup(const FName InGroupName); - - /** - * Called to register a channel with a forwarding group. - * - * @param InChannel The Channel to register. Does nothing if the FForwardingChannelSubsystem is - * not currently initialized. - */ - bool RegisterChannel(FForwardingChannel& InChannel); - - /** - * Called to unregister a channel with a forwarding group. - * - * @param InChannel The Channel to unregister. Always removes the channel from the group regardless - * of whether or not FForwardingChannelSubsystem is initialized. - */ - void UnregisterChannel(const FForwardingChannel& InChannel); - - FName GetName() const - { - return GroupName; - } - - bool IsSubsystemInitialized() const - { - return bIsSubsystemInitialized; - } - - void OnSubsystemDeinitialized(); - - /** - * Forward a packet to be queued up on clients. - * - * @param PacketToSend The packet to be forwarded. - * @param Filter Optional filter that can be passed in to prevent forwarding the packet to particular channels. - */ - FORWARDINGCHANNELS_API void ForwardPacket(const TSharedRef PacketToSend, FFilterChannelType Filter = FFilterChannelType()); - - /** - * Forward packets to be queued up on clients. - * - * @param PacketsToSend The packets to be forwarded. - * @param Filter Optional filter that can be passed in to prevent forwarding packets to particular channels. - */ - FORWARDINGCHANNELS_API void ForwardPackets(const TArray>& PacketsToSend, FFilterChannelType Filter = FFilterChannelType()); - - /** - * Queue a packet on the Server channel. - * Does nothing if the Server channel isn't valid / registered (@see IsServerChannelAvailable). - * - * @param PacketToSend The packet to be queued. - * @param Filter Optional filter that can be passed in to prevent forwarding the packet to particular channels. - */ - FORWARDINGCHANNELS_API void QueuePacketOnServer(const TSharedRef PacketToSend, FFilterChannelType Filter = FFilterChannelType()); - - /** - * Queue a packets on the Server channel. - * Does nothing if the Server channel isn't valid / registered (@see IsServerChannelAvailable). - * - * @param PackestToSend The packets to be queued. - * @param Filter Optional filter that can be passed in to prevent forwarding the packet to particular channels. - */ - FORWARDINGCHANNELS_API void QueuePacketsOnServer(const TArray>& PacketsToSend, FFilterChannelType Filter = FFilterChannelType()); - - /** Whether or not the Server Channel is currently available / registered. */ - FORWARDINGCHANNELS_API bool IsServerChannelAvailable() const; - - private: - - bool bIsSubsystemInitialized; - const FName GroupName; - - // We explicitly don't use TWeakPtr here for a few reasons: - // 1. We explicitly tie the lifetime of a FForwardingChannel registration. - // That is, we won't allow you to create an FForwardingChannel unless they are registered - // and upon deletion, they are automatically unregistered. - // - // 2. If we tried to use Weak Pointers, we wouldn't be able to do automatic registration - // as mentioned above. TWeakPtr has no way to compare its internal pointer directly - // to an arbitrary pointer. The interfaces provided will attempt to create a shared - // pointer from the weak pointer and then compare, but since we do unregistration - // from the destructor, the reference count will already be 0 and the pointer will - // be invalid. - // - // - // These are also not exposed via Getters / Setters to prevent external caching which could - // lead to dangling points. - // - // If we absolutely needed that, we may be able to store these as WeakPointers, and then - // try to verify that either the ServerChannel is already invalid or that there is 1 (and - // only one) invalid entry in ClientChannels, and that none of the other entries in - // ClientChannels point to the entry trying to be unregistered. - FForwardingChannel* ServerChannel; - TArray ClientChannels; - }; -} diff --git a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingPacket.h b/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingPacket.h deleted file mode 100644 index 39dd43555822..000000000000 --- a/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingPacket.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -namespace ForwardingChannels -{ - class FForwardingPacket - { - public: - FForwardingPacket() - { - } - - virtual ~FForwardingPacket() {} - }; -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryCache/Source/GeometryCacheTracks/Private/MovieSceneGeometryCacheSection.cpp b/Engine/Plugins/Experimental/GeometryCache/Source/GeometryCacheTracks/Private/MovieSceneGeometryCacheSection.cpp index 75f17c4a3997..eaf1915a1c81 100644 --- a/Engine/Plugins/Experimental/GeometryCache/Source/GeometryCacheTracks/Private/MovieSceneGeometryCacheSection.cpp +++ b/Engine/Plugins/Experimental/GeometryCache/Source/GeometryCacheTracks/Private/MovieSceneGeometryCacheSection.cpp @@ -46,8 +46,15 @@ void UMovieSceneGeometryCacheSection::PostLoad() { Super::PostLoad(); - FFrameRate DisplayRate = GetTypedOuter()->GetDisplayRate(); - FFrameRate TickResolution = GetTypedOuter()->GetTickResolution(); + UMovieScene* MovieSceneOuter = GetTypedOuter(); + + if (!MovieSceneOuter) + { + return; + } + + FFrameRate DisplayRate = MovieSceneOuter->GetDisplayRate(); + FFrameRate TickResolution = MovieSceneOuter->GetTickResolution(); if (Params.StartOffset_DEPRECATED != GeometryCacheDeprecatedMagicNumber) { diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/DynamicMesh.Build.cs b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/DynamicMesh.Build.cs index a369ed28820d..1e3cd6e7bcce 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/DynamicMesh.Build.cs +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/DynamicMesh.Build.cs @@ -12,7 +12,9 @@ public class DynamicMesh : ModuleRules new string[] { "Core", "GeometricObjects", - "GeometryAlgorithms" + "GeometryAlgorithms", + + "MeshUtilitiesCommon" // currently required for FAllocator2D used in FDynamicMeshUVPacker } ); diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/GroupTopology.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/GroupTopology.cpp index 1dafcdb79f99..21ece8cc57c9 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/GroupTopology.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/GroupTopology.cpp @@ -136,6 +136,16 @@ bool FGroupTopology::RebuildTopology() } +void FGroupTopology::RetargetOnClonedMesh(const FDynamicMesh3* NewMesh) +{ + Mesh = NewMesh; + for (FGroupEdge& Edge : Edges) + { + Edge.Span.Mesh = NewMesh; + } +} + + bool FGroupTopology::IsCornerVertex(int VertexID) const { FIndex3i UniqueGroups; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshCurvature.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshCurvature.cpp index 88f86ed6447e..7b5f071e0f12 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshCurvature.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshCurvature.cpp @@ -2,7 +2,9 @@ #include "MeshCurvature.h" #include "MeshWeights.h" +#include "MeshNormals.h" #include "VectorUtil.h" +#include "Async/ParallelFor.h" @@ -120,7 +122,7 @@ double TGaussianCurvature(const FDynamicMesh3& mesh, int32 v_i, GetPositionFuncT } else { - return (FMathd::TwoPi - AngleSum);// / MixedAreaSum; + return (FMathd::TwoPi - AngleSum) / MixedAreaSum; } } @@ -136,3 +138,63 @@ double UE::MeshCurvature::GaussianCurvature(const FDynamicMesh3& Mesh, int32 Ver return TGaussianCurvature(Mesh, VertexIndex, VertexPositionFunc); } + + + + +void FMeshVertexCurvatureCache::BuildAll(const FDynamicMesh3& Mesh) +{ + int32 NumVertices = Mesh.MaxVertexID(); + Curvatures.SetNum(NumVertices); + + TArray IsValidFlag; + IsValidFlag.Init(false, NumVertices); + + ParallelFor(NumVertices, [&](int32 vid) + { + if (Mesh.IsVertex(vid)) + { + if (Mesh.IsBoundaryVertex(vid)) + { + Curvatures[vid] = FVertexCurvature(); + return; + } + + FVector3d VertexNormal = FMeshNormals::ComputeVertexNormal(Mesh, vid); + + //double Area = FMeshWeights::VoronoiArea(Mesh, vid); + //double GaussCurvature = UE::MeshCurvature::GaussianCurvature(Mesh, vid) / Area; + + double GaussCurvature = UE::MeshCurvature::GaussianCurvature(Mesh, vid); + + FVector3d MeanCurvatureNormal = UE::MeshCurvature::MeanCurvatureNormal(Mesh, vid); + double MeanCurvature = 0.5 * MeanCurvatureNormal.Length(); + double Dot = MeanCurvatureNormal.Dot(VertexNormal); + MeanCurvature *= FMathd::Sign(Dot); + + double DeltaCurvature = FMathd::Max(0, MeanCurvature * MeanCurvature - GaussCurvature); + DeltaCurvature = FMathd::Sqrt(DeltaCurvature); + double PrincipalCurvature1 = MeanCurvature + DeltaCurvature; + double PrincipalCurvature2 = MeanCurvature - DeltaCurvature; + + Curvatures[vid] = { MeanCurvature, GaussCurvature, PrincipalCurvature1, PrincipalCurvature2 }; + IsValidFlag[vid] = true; + } + }); + + + MeanRange = FInterval1d::Empty(); + GaussianRange = FInterval1d::Empty(); + MaxPrincipalRange = FInterval1d::Empty(); + MinPrincipalRange = FInterval1d::Empty(); + for (int32 k = 0; k < NumVertices; ++k) + { + if (IsValidFlag[k]) + { + MeanRange.Contain(Curvatures[k].Mean); + GaussianRange.Contain(Curvatures[k].Gaussian); + MaxPrincipalRange.Contain(Curvatures[k].MaxPrincipal); + MinPrincipalRange.Contain(Curvatures[k].MinPrincipal); + } + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupEdgeInserter.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupEdgeInserter.cpp new file mode 100644 index 000000000000..e82983d00829 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupEdgeInserter.cpp @@ -0,0 +1,1289 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Operations/GroupEdgeInserter.h" + +#include "CompGeom/PolygonTriangulation.h" +#include "ConstrainedDelaunay2.h" +#include "DynamicMeshChangeTracker.h" +#include "DynamicMeshEditor.h" +#include "FrameTypes.h" +#include "MeshIndexUtil.h" +#include "MeshRegionBoundaryLoops.h" +#include "Operations/GroupEdgeInserter.h" +#include "Operations/MeshPlaneCut.h" +#include "Operations/EmbedSurfacePath.h" +#include "Operations/SimpleHoleFiller.h" +#include "Selections/MeshConnectedComponents.h" +#include "Util/ProgressCancel.h" +#include "Util/IndexUtil.h" + +// Forward declarations of local helper functions +bool GetEdgeLoopOpposingEdgeAndCorner(const FGroupTopology& Topology, int32 GroupID, int32 GroupEdgeIDIn, + int32 CornerIDIn, int32& GroupEdgeIDOut, int32& CornerIDOut, int32& BoundaryIndexOut); +bool InsertEdgeLoopEdgesInDirection(const FGroupEdgeInserter::FEdgeLoopInsertionParams& Params, + const TArray& StartEndpoints, + int32 NextGroupID, int32 NextEdgeID, int32 NextCornerID, int32 NextBoundaryID, + TSet& AlteredGroups, int32& NumInserted, TSet* NewEids, FProgressCancel* Progress); +void InsertNewVertexEndpoints( + const FGroupEdgeInserter::FEdgeLoopInsertionParams& Params, + int32 GroupEdgeID, int32 StartCornerID, + TArray& EndPointsOut); +void ConvertProportionsToArcLengths( + const FGroupTopology& Topology, int32 GroupEdgeID, + const TArray& ProportionsIn, + TArray& ArcLengthsOut, TArray* PerVertexLengthsOut); +bool ConnectEndpoints( + const FGroupEdgeInserter::FEdgeLoopInsertionParams& Params, int32 GroupID, + const FGroupTopology::FGroupBoundary& GroupBoundary, + const TArray& StartPoints, + const TArray& EndPoints, + TSet* NewEids, int32& NumGroupsCreated, FProgressCancel* Progress); +bool ConnectMultipleUsingRetriangulation( + FDynamicMesh3& Mesh, const FGroupTopology& Topology, int32 GroupID, + const FGroupTopology::FGroupBoundary& GroupBoundary, + const TArray& StartPoints, + const TArray & EndPoints, + TSet* ConnectionEidsOut, int32& NumGroupsCreated, FProgressCancel* Progress); +bool DeleteGroupTrianglesAndGetLoop(FDynamicMesh3& Mesh, const FGroupTopology& Topology, int32 GroupID, + const FGroupTopology::FGroupBoundary& GroupBoundary, TArray& BoundaryVertices, FProgressCancel* Progress); +void AppendInclusiveRangeWrapAround(const TArray& InputArray, TArray& OutputArray, + int32 StartIndex, int32 InclusiveEndIndex); +bool RetriangulateLoop(FDynamicMesh3& Mesh, const TArray& LoopVertices, int32 NewGroupID); + +bool ConnectMultipleUsingPlaneCut(FDynamicMesh3& Mesh, + const FGroupTopology& Topology, int32 GroupID, + const FGroupTopology::FGroupBoundary& GroupBoundary, + const TArray& StartPoints, + const TArray & EndPoints, + double VertexTolerance, TSet* ConnectionEidsOut, + int32& NumGroupsCreated, FProgressCancel* Progress); +bool EmbedPlaneCutPath(FDynamicMesh3& Mesh, int32 GroupID, + const FGroupEdgeInserter::FGroupEdgeSplitPoint& StartPoint, + const FGroupEdgeInserter::FGroupEdgeSplitPoint& EndPoint, + double VertexTolerance, TSet& PathEidsOut, FProgressCancel* Progress); +bool CreateNewGroups(FDynamicMesh3& Mesh, TSet& PathEids, int32 OriginalGroupID, int32& NumGroupsCreated, FProgressCancel* Progress); +bool GetPlaneCutPath(const FDynamicMesh3& Mesh, int32 GroupID, + const FGroupEdgeInserter::FGroupEdgeSplitPoint& StartPoint, const FGroupEdgeInserter::FGroupEdgeSplitPoint& EndPoint, + TArray>& OutputPath, double VertexCutTolerance, FProgressCancel* Progress); + +bool InsertSingleWithRetriangulation(FDynamicMesh3& Mesh, FGroupTopology& Topology, + int32 GroupID, FGroupTopology::FGroupBoundary& Boundary, + const FGroupEdgeInserter::FGroupEdgeSplitPoint& StartPoint, + const FGroupEdgeInserter::FGroupEdgeSplitPoint& EndPoint, FProgressCancel* Progress); + +/** Inserts an edge loop into a mesh, where an edge loop is a sequence of (group) edges across quads. */ +bool FGroupEdgeInserter::InsertEdgeLoops(const FEdgeLoopInsertionParams& Params, TSet* NewEids, FProgressCancel* Progress) +{ + if (Progress && Progress->Cancelled()) + { + return false; + } + + // Validate the inputs + check(Params.Mesh); + check(Params.Topology); + check(Params.SortedInputLengths); + check(Params.GroupEdgeID != FDynamicMesh3::InvalidID); + check(Params.StartCornerID != FDynamicMesh3::InvalidID); + + const FGroupTopology::FGroupEdge& GroupEdge = Params.Topology->Edges[Params.GroupEdgeID]; + + // We check whether we have a valid path forward or backward first, because we don't want + // to do any edge splits if we have neither. + int32 ForwardGroupID = GroupEdge.Groups.A; + int32 ForwardEdgeID, ForwardCornerID, ForwardBoundaryIndex; + bool bHaveForwardEdge = GetEdgeLoopOpposingEdgeAndCorner(*Params.Topology, ForwardGroupID, + Params.GroupEdgeID, Params.StartCornerID, ForwardEdgeID, ForwardCornerID, ForwardBoundaryIndex); + + int32 BackwardGroupID = GroupEdge.Groups.B; + int32 BackwardEdgeID, BackwardCornerID, BackwardBoundaryIndex; + bool bHaveBackwardEdge = GetEdgeLoopOpposingEdgeAndCorner(*Params.Topology, BackwardGroupID, + Params.GroupEdgeID, Params.StartCornerID, BackwardEdgeID, BackwardCornerID, BackwardBoundaryIndex); + + if (!bHaveForwardEdge && !bHaveBackwardEdge) + { + // Neither of the neighbors is quad-like, so we can't insert an edge loop at this edge. + return false; + } + + // Depending on the topology, it is possible for our loop to attempt to cross itself from the side. We + // could support this case, because the "loop" will still eventually end. However, this isn't a particularly + // useful feature, so for now, we'll keep things a cleaner by ending the loop if we arrive at a group that + // we've already altered. This also allows us to avoid updating the topology as we go along. + TSet AlteredGroups; + + // We will need to keep the first endpoints around in case we use them to close the loop. + TArray StartEndpoints; + + // Although we have code that can insert new edges at edge endpoints, it is cleaner to do splits for all the loops + // down an edge ahead of time to make vertex endpoints, in part because if we don't, a split can change the eid + // of the next endpoint. + InsertNewVertexEndpoints(Params, Params.GroupEdgeID, Params.StartCornerID, StartEndpoints); + + if (StartEndpoints.Num() == 0 || (Progress && Progress->Cancelled())) + { + return false; + } + + // Insert edges in both directions. In case of a loop, the second call won't do anything because + // AlteredGroups will be updated. + bool bSuccess = false; + int32 TotalNumInserted = 0; + if (bHaveForwardEdge) + { + bSuccess = InsertEdgeLoopEdgesInDirection(Params, StartEndpoints, ForwardGroupID, ForwardEdgeID, + ForwardCornerID, ForwardBoundaryIndex, AlteredGroups, TotalNumInserted, NewEids, Progress); + } + if (bHaveBackwardEdge) + { + int32 NumInserted = 0; + bSuccess = InsertEdgeLoopEdgesInDirection(Params, StartEndpoints, BackwardGroupID, BackwardEdgeID, + BackwardCornerID, BackwardBoundaryIndex, AlteredGroups, NumInserted, NewEids, Progress) && bSuccess; + TotalNumInserted += NumInserted; + } + + if (TotalNumInserted == 0 || (Progress && Progress->Cancelled())) + { + return false; + } + + return Params.Topology->RebuildTopology() && bSuccess; +} + +/** + * Given a group edge and the (adjoining) quad-like group across which we want to continue an edge loop, + * finds the id of the opposite (i.e., destination) group edge. Additionally, gives the corner ID attached + * to the provided one. Safe to call with an FDynamicMesh3::InvalidID parameters (will return false) + * + * @returns true if a satisfactory edge was found. + */ +bool GetEdgeLoopOpposingEdgeAndCorner(const FGroupTopology& Topology, int32 GroupID, int32 GroupEdgeIDIn, + int32 CornerIDIn, int32& GroupEdgeIDOut, int32& CornerIDOut, int32& BoundaryIndexOut) +{ + GroupEdgeIDOut = FDynamicMesh3::InvalidID; + CornerIDOut = FDynamicMesh3::InvalidID; + BoundaryIndexOut = FDynamicMesh3::InvalidID; + if (GroupEdgeIDIn == FDynamicMesh3::InvalidID || GroupID == FDynamicMesh3::InvalidID) + { + return false; + } + + const FGroupTopology::FGroup* Group = Topology.FindGroupByID(GroupID); + check(Group); + + for (int32 i = 0; i < Group->Boundaries.Num(); ++i) + { + const FGroupTopology::FGroupBoundary& Boundary = Group->Boundaries[i]; + int32 GroupEdgeIndex = Boundary.GroupEdges.IndexOfByKey(GroupEdgeIDIn); + if (GroupEdgeIndex != INDEX_NONE) + { + if (Boundary.GroupEdges.Num() != 4) + { + return false; + } + + GroupEdgeIDOut = Boundary.GroupEdges[(GroupEdgeIndex + 2) % 4]; + BoundaryIndexOut = i; + + // Get the corner attached to the one we were given + if (CornerIDIn != FDynamicMesh3::InvalidID) + { + FGroupTopology::FGroupEdge SideEdge1 = Topology.Edges[Boundary.GroupEdges[(GroupEdgeIndex + 1) % 4]]; + FGroupTopology::FGroupEdge SideEdge2 = Topology.Edges[Boundary.GroupEdges[(GroupEdgeIndex + 3) % 4]]; + if (SideEdge1.EndpointCorners.A == CornerIDIn) + { + CornerIDOut = SideEdge1.EndpointCorners.B; + } + else if (SideEdge1.EndpointCorners.B == CornerIDIn) + { + CornerIDOut = SideEdge1.EndpointCorners.A; + } + else if (SideEdge2.EndpointCorners.A == CornerIDIn) + { + CornerIDOut = SideEdge2.EndpointCorners.B; + } + else if (SideEdge2.EndpointCorners.B == CornerIDIn) + { + CornerIDOut = SideEdge2.EndpointCorners.A; + } + } + + return true; + } + } + return false; +} + +/** + * Helper function, continues the loop in one direction from a start edge. + * @returns false if there is an error. + */ +bool InsertEdgeLoopEdgesInDirection(const FGroupEdgeInserter::FEdgeLoopInsertionParams& Params, + const TArray& StartEndpoints, + int32 NextGroupID, int32 NextEdgeID, int32 NextCornerID, int32 NextBoundaryIndex, + TSet& AlteredGroups, int32& NumInserted, TSet* NewEids, FProgressCancel* Progress) +{ + NumInserted = 0; + if (AlteredGroups.Contains(NextGroupID) || StartEndpoints.Num() == 0) + { + return true; + } + + // We keep endpoints in two arrays and swap the current one as we move along + TArray EndpointStorage1 = StartEndpoints; + TArray EndpointStorage2; + TArray* CurrentEndpoints = &EndpointStorage1; + TArray* NextEndpoints = &EndpointStorage2; + + bool bHaveNextGroup = true; + bool bSuccess = true; + while (bHaveNextGroup && !AlteredGroups.Contains(NextGroupID)) + { + if (Progress && Progress->Cancelled()) + { + return false; + } + + const FGroupTopology::FGroup* CurrentGroup = Params.Topology->FindGroupByID(NextGroupID); + check(CurrentGroup); + const FGroupTopology::FGroupBoundary& Boundary = CurrentGroup->Boundaries[NextBoundaryIndex]; + + // See if we looped around to the start + if (NextEdgeID == Params.GroupEdgeID) + { + int32 NumGroupsCreated; + bSuccess = ConnectEndpoints(Params, NextGroupID, Boundary, *CurrentEndpoints, StartEndpoints, + NewEids, NumGroupsCreated, Progress); + AlteredGroups.Add(NextGroupID); + NumInserted += (NumGroupsCreated > 1 ? 1 : 0); + break; + } + + // Otherwise, create next endpoints + InsertNewVertexEndpoints(Params, NextEdgeID, NextCornerID, *NextEndpoints); + + if (NextEndpoints->Num() == 0) + { + // Next edge wasn't long enough for the input lengths we wanted. Stop here. + // TODO: Actually we now clamp to max length. Should we? + return true; + } + + // Connect up the endpoints + int32 NumGroupsCreated; + bSuccess = ConnectEndpoints(Params, NextGroupID, Boundary, *CurrentEndpoints, *NextEndpoints, + NewEids, NumGroupsCreated, Progress); + AlteredGroups.Add(NextGroupID); + NumInserted += (NumGroupsCreated > 1 ? 1 : 0); + + if (!bSuccess || (Progress && Progress->Cancelled())) + { + return false; + } + + // Get the next group edge target + if (Params.Topology->IsBoundaryEdge(NextEdgeID)) + { + break; + } + NextGroupID = Params.Topology->Edges[NextEdgeID].OtherGroupID(NextGroupID); + bHaveNextGroup = GetEdgeLoopOpposingEdgeAndCorner(*Params.Topology, NextGroupID, NextEdgeID, NextCornerID, + NextEdgeID, NextCornerID, NextBoundaryIndex); // outputs + + Swap(CurrentEndpoints, NextEndpoints); + } + return bSuccess; +} + +/** + * Inserts vertices along an existing group edge that will be used as endpoints + * for new group edges. + + * Note that due to tolerance, multiple inputs can map to the same vertex. We + * want to keep this functionality because in the case of proportion inputs, the + * snapping will partly depend on the narrowness of an edge, and we still want to + * allow connection to adjacent edges. + * + * Clears EndPointsOut before use. + */ +void InsertNewVertexEndpoints( + const FGroupEdgeInserter::FEdgeLoopInsertionParams& Params, + int32 GroupEdgeID, int32 StartCornerID, + TArray& EndPointsOut) +{ + EndPointsOut.Reset(); + if (Params.SortedInputLengths->Num() == 0) + { + return; + } + + const FGroupTopology::FGroupEdge& GroupEdge = Params.Topology->Edges[GroupEdgeID]; + + // Prep the list of vertex ids and the corresponding cumulative lengths. It is easier + // to make our own copies because we may need to iterate backwards relative to the + // order in the topology. + bool bGoBackward = (GroupEdge.Span.Vertices.Last() == Params.Topology->GetCornerVertexID(StartCornerID)); + TArray SpanVids; + if (!bGoBackward) + { + SpanVids = GroupEdge.Span.Vertices; + } + else + { + for (int i = GroupEdge.Span.Vertices.Num() - 1; i >= 0; --i) + { + SpanVids.Add(GroupEdge.Span.Vertices[i]); + } + } + + TArray PerVertexLengths; + TArray ArcLengths; + if (Params.bInputsAreProportions) + { + ConvertProportionsToArcLengths(*Params.Topology, GroupEdgeID, + *Params.SortedInputLengths, ArcLengths, &PerVertexLengths); + } + else + { + ArcLengths = *Params.SortedInputLengths; + Params.Topology->GetEdgeArcLength(GroupEdgeID, &PerVertexLengths); // Get per vertex lengths + } + + double TotalLength = PerVertexLengths.Last(); + if (bGoBackward) + { + // Reverse order and update lengths to be TotalLength-length. Could do in one pass but then + // don't forget to modify the middle. + for (int i = 0; i < PerVertexLengths.Num()/2; ++i) + { + Swap(PerVertexLengths[i], PerVertexLengths[PerVertexLengths.Num() - 1 - i]); + } + for (double& Length : PerVertexLengths) + { + Length = TotalLength - Length; + } + } + + // We're going to walk forward selecting existing vertices or adding new ones as we go along. + // CurrentVid and CurrentArcLength may take on values that are not in SpanVids or PerVertexLengths + // as we insert new vertices. NextIndex, however is always an index into those two structures + // of the next vertex in front of the current one. + int32 CurrentVid = SpanVids[0]; + double CurrentArcLength = 0; + int32 NextIndex = 1; + + for (double TargetLength : ArcLengths) + { + // If the next target is beyond the last vertex, clamp it to the last vertex + if (TargetLength > TotalLength + Params.VertexTolerance) + { + TargetLength = TotalLength; + } + + // Advance until the next vertex would overshoot the target length. + while (NextIndex < PerVertexLengths.Num() && PerVertexLengths[NextIndex] <= TargetLength + Params.VertexTolerance) + { + CurrentVid = SpanVids[NextIndex]; + CurrentArcLength = PerVertexLengths[NextIndex]; + ++NextIndex; + } + + // The point is now either at the current vertex, or on the edge going forward (if there is one) + FGroupEdgeInserter::FGroupEdgeSplitPoint SplitPoint; + + // See if the target is at the current vertex + if (abs(TargetLength - CurrentArcLength) <= Params.VertexTolerance) + { + SplitPoint.ElementID = CurrentVid; + SplitPoint.bIsVertex = true; + + // Get the tangent vector. We haven't been keeping track of any previous verts we inserted, + // but inserted verts must be on an edge and must have the forward edge as their tangent. + bool bVertexIsOriginal = (CurrentVid == SpanVids[NextIndex - 1]); + if (!bVertexIsOriginal) + { + SplitPoint.Tangent = (Params.Mesh->GetVertex(SpanVids[NextIndex]) - Params.Mesh->GetVertex(CurrentVid)).Normalized(); + } + else + { + FVector3d VertexPosition = Params.Mesh->GetVertex(CurrentVid); + SplitPoint.Tangent = FVector3d::Zero(); + if (NextIndex > 1) + { + SplitPoint.Tangent += (VertexPosition - Params.Mesh->GetVertex(SpanVids[NextIndex - 2])).Normalized(); + } + if (NextIndex < SpanVids.Num()) + { + SplitPoint.Tangent += (Params.Mesh->GetVertex(SpanVids[NextIndex]) - VertexPosition).Normalized(); + } + SplitPoint.Tangent.Normalize(); + } + } + else + { + // Target must be on the edge that goes to the next vertex + int32 CurrentEid = Params.Mesh->FindEdge(CurrentVid, SpanVids[NextIndex]); + + double SplitT = (TargetLength - CurrentArcLength) / (PerVertexLengths[NextIndex] - CurrentArcLength); + + // See if the edge is stored backwards relative to the direction we're traveling + if (Params.Mesh->GetEdge(CurrentEid).Vert.B != SpanVids[NextIndex]) + { + SplitT = 1 - SplitT; + } + + // Perform the split + FDynamicMesh3::FEdgeSplitInfo EdgeSplitInfo; + Params.Mesh->SplitEdge(CurrentEid, EdgeSplitInfo, SplitT); + + // Update our position given that we're at a new vertex. + CurrentVid = EdgeSplitInfo.NewVertex; + CurrentArcLength = TargetLength; + + // Assemble the actual output + SplitPoint.ElementID = CurrentVid; + SplitPoint.bIsVertex = true; // we made the vertex that is this target + SplitPoint.Tangent = (Params.Mesh->GetVertex(SpanVids[NextIndex]) - Params.Mesh->GetVertex(CurrentVid)).Normalized(); + } + + EndPointsOut.Add(SplitPoint); + }//end going through targets +} + +void ConvertProportionsToArcLengths( + const FGroupTopology& Topology, int32 GroupEdgeID, + const TArray& ProportionsIn, + TArray& ArcLengthsOut, TArray* PerVertexLengthsOut) +{ + ArcLengthsOut.Reset(ProportionsIn.Num()); + double TotalLength = Topology.GetEdgeArcLength(GroupEdgeID, PerVertexLengthsOut); + for (double Proportion : ProportionsIn) + { + ArcLengthsOut.Add(Proportion * TotalLength); + } +} + +/** + * Helper function, assumes that StartPoints and EndPoints are all vertices and connects them. + * See ConnectMultipleUsingRetriangulation for details. + * + * @param GroupID Group across which to make the connections. + * @param GroupBoundary Boundary of the group across which the connections are being made. + */ +bool ConnectEndpoints( + const FGroupEdgeInserter::FEdgeLoopInsertionParams& Params, int32 GroupID, + const FGroupTopology::FGroupBoundary& GroupBoundary, + const TArray& StartPoints, + const TArray& EndPoints, + TSet* NewEids, int32& NumGroupsCreated, FProgressCancel* Progress) +{ + if (Params.Mode == FGroupEdgeInserter::EInsertionMode::Retriangulate) + { + return ConnectMultipleUsingRetriangulation(*Params.Mesh, *Params.Topology, GroupID, + GroupBoundary, StartPoints, EndPoints, NewEids, NumGroupsCreated, Progress); + } + else if (Params.Mode == FGroupEdgeInserter::EInsertionMode::PlaneCut) + { + return ConnectMultipleUsingPlaneCut(*Params.Mesh, *Params.Topology, GroupID, + GroupBoundary, StartPoints, EndPoints, Params.VertexTolerance, NewEids, + NumGroupsCreated, Progress); + } + else + { + checkf(false, TEXT("GroupEdgeInserter:ConnectEndpoints: Unimplemented insertion method.")); + } + return false; +} + +/** + * Connects multiple endpoints across the same group, making the assumption that StartPoints and + * EndPoints are composed only of vertex endpoints, are 1:1 in their arrays and are ordered + * sequentially away from the first startpoint and first endpoint, which have no endpoints between + * them. So the arrangement is something like this (may be mirrored): + *---* + | | + s0-e0 + | | + s1-e1 + | | + *---* + * The assumption is used to order the generated loops. + * The function exists because it is more efficient than generating each edge one at a + * time in a multi-loop case. + * @returns false if there's an error. + */ +bool ConnectMultipleUsingRetriangulation( + FDynamicMesh3& Mesh, + const FGroupTopology& Topology, + int32 GroupID, + const FGroupTopology::FGroupBoundary& GroupBoundary, + const TArray& StartPoints, + const TArray & EndPoints, + TSet* ConnectionEidsOut, int32& NumGroupsCreated, FProgressCancel* Progress) +{ + NumGroupsCreated = 0; + if (Progress && Progress->Cancelled()) + { + return false; + } + + int32 NumNewEdges = FMath::Min(StartPoints.Num(), EndPoints.Num()); + if (NumNewEdges == 0) + { + return true; // Nothing to do. + } + + TArray BoundaryVertices; + bool bSuccess = DeleteGroupTrianglesAndGetLoop(Mesh, Topology, GroupID, GroupBoundary, BoundaryVertices, Progress); + + if (!bSuccess || (Progress && Progress->Cancelled())) + { + return false; + } + + // Convert endpoint arrays to arrays of indices into the boundary vertex array. + TArray StartIndices; + TArray EndIndices; + for (int32 i = 0; i < NumNewEdges; ++i) + { + check(StartPoints[i].bIsVertex && EndPoints[i].bIsVertex); + + int32 StartIndex = BoundaryVertices.Find(StartPoints[i].ElementID); + int32 EndIndex = BoundaryVertices.Find(EndPoints[i].ElementID); + check(StartIndex != INDEX_NONE && EndIndex != INDEX_NONE); + + StartIndices.Add(StartIndex); + EndIndices.Add(EndIndex); + } + + // We don't know which way the vertices are ordered relative to the counterclockwise ordering + // of the original group. If we were to go ccw from the first start vertex, we would expect + // to reach the second start vertex before the first end vertex. If we reach the end vertex + // first, then the diagram in the function header is flipped. + bool bReverseSubloopDirection = NumNewEdges > 1 && + (StartIndices[1] - StartIndices[0] + BoundaryVertices.Num()) % BoundaryVertices.Num() // ccw distance from start + > (EndIndices[0] - StartIndices[0] + BoundaryVertices.Num()) % BoundaryVertices.Num();// ccw distance from start + + if (Progress && Progress->Cancelled()) + { + return false; + } + + // Do the first loop + TArray LoopVids; + if (!bReverseSubloopDirection) + { + AppendInclusiveRangeWrapAround(BoundaryVertices, LoopVids, EndIndices[0], StartIndices[0]); + } + else + { + AppendInclusiveRangeWrapAround(BoundaryVertices, LoopVids, StartIndices[0], EndIndices[0]); + } + if (LoopVids.Num() > 2) + { + bSuccess = RetriangulateLoop(Mesh, LoopVids, GroupID); + NumGroupsCreated += (bSuccess ? 1 : 0); + } + + // Do the middle loops + for (int32 i = 1; i < NumNewEdges; ++i) + { + if (!bSuccess || (Progress && Progress->Cancelled())) + { + return false; + } + + if (StartIndices[i - 1] == StartIndices[i] && EndIndices[i - 1] == EndIndices[i]) + { + continue; + } + + LoopVids.Reset(); + if (!bReverseSubloopDirection) + { + AppendInclusiveRangeWrapAround(BoundaryVertices, LoopVids, StartIndices[i - 1], StartIndices[i]); // previous to current + AppendInclusiveRangeWrapAround(BoundaryVertices, LoopVids, EndIndices[i], EndIndices[i - 1]); // current to previous + } + else + { + AppendInclusiveRangeWrapAround(BoundaryVertices, LoopVids, StartIndices[i], StartIndices[i - 1]); // current to previous + AppendInclusiveRangeWrapAround(BoundaryVertices, LoopVids, EndIndices[i - 1], EndIndices[i]); // previous to current + } + + bSuccess = RetriangulateLoop(Mesh, LoopVids, Mesh.AllocateTriangleGroup()); + NumGroupsCreated += (bSuccess ? 1 : 0); + } + + if (!bSuccess || (Progress && Progress->Cancelled())) + { + return false; + } + + // Do the last loop + LoopVids.Reset(); + if (!bReverseSubloopDirection) + { + AppendInclusiveRangeWrapAround(BoundaryVertices, LoopVids, StartIndices.Last(), EndIndices.Last()); + } + else + { + AppendInclusiveRangeWrapAround(BoundaryVertices, LoopVids, EndIndices.Last(), StartIndices.Last()); + } + if (LoopVids.Num() > 2) + { + bSuccess = RetriangulateLoop(Mesh, LoopVids, Mesh.AllocateTriangleGroup()); + NumGroupsCreated += (bSuccess ? 1 : 0); + } + + if (ConnectionEidsOut) + { + for (int32 i = 0; i < NumNewEdges; ++i) + { + ConnectionEidsOut->Add(Mesh.FindEdge(StartPoints[i].ElementID, EndPoints[i].ElementID)); + } + } + + return bSuccess; +} + +/** + * Helper function, deletes triangles in a group connected component and outputs the corresponding boundary. + * Does not delete the vertices. + */ +bool DeleteGroupTrianglesAndGetLoop(FDynamicMesh3& Mesh, const FGroupTopology& Topology, int32 GroupID, + const FGroupTopology::FGroupBoundary& GroupBoundary, TArray& BoundaryVertices, FProgressCancel* Progress) +{ + // Since groups may not be contiguous, we have to do a connected component search + // rather than deleting all triangles marked with that group, so get the seeds for the search. + int32 FirstEid = Topology.Edges[GroupBoundary.GroupEdges[0]].Span.Edges[0]; + FIndex2i PotentialSeedTriangles = Mesh.GetEdge(FirstEid).Tri; + TArray SeedTriangles; + if (Mesh.GetTriangleGroup(PotentialSeedTriangles.A) == GroupID) + { + SeedTriangles.Add(PotentialSeedTriangles.A); + } + else + { + check(PotentialSeedTriangles.B != FDynamicMesh3::InvalidID && Mesh.GetTriangleGroup(PotentialSeedTriangles.B) == GroupID); + SeedTriangles.Add(PotentialSeedTriangles.B); + } + + FMeshConnectedComponents ConnectedComponents(&Mesh); + ConnectedComponents.FindTrianglesConnectedToSeeds(SeedTriangles, [&](int32 t0, int32 t1) { + return Mesh.GetTriangleGroup(t0) == Mesh.GetTriangleGroup(t1); + }); + + if (Progress && Progress->Cancelled()) + { + return false; + } + + FMeshConnectedComponents::FComponent& Component = ConnectedComponents.GetComponent(0); + + // Get the boundary loop + FMeshRegionBoundaryLoops RegionLoops(&Mesh, Component.Indices, true); + if (RegionLoops.bFailed || RegionLoops.Loops.Num() != 1) + { + return false; + } + RegionLoops.Loops[0].Reverse(); + BoundaryVertices = RegionLoops.Loops[0].Vertices; + + if (Progress && Progress->Cancelled()) + { + return false; + } + + // Do the actual deletion. + // TODO: We don't want to remove orphaned vertices on the boundaries, but we do in + // the center. We should implement that. + FDynamicMeshEditor Editor(&Mesh); + Editor.RemoveTriangles(Component.Indices, false); + + return true; +} + +/** + * Appends entries from an input array from a start index to end index (inclusive), wrapping around + * at the end. + */ +void AppendInclusiveRangeWrapAround(const TArray& InputArray, TArray& OutputArray, + int32 StartIndex, int32 InclusiveEndIndex) +{ + check(InclusiveEndIndex >= 0 && InclusiveEndIndex < InputArray.Num() + && StartIndex >= 0 && StartIndex < InputArray.Num()); + + int32 CurrentIndex = StartIndex; + while (CurrentIndex != InclusiveEndIndex) + { + OutputArray.Add(InputArray[CurrentIndex]); + CurrentIndex = (CurrentIndex + 1) % InputArray.Num(); + } + OutputArray.Add(InputArray[InclusiveEndIndex]); +} + +bool RetriangulateLoop(FDynamicMesh3& Mesh, const TArray& LoopVertices, int32 NewGroupID) +{ + TArray LoopEdges; + FEdgeLoop::VertexLoopToEdgeLoop(&Mesh, LoopVertices, LoopEdges); + FEdgeLoop Loop(&Mesh, LoopVertices, LoopEdges); + FSimpleHoleFiller HoleFiller(&Mesh, Loop, FSimpleHoleFiller::EFillType::PolygonEarClipping); + return HoleFiller.Fill(NewGroupID); +} + +/** + * See ConnectMultipleUsingRetriangulation for details. + */ +bool ConnectMultipleUsingPlaneCut(FDynamicMesh3& Mesh, + const FGroupTopology& Topology, int32 GroupID, + const FGroupTopology::FGroupBoundary& GroupBoundary, + const TArray& StartPoints, + const TArray & EndPoints, + double VertexTolerance, TSet* ConnectionEidsOut, + int32& NumGroupsCreated, FProgressCancel* Progress) +{ + NumGroupsCreated = 0; + if (Progress && Progress->Cancelled()) + { + return false; + } + + int32 NumEdgesToInsert = FMath::Min(StartPoints.Num(), EndPoints.Num()); + TSet PathsEids; + for (int32 i = 0; i < NumEdgesToInsert; ++i) + { + bool bSuccess = EmbedPlaneCutPath(Mesh, GroupID, StartPoints[i], EndPoints[i], + VertexTolerance, PathsEids, Progress); + + if (!bSuccess || (Progress && Progress->Cancelled())) + { + return false; + } + } + + if (ConnectionEidsOut) + { + *ConnectionEidsOut = ConnectionEidsOut->Union(PathsEids); + } + + bool bSuccess = CreateNewGroups(Mesh, PathsEids, GroupID, NumGroupsCreated, Progress); + return bSuccess; +} + +/** + * Places a plane path connecting the endpoints into the mesh, but does not give the triangles new groups yet. + * However, outputs the path edge ID's so that can be done later. + */ +bool EmbedPlaneCutPath(FDynamicMesh3& Mesh, int32 GroupID, + const FGroupEdgeInserter::FGroupEdgeSplitPoint& StartPoint, + const FGroupEdgeInserter::FGroupEdgeSplitPoint& EndPoint, + double VertexTolerance, TSet& PathEidsOut, FProgressCancel* Progress) +{ + if (Progress && Progress->Cancelled()) + { + return false; + } + + // Find the path we're going to take. + TArray> CutPath; + bool bSuccess = GetPlaneCutPath(Mesh, GroupID, StartPoint, EndPoint, + CutPath, VertexTolerance, Progress); + if (!bSuccess || (Progress && Progress->Cancelled())) + { + return false; + } + check(CutPath.Num() >= 2); + + // Embed the path. + FMeshSurfacePath PathEmbedder(&Mesh); + PathEmbedder.Path = CutPath; + TArray PathVertices; + bSuccess = PathEmbedder.EmbedSimplePath(false, PathVertices, false); + if (!bSuccess || (Progress && Progress->Cancelled())) + { + return false; + } + check(PathVertices.Num() >= 2); + + for (int32 i = 1; i < PathVertices.Num(); ++i) + { + PathEidsOut.Add(Mesh.FindEdge(PathVertices[i - 1], PathVertices[i])); + } + + return true; +} + +/** + * Uses the given path edge ID's to split a group into new groups. + */ +bool CreateNewGroups(FDynamicMesh3& Mesh, TSet& PathEids, int32 OriginalGroup, int32& NumGroupsCreated, FProgressCancel* Progress) +{ + NumGroupsCreated = 0; + + if (Progress && Progress->Cancelled()) + { + return false; + } + + // Create the new groups. We do so + TSet SeedTriangleSet; + for (int32 Eid : PathEids) + { + FIndex2i Tris = Mesh.GetEdgeT(Eid); + if (Mesh.GetTriangleGroup(Tris.A) == OriginalGroup) + { + SeedTriangleSet.Add(Tris.A); + } + if (Tris.B != FDynamicMesh3::InvalidID && Mesh.GetTriangleGroup(Tris.B) == OriginalGroup) + { + SeedTriangleSet.Add(Tris.B); + } + } + + FMeshConnectedComponents ConnectedComponents(&Mesh); + ConnectedComponents.FindTrianglesConnectedToSeeds(SeedTriangleSet.Array(), [&](int32 t0, int32 t1) { + + // Triangles are connected only if they have the same group and are not across one of the + // newly inserted group edges. + int32 Group0 = Mesh.GetTriangleGroup(t0); + int32 Group1 = Mesh.GetTriangleGroup(t1); + if (Group0 == Group1) + { + int32 SharedEdge = Mesh.FindEdgeFromTriPair(t0, t1); + return !PathEids.Contains(SharedEdge); + } + return false; + }); + + if (Progress && Progress->Cancelled()) + { + return false; + } + + // Assign a new group id for each component. The first component keeps the old group ID. + for (int32 i = 1; i < ConnectedComponents.Num(); ++i) + { + FMeshConnectedComponents::FComponent& Component = ConnectedComponents.GetComponent(i); + + int32 NewGroupID = Mesh.AllocateTriangleGroup(); + for (int Tid : Component.Indices) + { + Mesh.SetTriangleGroup(Tid, NewGroupID); + } + } + + NumGroupsCreated = ConnectedComponents.Num(); + + return true; +} + +/** Inserts a group edge into a given group. */ +bool FGroupEdgeInserter::InsertGroupEdge(FGroupEdgeInsertionParams& Params, TSet* NewEids, FProgressCancel* Progress) +{ + if (Progress && Progress->Cancelled()) + { + return false; + } + + // Validate the inputs + check(Params.Mesh); + check(Params.Topology); + check(Params.GroupBoundary); + check(Params.GroupID != FDynamicMesh3::InvalidID); + check(Params.StartPoint.ElementID != FDynamicMesh3::InvalidID); + check(Params.EndPoint.ElementID != FDynamicMesh3::InvalidID); + + if (Params.Mode == EInsertionMode::PlaneCut) + { + bool bSuccess = EmbedPlaneCutPath(*Params.Mesh, Params.GroupID, + Params.StartPoint, Params.EndPoint, Params.VertexTolerance, *NewEids, Progress); + if (bSuccess || (Progress && Progress->Cancelled())) + { + return false; + } + + int32 NumGroupsCreated; + bSuccess = CreateNewGroups(*Params.Mesh, *NewEids, Params.GroupID, NumGroupsCreated, Progress); + if (!bSuccess) + { + return false; + } + } + else if (Params.Mode == EInsertionMode::Retriangulate) + { + bool bSuccess = InsertSingleWithRetriangulation(*Params.Mesh, *Params.Topology, + Params.GroupID, *Params.GroupBoundary, Params.StartPoint, Params.EndPoint, + Progress); + if (!bSuccess) + { + return false; + } + } + else + { + checkf(false, TEXT("GroupEdgeInserter:InsertGroupEdge: Unimplemented insertion method.")); + } + + if (Progress && Progress->Cancelled()) + { + return false; + } + + Params.Topology->RebuildTopology(); + + return true; +} + +/** Helper function. Not used when inserting multiple edges at once into a group to avoid continuously retriangulating and deleting. */ +bool InsertSingleWithRetriangulation(FDynamicMesh3& Mesh, FGroupTopology& Topology, + int32 GroupID, FGroupTopology::FGroupBoundary& Boundary, + const FGroupEdgeInserter::FGroupEdgeSplitPoint& StartPoint, + const FGroupEdgeInserter::FGroupEdgeSplitPoint& EndPoint, FProgressCancel* Progress) +{ + if (Progress && Progress->Cancelled()) + { + return false; + } + + int32 IndexA; + if (!StartPoint.bIsVertex) + { + FDynamicMesh3::FEdgeSplitInfo SplitInfo; + Mesh.SplitEdge(StartPoint.ElementID, SplitInfo, StartPoint.EdgeTValue); + } + + int32 IndexB; + if (!EndPoint.bIsVertex) + { + FDynamicMesh3::FEdgeSplitInfo SplitInfo; + Mesh.SplitEdge(EndPoint.ElementID, SplitInfo, EndPoint.EdgeTValue); + } + + TArray BoundaryVertices; + bool bSuccess = DeleteGroupTrianglesAndGetLoop(Mesh, Topology, GroupID, Boundary, BoundaryVertices, Progress); + if (!bSuccess || (Progress && Progress->Cancelled())) + { + return false; + } + + IndexA = BoundaryVertices.IndexOfByKey(StartPoint.ElementID); + IndexB = BoundaryVertices.IndexOfByKey(EndPoint.ElementID); + + TArray LoopVids; + AppendInclusiveRangeWrapAround(BoundaryVertices, LoopVids, IndexA, IndexB); + bSuccess = RetriangulateLoop(Mesh, LoopVids, GroupID); + + if (!bSuccess || (Progress && Progress->Cancelled())) + { + return false; + } + + LoopVids.Reset(); + AppendInclusiveRangeWrapAround(BoundaryVertices, LoopVids, IndexB, IndexA); + bSuccess = RetriangulateLoop(Mesh, LoopVids, GroupID); + + return bSuccess; +} + + +/** + * Creates a path of FMeshSurfacePoint instances across a group that can be embedded into the mesh, + * based on a plane cut from start to end. Does not actually embed that path yet. + * + * Does not attempt to deal with topology that creates local ambiguities in the path, for instance, + * if the surface twists to become coplanar with the cut plane somewhere in the middle, or folds + * over at one of the path vertices. + * Assumes that the start and end points are on the boundary of the group. + * + * Instead of having this function, we should modify EmbedSurfacePath.cpp::WalkMeshPlanar to allow the start and + * end points to be edges/vertices and to have a filter function that we can use to filter out triangles that are + * not in the group we want. + * + * @returns false if path could not be found. + */ +bool GetPlaneCutPath(const FDynamicMesh3& Mesh, int32 GroupID, + const FGroupEdgeInserter::FGroupEdgeSplitPoint& StartPoint, const FGroupEdgeInserter::FGroupEdgeSplitPoint& EndPoint, + TArray>& OutputPath, double VertexCutTolerance, FProgressCancel* Progress) +{ + if (Progress && Progress->Cancelled()) + { + return false; + } + + // Start by determining the plane we will use. + FVector3d StartPosition = StartPoint.bIsVertex ? Mesh.GetVertex(StartPoint.ElementID) + : Mesh.GetEdgePoint(StartPoint.ElementID, StartPoint.EdgeTValue); + FVector3d EndPosition = EndPoint.bIsVertex ? Mesh.GetVertex(EndPoint.ElementID) + : Mesh.GetEdgePoint(EndPoint.ElementID, EndPoint.EdgeTValue); + + FVector3d InPlaneVector = (EndPosition - StartPosition).Normalized(); + + // Get components of the two tangents that are orthogonal to the vector between the points. + FVector3d NormalA = (StartPoint.Tangent - StartPoint.Tangent.Dot(InPlaneVector) * InPlaneVector).Normalized(); + FVector3d NormalB = (StartPoint.Tangent - StartPoint.Tangent.Dot(InPlaneVector) * InPlaneVector).Normalized(); + + // Make the vectors be in the same half space so that their average represents the closer average of the + // corresponding lines. + if (NormalA.Dot(NormalB) < 0) + { + NormalB = -NormalB; + } + + // Do the averaging + FVector CutPlaneNormal = (FVector)(NormalA + NormalB).Normalized(); + if (CutPlaneNormal.IsZero()) + { + // This likely shouldn't happen, since it means that the two endpoint tangents are colinear + // with the vector between them. Let's say that the normal doesn't matter then as long as it's + // orthogonal. + CutPlaneNormal = FVector(InPlaneVector.Y, -InPlaneVector.X, InPlaneVector.Z); + } + + FVector CutPlaneOrigin = (FVector)StartPosition; + + // Set up a bunch of variables we'll need as we walk from start to end + bool bCurrentPointIsVertex = StartPoint.bIsVertex; + int32 CurrentElementID = StartPoint.ElementID; + + // These help us avoid backtracking. + int32 PreviousTid = FDynamicMesh3::InvalidID; // if we walked across a triangle + int32 PreviousVid = FDynamicMesh3::InvalidID; // if we walked from a vertex + + // These avoid recomuting some distances + float CurrentEdgeVertPlaneDistances[2]; + + // Prep the first point + if (StartPoint.bIsVertex) + { + OutputPath.Emplace(FMeshSurfacePoint(StartPoint.ElementID), FDynamicMesh3::InvalidID); + } + else + { + OutputPath.Emplace(FMeshSurfacePoint(StartPoint.ElementID, StartPoint.EdgeTValue), FDynamicMesh3::InvalidID); + FIndex2i EdgeVids = Mesh.GetEdgeV(CurrentElementID); + CurrentEdgeVertPlaneDistances[0] = FVector::PointPlaneDist((FVector)Mesh.GetVertex(EdgeVids.A), CutPlaneOrigin, CutPlaneNormal); + CurrentEdgeVertPlaneDistances[1] = FVector::PointPlaneDist((FVector)Mesh.GetVertex(EdgeVids.B), CutPlaneOrigin, CutPlaneNormal); + } + + int32 PointCount = 1; // Used as a sanity check + + // Tracks which triangle (if not an edge) we traversed to get to the next point, so we can avoid + // backtracking in the next step. + int32 TraversedTid = FDynamicMesh3::InvalidID; + + // Do the walk. The exit condition is that the last point of our output path has the same ID and type + // as our endpoint. + while (!(CurrentElementID == EndPoint.ElementID && bCurrentPointIsVertex == EndPoint.bIsVertex)) + { + if (Progress && Progress->Cancelled()) + { + return false; + } + + check(PointCount < Mesh.EdgeCount()); // sanity check to avoid infinite loop + + if (bCurrentPointIsVertex) + { + FMeshSurfacePoint NextPoint(FDynamicMesh3::InvalidID); + + // Look through the surrounding triangles that have the group we want and find one that intersects the plane + for (int32 Tid : Mesh.VtxTrianglesItr(CurrentElementID)) + { + if (Tid == PreviousTid || Mesh.GetTriangleGroup(Tid) != GroupID) + { + continue; + } + + const FIndex3i& TriangleVids = Mesh.GetTriangle(Tid); + int32 VertA = (TriangleVids.A == CurrentElementID) ? TriangleVids.C : TriangleVids.A; + int32 VertB = (TriangleVids.B == CurrentElementID) ? TriangleVids.C : TriangleVids.B; + if (VertA == PreviousVid || VertB == PreviousVid) + { + // Our path can't go through this triangle, since we already went down one of its sides and + // since we bail when we see triangles that are coplanar with the cut plane. + continue; + } + + // Check to see if one of the triangle vertices is the end + if (EndPoint.bIsVertex && (EndPoint.ElementID == VertA || EndPoint.ElementID == VertB)) + { + // TODO: This ought to be cleaned up because there is inconsistency in how we deal ambiguity + // at the end of our path (we may accept or not accept ambiguities). + NextPoint = FMeshSurfacePoint(EndPoint.ElementID); + break; + } + + float PlaneDistanceA = FVector::PointPlaneDist((FVector)Mesh.GetVertex(VertA), CutPlaneOrigin, CutPlaneNormal); + float PlaneDistanceB = FVector::PointPlaneDist((FVector)Mesh.GetVertex(VertB), CutPlaneOrigin, CutPlaneNormal); + bool bVertAIsOnPlane = abs(PlaneDistanceA) <= VertexCutTolerance; + bool bVertBIsOnPlane = abs(PlaneDistanceB) <= VertexCutTolerance; + + if (bVertAIsOnPlane && bVertBIsOnPlane) + { + return false; // Triangle coplanar with the cut plane. We're not going to try to handle that. + } + + if (bVertAIsOnPlane || bVertBIsOnPlane) + { + int32 CandidateVert = bVertAIsOnPlane ? VertA : VertB; + if (NextPoint.ElementID != FDynamicMesh3::InvalidID) + { + // We already found a destination point. See if we're looking at the same one (from an adjacent triangle). + if (NextPoint.PointType == ESurfacePointType::Vertex && NextPoint.ElementID == CandidateVert) + { + continue; // Ok, same point + } + else + { + return false; // Another point, so ambiguous + } + } + else + { + // Save point + NextPoint = FMeshSurfacePoint(CandidateVert); + } + }//end if one of the points is on the plane + else if (PlaneDistanceA * PlaneDistanceB < 0) + { + // The triangle's opposite edge crosses the plane. + int32 Eid = Mesh.FindEdge(VertA, VertB); + + if (!EndPoint.bIsVertex && EndPoint.ElementID == Eid) + { + // Got to the end + NextPoint = FMeshSurfacePoint(EndPoint.ElementID, EndPoint.EdgeTValue); + break; + } + + if (NextPoint.ElementID != FDynamicMesh3::InvalidID) + { + // Looks like there are multiple intersections with the plane, so ambigous. + return false; + } + + // Otherwise, save the edge point + + double EdgeTValue = PlaneDistanceA / (PlaneDistanceA - PlaneDistanceB); + + CurrentEdgeVertPlaneDistances[0] = PlaneDistanceA; + CurrentEdgeVertPlaneDistances[1] = PlaneDistanceB; + + if (VertA != Mesh.GetEdgeV(Eid).A) + { + EdgeTValue = 1 - EdgeTValue; + Swap(CurrentEdgeVertPlaneDistances[0], CurrentEdgeVertPlaneDistances[1]); + } + + NextPoint = FMeshSurfacePoint(Eid, EdgeTValue); + TraversedTid = Tid; + } + }//end going through triangles + + // Make sure we found the next point. + if (NextPoint.ElementID == FDynamicMesh3::InvalidID) + { + return false; + } + else + { + OutputPath.Emplace(NextPoint, FDynamicMesh3::InvalidID); + } + }//end if at vert + else + { + const FDynamicMesh3::FEdge& Edge = Mesh.GetEdge(CurrentElementID); + + // We're starting from an edge. Get the triangle that we're dealing with. + int32 NextTid; + if (Edge.Tri.A == PreviousTid) + { + NextTid = Edge.Tri.B; + } + else if (Edge.Tri.B == PreviousTid) + { + NextTid = Edge.Tri.A; + } + else + { + NextTid = Mesh.GetTriangleGroup(Edge.Tri.A) == GroupID ? Edge.Tri.A : Edge.Tri.B; + } + + if (NextTid == FDynamicMesh3::InvalidID || Mesh.GetTriangleGroup(NextTid) != GroupID) + { + // We dead ended before getting to the end. + return false; + } + TraversedTid = NextTid; + + // Get the vertex opposite the current edge and it's location relative to the cut plane + int32 OppositeVert = IndexUtil::FindTriOtherVtx(Edge.Vert.A, Edge.Vert.B, Mesh.GetTriangle(NextTid)); + float OppositeVertPlaneDistance = FVector::PointPlaneDist((FVector)Mesh.GetVertex(OppositeVert), CutPlaneOrigin, CutPlaneNormal); + + if (EndPoint.bIsVertex && EndPoint.ElementID == OppositeVert) + { + // Got to the end + OutputPath.Emplace(FMeshSurfacePoint(EndPoint.ElementID), FDynamicMesh3::InvalidID); + } + else if (abs(OppositeVertPlaneDistance) <= VertexCutTolerance) + { + // We are cutting through a vertex + OutputPath.Emplace(FMeshSurfacePoint(OppositeVert), FDynamicMesh3::InvalidID); + } + else + { + // We are cutting through an edge. Figure out which one + int32 SecondVertOfNextEdge; + float SecondPlaneDistance; + if (CurrentEdgeVertPlaneDistances[0] * OppositeVertPlaneDistance < 0) + { + SecondVertOfNextEdge = Edge.Vert.A; + SecondPlaneDistance = CurrentEdgeVertPlaneDistances[0]; + } + else + { + // Impossible that all vertices are on the same side of plane, if we started from an edge that was + // cut by the plane. + check(CurrentEdgeVertPlaneDistances[1] * OppositeVertPlaneDistance < 0); + SecondVertOfNextEdge = Edge.Vert.B; + SecondPlaneDistance = CurrentEdgeVertPlaneDistances[1]; + } + + // Add the edge point to output + int32 Eid = Mesh.FindEdge(OppositeVert, SecondVertOfNextEdge); + + if (!EndPoint.bIsVertex && EndPoint.ElementID == Eid) + { + // Got to the end + OutputPath.Emplace(FMeshSurfacePoint(EndPoint.ElementID, EndPoint.EdgeTValue), FDynamicMesh3::InvalidID); + } + else + { + double EdgeTValue = OppositeVertPlaneDistance / (OppositeVertPlaneDistance - SecondPlaneDistance); + + CurrentEdgeVertPlaneDistances[0] = OppositeVertPlaneDistance; + CurrentEdgeVertPlaneDistances[1] = SecondPlaneDistance; + + if (OppositeVert != Mesh.GetEdgeV(Eid).A) + { + EdgeTValue = 1 - EdgeTValue; + Swap(CurrentEdgeVertPlaneDistances[0], CurrentEdgeVertPlaneDistances[1]); + } + + OutputPath.Emplace(FMeshSurfacePoint(Eid, EdgeTValue), FDynamicMesh3::InvalidID); + } + }//end cutting through edge + }//end if last point was edge + + ++PointCount; + check(PointCount == OutputPath.Num()) // Another sanity check, to make sure we're always advancing + + PreviousTid = TraversedTid; + PreviousVid = bCurrentPointIsVertex ? CurrentElementID : FDynamicMesh3::InvalidID; + + CurrentElementID = OutputPath.Last().Key.ElementID; + bCurrentPointIsVertex = (OutputPath.Last().Key.PointType == ESurfacePointType::Vertex); + }//end until we get to end + + return true; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupTopologyDeformer.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupTopologyDeformer.cpp index 3875f1623fb9..1dc47bde2a52 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupTopologyDeformer.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupTopologyDeformer.cpp @@ -196,10 +196,13 @@ void FGroupTopologyDeformer::CalculateROI(const TArray& HandleGroups, const { for (int32 tid : Mesh->VtxTrianglesItr(vid)) { - FIndex3i NormalTri = Normals->GetTriangle(tid); - ModifiedOverlayNormals.Add(NormalTri.A); - ModifiedOverlayNormals.Add(NormalTri.B); - ModifiedOverlayNormals.Add(NormalTri.C); + if (Normals->IsSetTriangle(tid)) + { + FIndex3i NormalTri = Normals->GetTriangle(tid); + ModifiedOverlayNormals.Add(NormalTri.A); + ModifiedOverlayNormals.Add(NormalTri.B); + ModifiedOverlayNormals.Add(NormalTri.C); + } } } } diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/InsetMeshRegion.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/InsetMeshRegion.cpp index eb50c36d7fab..ad5faac88ffe 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/InsetMeshRegion.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/InsetMeshRegion.cpp @@ -7,7 +7,10 @@ #include "DynamicMeshChangeTracker.h" #include "Selections/MeshConnectedComponents.h" #include "Distance/DistLine3Line3.h" - +#include "DynamicSubmesh3.h" +#include "Solvers/ConstrainedMeshDeformer.h" +#include "DynamicMeshAABBTree3.h" +#include "MeshTransforms.h" FInsetMeshRegion::FInsetMeshRegion(FDynamicMesh3* mesh) : Mesh(mesh) { @@ -78,6 +81,20 @@ bool FInsetMeshRegion::ApplyInset(FInsetInfo& Region, FMeshNormals* UseNormals) return false; } + // make copy of separated submesh for deformation + // (could we defer this copy until we know we need it?) + FDynamicSubmesh3 SubmeshCalc(Mesh, Region.InitialTriangles, (int)EMeshComponents::None, false); + FDynamicMesh3& Submesh = SubmeshCalc.GetSubmesh(); + bool bHaveInteriorVerts = false; + for (int32 vid : Submesh.VertexIndicesItr()) + { + if (Submesh.IsBoundaryVertex(vid) == false) + { + bHaveInteriorVerts = true; + break; + } + } + // inset vertices for (FDynamicMeshEditor::FLoopPairSet& LoopPair : LoopPairs) { @@ -119,6 +136,7 @@ bool FInsetMeshRegion::ApplyInset(FInsetInfo& Region, FMeshNormals* UseNormals) Mesh->SetVertex(LoopPair.InnerVertices[vi], NewPos); } + }; // stitch each loop @@ -126,6 +144,8 @@ bool FInsetMeshRegion::ApplyInset(FInsetInfo& Region, FMeshNormals* UseNormals) Region.InsetLoops.SetNum(NumInitialLoops); Region.StitchTriangles.SetNum(NumInitialLoops); Region.StitchPolygonIDs.SetNum(NumInitialLoops); + TArray> QuadLoops; + QuadLoops.Reserve(NumInitialLoops); int32 LoopIndex = 0; for (FDynamicMeshEditor::FLoopPairSet& LoopPair : LoopPairs) { @@ -172,17 +192,135 @@ bool FInsetMeshRegion::ApplyInset(FInsetInfo& Region, FMeshNormals* UseNormals) StitchResult.GetAllTriangles(Region.StitchTriangles[LoopIndex]); Region.StitchPolygonIDs[LoopIndex] = NewGroupIDs; - // for each polygon we created in stitch, set UVs and normals - // TODO copied from FExtrudeMesh, doesn't really make sense in this context... - if (Mesh->HasAttributes()) + QuadLoops.Add(MoveTemp(StitchResult.NewQuads)); + + Region.BaseLoops[LoopIndex].InitializeFromVertices(Mesh, BaseLoopV); + Region.InsetLoops[LoopIndex].InitializeFromVertices(Mesh, InsetLoopV); + LoopIndex++; + } + + // if we have interior vertices or just want to try to resolve foldovers we + // do a Laplacian solve using the inset positions determined geometrically + // as weighted soft constraints. + if ( (bHaveInteriorVerts || Softness > 0.0) && bSolveRegionInteriors ) + { + bool bReprojectInset = bReproject; + bool bReprojectInterior = bReproject; + bool bSolveBoundary = (Softness > 0.0); + + // Build AABBTree for initial surface so that we can reproject onto it. + // (conceivably this could be cached during interactive operations, also not + // necessary if we are not projecting!) + FDynamicMesh3 ProjectSurface(Submesh); + FDynamicMeshAABBTree3 Projection(&ProjectSurface, bReproject); + + // if we are reprojecting, do inset border immediately so that the measurements below + // use the projected values + if (bReprojectInset) { + for (const FEdgeLoop& Loop : Region.InsetLoops) + { + for (int32 BaseVID : Loop.Vertices) + { + int32 SubmeshVID = SubmeshCalc.MapVertexToSubmesh(BaseVID); + Mesh->SetVertex(BaseVID, Projection.FindNearestPoint(Mesh->GetVertex(BaseVID))); + } + } + } + + // compute area of inserted quad-strip border + double TotalBorderQuadArea = 0; + int32 NumLoops = LoopPairs.Num(); + for (int32 li = 0; li < NumLoops; ++li) + { + int32 NumQuads = QuadLoops[li].Num(); + for (int32 k = 0; k < NumQuads; k++) + { + TotalBorderQuadArea += Mesh->GetTriArea(QuadLoops[li][k].A); + TotalBorderQuadArea += Mesh->GetTriArea(QuadLoops[li][k].B); + } + } + + // Figure how much area chnaged by subtracting area of quad-strip from original area. + // (quad-strip area seems implausibly high at larger distances, ie becomes larger than initial area. Possibly due to sawtooth-shaped profile + // of non-planar quads - measure each quad in planar projection?) + FVector2d VolArea = TMeshQueries::GetVolumeArea(Submesh); + double InitialArea = VolArea.Y; + double TargetArea = FMathd::Max(0, InitialArea - TotalBorderQuadArea); + double AreaRatio = TargetArea / InitialArea; + double LinearAreaScale = FMathd::Max(0.1, FMathd::Sqrt(AreaRatio)); + + // compute deformation + TUniquePtr Solver = UE::MeshDeformation::ConstructSoftMeshDeformer(Submesh); + + // configure area correction based on scaling parameter + double AreaCorrectT = FMathd::Clamp(AreaCorrection, 0.0, 1.0); + LinearAreaScale = (1 - AreaCorrectT) * 1.0 + (AreaCorrectT)*LinearAreaScale; + Solver->UpdateLaplacianScale(LinearAreaScale); + + // Want to convert [0,1] softness parameter to a per-boundary-vertex Weight. + // Trying to use Vertex Count and Scaling factor to normalize for scale + // (really should scale mesh down to consistent size, but this is messy due to mapping back to Mesh) + // Laplacian scale above also impacts this...and perhaps we should only be counting boundary vertices?? + double UnitScalingMeasure = FMathd::Max(0.01, FMathd::Sqrt(VolArea.Y / 6.0)); + double NonlinearT = FMathd::Pow(Softness, 2.0); + double ScaledPower = (NonlinearT / 50.0) * (double)Submesh.VertexCount() * UnitScalingMeasure; + double Weight = (ScaledPower < FMathf::ZeroTolerance) ? 100.0 : (1.0 / ScaledPower); + + // add constraints on all the boundary vertices + for (const FEdgeLoop& Loop : Region.InsetLoops) + { + for (int32 BaseVID : Loop.Vertices) + { + int32 SubmeshVID = SubmeshCalc.MapVertexToSubmesh(BaseVID); + FVector3d CurPosition = Mesh->GetVertex(BaseVID); + Solver->AddConstraint(SubmeshVID, Weight, CurPosition, bSolveBoundary == false); + } + } + + // solve for deformed (and possibly reprojected) positions and update mesh + TArray DeformedPositions; + if (Solver->Deform(DeformedPositions)) + { + for (int32 SubmeshVID : Submesh.VertexIndicesItr()) + { + if (bSolveBoundary || Solver->IsConstrained(SubmeshVID) == false) + { + int32 BaseVID = SubmeshCalc.MapVertexToBaseMesh(SubmeshVID); + + FVector3d SolvePosition = DeformedPositions[SubmeshVID]; + if (bReprojectInterior) + { + SolvePosition = Projection.FindNearestPoint(SolvePosition); + } + + Mesh->SetVertex(BaseVID, SolvePosition); + } + } + } + } + + + // calculate UVs/etc + if (Mesh->HasAttributes()) + { + int32 NumLoops = LoopPairs.Num(); + for ( int32 li = 0; li < NumLoops; ++li) + { + FDynamicMeshEditor::FLoopPairSet& LoopPair = LoopPairs[li]; + TArray& BaseLoopV = LoopPair.OuterVertices; + TArray& InsetLoopV = LoopPair.InnerVertices; + + // for each polygon we created in stitch, set UVs and normals + // TODO copied from FExtrudeMesh, doesn't really make sense in this context... float AccumUVTranslation = 0; FFrame3d FirstProjectFrame; FVector3d FrameUp; - for (int32 k = 0; k < NumNewQuads; k++) + int32 NumQuads = QuadLoops[li].Num(); + for (int32 k = 0; k < NumQuads; k++) { - FVector3f Normal = Editor.ComputeAndSetQuadNormal(StitchResult.NewQuads[k], true); + FVector3f Normal = Editor.ComputeAndSetQuadNormal( QuadLoops[li][k], true); // align axis 0 of projection frame to first edge, then for further edges, // rotate around 'up' axis to keep normal aligned and frame horizontal @@ -209,13 +347,9 @@ bool FInsetMeshRegion::ApplyInset(FInsetInfo& Region, FMeshNormals* UseNormals) // translate horizontally such that vertical spans are adjacent in UV space (so textures tile/wrap properly) float TranslateU = UVScaleFactor * AccumUVTranslation; - Editor.SetQuadUVsFromProjection(StitchResult.NewQuads[k], ProjectFrame, UVScaleFactor, FVector2f(TranslateU, 0)); + Editor.SetQuadUVsFromProjection(QuadLoops[li][k], ProjectFrame, UVScaleFactor, FVector2f(TranslateU, 0)); } } - - Region.BaseLoops[LoopIndex].InitializeFromVertices(Mesh, BaseLoopV); - Region.InsetLoops[LoopIndex].InitializeFromVertices(Mesh, InsetLoopV); - LoopIndex++; } return true; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MeshConvexHull.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MeshConvexHull.cpp index 40ad2b388a6f..dca1d1512f88 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MeshConvexHull.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MeshConvexHull.cpp @@ -10,20 +10,10 @@ bool FMeshConvexHull::Compute() { - TArray ToLinear, FromLinear; - ToLinear.SetNum(Mesh->MaxVertexID()); - FromLinear.SetNum(Mesh->VertexCount()); - int32 LinearIndex = 0; - for (int32 vid : Mesh->VertexIndicesItr()) - { - FromLinear[LinearIndex] = vid; - ToLinear[vid] = LinearIndex++; - } - FConvexHull3d HullCompute; - bool bOK = HullCompute.Solve(FromLinear.Num(), - [&](int32 Index) { return Mesh->GetVertex(FromLinear[Index]); }, - bUseExactComputation); + bool bOK = HullCompute.Solve(Mesh->MaxVertexID(), + [this](int32 Index) { return Mesh->GetVertex(Index); }, + [this](int32 Index) { return Mesh->IsVertex(Index); }); if (!bOK) { return false; @@ -39,7 +29,7 @@ bool FMeshConvexHull::Compute() int32 Index = Triangle[j]; if (HullVertMap.Contains(Index) == false) { - FVector3d OrigPos = Mesh->GetVertex(FromLinear[Index]); + FVector3d OrigPos = Mesh->GetVertex(Index); int32 NewVID = ConvexHull.AppendVertex(OrigPos); HullVertMap.Add(Index, NewVID); Triangle[j] = NewVID; @@ -53,15 +43,13 @@ bool FMeshConvexHull::Compute() ConvexHull.AppendTriangle(Triangle); }); - - - if (bPostSimplify) { bool bSimplified = false; if (ConvexHull.TriangleCount() > MaxTargetFaceCount) { FVolPresMeshSimplification Simplifier(&ConvexHull); + Simplifier.CollapseMode = FVolPresMeshSimplification::ESimplificationCollapseModes::MinimalExistingVertexError; Simplifier.SimplifyToTriangleCount(MaxTargetFaceCount); bSimplified = true; } diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MeshProjectionHull.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MeshProjectionHull.cpp new file mode 100644 index 000000000000..73f58b551686 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MeshProjectionHull.cpp @@ -0,0 +1,72 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Operations/MeshProjectionHull.h" +#include "Solvers/MeshLinearization.h" +#include "MeshSimplification.h" +#include "MeshNormals.h" +#include "ConvexHull2.h" +#include "Generators/SweepGenerator.h" + + +bool FMeshProjectionHull::Compute() +{ + // compute set of projected 2D vertices, and find the 1D bounding interval along the projection axis + FVector3d ProjAxis = ProjectionFrame.Z(); + FInterval1d ProjInterval = FInterval1d::Empty(); + TArray Vertices; + Vertices.Reserve(Mesh->VertexCount()); + for (int32 vid : Mesh->VertexIndicesItr()) + { + FVector3d Position = Mesh->GetVertex(vid); + FVector2d ProjPos = ProjectionFrame.ToPlaneUV(Position, 2); + Vertices.Add(ProjPos); + + ProjInterval.Contain( (Position-ProjectionFrame.Origin).Dot(ProjAxis) ); + } + + // compute the 2D convex hull + FConvexHull2d HullCompute; + bool bOK = HullCompute.Solve(Vertices); + if (!bOK) + { + return false; + } + + // extract hull polygon + ConvexHull2D = FPolygon2d(Vertices, HullCompute.GetPolygonIndices()); + if (ConvexHull2D.Area() < FMathf::ZeroTolerance) + { + return false; + } + + // simplify if requested + if (bSimplifyPolygon) + { + SimplifiedHull2D = ConvexHull2D; + SimplifiedHull2D.Simplify(MinEdgeLength, DeviationTolerance); + } + + // shift the projection frame to the min position along the axis + FFrame3d CenterFrame = ProjectionFrame; + CenterFrame.Origin += ProjInterval.Min * ProjAxis; + + // apply min-thickness if necessary + double ExtrudeLength = ProjInterval.Length(); + if (ExtrudeLength < MinThickness) + { + ExtrudeLength = MinThickness; + CenterFrame.Origin -= (ExtrudeLength * 0.5) * ProjAxis; + } + + // generate the swept-polygon mesh + FGeneralizedCylinderGenerator MeshGen; + MeshGen.CrossSection = (bSimplifyPolygon) ? SimplifiedHull2D : ConvexHull2D; + MeshGen.Path.Add(CenterFrame.Origin); + CenterFrame.Origin += ExtrudeLength * ProjAxis; + MeshGen.Path.Add(CenterFrame.Origin); + MeshGen.bCapped = true; + MeshGen.Generate(); + ConvexHull3D.Copy(&MeshGen); + + return true; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Parameterization/DynamicMeshUVEditor.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Parameterization/DynamicMeshUVEditor.cpp index a84cd75dce25..e4e76d4a9343 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Parameterization/DynamicMeshUVEditor.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Parameterization/DynamicMeshUVEditor.cpp @@ -46,6 +46,51 @@ void FDynamicMeshUVEditor::CreateUVLayer(int32 LayerIndex) +template +void InternalSetPerTriangleUVs(EnumerableType TriangleIDs, const FDynamicMesh3* Mesh, FDynamicMeshUVOverlay* UVOverlay, double ScaleFactor, FUVEditResult* Result) +{ + TMap BaseToOverlayVIDMap; + TArray NewUVIndices; + + for (int32 TriangleID : TriangleIDs) + { + FIndex3i MeshTri = Mesh->GetTriangle(TriangleID); + FFrame3d TriProjFrame = Mesh->GetTriFrame(TriangleID, 0); + + FIndex3i ElemTri; + for (int32 j = 0; j < 3; ++j) + { + FVector2f UV = (FVector2f)TriProjFrame.ToPlaneUV(Mesh->GetVertex(MeshTri[j]), 2); + UV *= ScaleFactor; + ElemTri[j] = UVOverlay->AppendElement(UV); + NewUVIndices.Add(ElemTri[j]); + } + UVOverlay->SetTriangle(TriangleID, ElemTri); + } + + if (Result != nullptr) + { + Result->NewUVElements = MoveTemp(NewUVIndices); + } +} + + +void FDynamicMeshUVEditor::SetPerTriangleUVs(const TArray& Triangles, double ScaleFactor, FUVEditResult* Result) +{ + if (ensure(UVOverlay) == false) return; + if (!Triangles.Num()) return; + + InternalSetPerTriangleUVs(Triangles, Mesh, UVOverlay, ScaleFactor, Result); +} + + +void FDynamicMeshUVEditor::SetPerTriangleUVs(double ScaleFactor, FUVEditResult* Result) +{ + if (ensure(UVOverlay) == false) return; + if (Mesh->TriangleCount() <= 0) return; + + InternalSetPerTriangleUVs(Mesh->TriangleIndicesItr(), Mesh, UVOverlay, ScaleFactor, Result); +} void FDynamicMeshUVEditor::SetTriangleUVsFromProjection(const TArray& Triangles, const FFrame3d& ProjectionFrame, FUVEditResult* Result) diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Parameterization/MeshUVPacking.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Parameterization/MeshUVPacking.cpp new file mode 100644 index 000000000000..a6152b6b52e6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Parameterization/MeshUVPacking.cpp @@ -0,0 +1,981 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "Parameterization/MeshUVPacking.h" +#include "DynamicSubmesh3.h" +#include "Selections/MeshConnectedComponents.h" + +#include "Async/Future.h" +#include "Async/Async.h" + +#include "Misc/SecureHash.h" +#include "Allocator2D.h" + + +FDynamicMeshUVPacker::FDynamicMeshUVPacker(FDynamicMeshUVOverlay* UVOverlayIn) +{ + UVOverlay = UVOverlayIn; +} + + + +// +// local representation of a UV island as a set of triangle indices +// +struct FUVIsland +{ + // Store a unique id so that we can come back to the initial Charts ordering when necessary + int32 Id; + + // Set of triangles that make up this UV island. Assumption is this is single connected-component, + // otherwise multiple islands will be grouped. + TArray Triangles; + + // axis-aligned 2D bounding box min/max + FVector2d MinUV; + FVector2d MaxUV; + + double UVArea; + FVector2d WorldScale; + + FVector2d UVScale; + FVector2d PackingScaleU; + FVector2d PackingScaleV; + FVector2d PackingBias; +}; + + + + +// +// +// Chart Packer for UV islands of a FDynamicMesh3 +// This code is a port of the FLayoutUV class/implementation to FDynamicMesh3. +// The packing strategy is generally the same, however: +// - additional control over flips has been added +// - island merging support was removed, input islands must be externally computed +// - backwards-compatibility paths were removed +// +class FDynamicMeshStandardChartPacker +{ +public: + FDynamicMesh3* Mesh; + FDynamicMeshUVOverlay* Overlay; + + // packing target texture resolution, used to calculate gutter/border size + uint32 TextureResolution = 512; + + // if true, UV islands can be mirrored in X and/or Y to improve packing + bool bAllowFlips = false; + + double TotalUVArea = 0; + + // Top-level function, packs input charts into positive-unit-square + bool FindBestPacking(TArray& AllCharts); + +protected: + // + void ScaleCharts(TArray& Charts, double UVScale); + bool PackCharts(TArray& Charts, double UVScale, double& OutEfficiency, TAtomic& bAbort); + void OrientChart(FUVIsland& Chart, int32 Orientation); + void RasterizeChart(const FUVIsland& Chart, uint32 RectW, uint32 RectH, FAllocator2D& OutChartRaster); +}; + + + +bool FDynamicMeshStandardChartPacker::FindBestPacking(TArray& Charts) +{ + if ( (uint32)Charts.Num() > TextureResolution * TextureResolution) + { + // More charts than texels + return false; + } + + TotalUVArea = 0.0; + for (const FUVIsland& Chart : Charts) + { + TotalUVArea += Chart.UVArea * Chart.WorldScale.X * Chart.WorldScale.Y; + } + + if (TotalUVArea <= 0.0) + { + return false; + } + + // Cleanup uninitialized values to get a stable input hash + for (FUVIsland& Chart : Charts) + { + Chart.PackingBias = FVector2d::Zero(); + Chart.PackingScaleU = FVector2d::Zero(); + Chart.PackingScaleV = FVector2d::Zero(); + Chart.UVScale = FVector2d::Zero(); + } + + // Those might require tuning, changing them won't affect the outcome and will maintain backward compatibility + const int32 MultithreadChartsCountThreshold = 100 * 1000; + const int32 MultithreadTextureResolutionThreshold = 1000; + const int32 MultithreadAheadWorkCount = 3; + + const double LinearSearchStart = 0.5; + const double LinearSearchStep = 0.5; + const int32 BinarySearchSteps = 6; + + double UVScaleFail = TextureResolution * FMathd::Sqrt(1.0 / TotalUVArea); + double UVScalePass = TextureResolution * FMathd::Sqrt(LinearSearchStart / TotalUVArea); + + // Store successful charts packing to avoid redoing the final step + TArray LastPassCharts; + TAtomic bAbort(false); + + struct FThreadContext + { + TArray Charts; + TFuture Result; + double Efficiency = 0.0; + }; + + TArray ThreadContexts; + + bool bShouldUseMultipleThreads = + Charts.Num()>= MultithreadChartsCountThreshold && + TextureResolution>= MultithreadTextureResolutionThreshold; + + if (bShouldUseMultipleThreads) + { + // Do forward work only when multi-thread activated + ThreadContexts.SetNum(MultithreadAheadWorkCount); + } + + // Linear search for first fit + double LastEfficiency = 0.0f; + { + while (!bAbort) + { + // Launch forward work in other threads + for (int32 Index = 0; Index 0) + { + Charts = ThreadContexts[Index - 1].Charts; + LastEfficiency = ThreadContexts[Index - 1].Efficiency; + } + + LastPassCharts = Charts; + } + + if (!bAbort) + { + UVScaleFail = UVScalePass; + UVScalePass *= LinearSearchStep; + } + } + } + } + + // Binary search for best fit + { + bAbort = false; + for (int32 i = 0; i & Charts, double UVScale ) +{ + for( int32 i = 0; i MaxChartEdge) + { + // Rescale oversized charts to fit + Chart.UVScale.X = MaxChartEdge / FMathd::Max(ChartSize.X, ChartSize.Y); + Chart.UVScale.Y = MaxChartEdge / FMathd::Max(ChartSize.X, ChartSize.Y); + NumMaxedOut++; + } + else + { + Chart.UVScale.X *= UniformScale; + Chart.UVScale.Y *= UniformScale; + } + + ScaledUVArea += Chart.UVArea * Chart.UVScale.X * Chart.UVScale.Y; + } + + if (NumMaxedOut == 0) + { + // No charts maxed out so no need to rebalance + break; + } + + if (NumMaxedOut == Charts.Num()) + { + // All charts are maxed out + break; + } + + // Scale up smaller charts to maintain expected total area + // Want ScaledUVArea == TotalUVArea * UVScale^2 + double RebalanceScale = UVScale * FMathd::Sqrt(TotalUVArea / ScaledUVArea); + if (RebalanceScale < 1.01f) + { + // Stop if further rebalancing is minor + break; + } + UniformScale = RebalanceScale; + } +#endif + +#if 1 + double NonuniformScale = 1.0f; + for (int i = 0; i < 1000; i++) + { + uint32 NumMaxedOut = 0; + double ScaledUVArea = 0.0f; + for (int32 ChartIndex = 0; ChartIndex < Charts.Num(); ChartIndex++) + { + FUVIsland& Chart = Charts[ChartIndex]; + + for (int k = 0; k < 2; k++) + { + const double MaximumChartSize = TextureResolution - 1.0f; + const double ChartSize = Chart.MaxUV[k] - Chart.MinUV[k]; + const double ChartSizeScaled = ChartSize * Chart.UVScale[k] * NonuniformScale; + + const double Epsilon = 0.01f; + if (ChartSizeScaled + Epsilon > MaximumChartSize) + { + // Scale oversized charts to max size + Chart.UVScale[k] = MaximumChartSize / ChartSize; + NumMaxedOut++; + } + else + { + Chart.UVScale[k] *= NonuniformScale; + } + } + + ScaledUVArea += Chart.UVArea * Chart.UVScale.X * Chart.UVScale.Y; + } + + if (NumMaxedOut == 0) + { + // No charts maxed out so no need to rebalance + break; + } + + if (NumMaxedOut == Charts.Num() * 2) + { + // All charts are maxed out in both dimensions + break; + } + + // Scale up smaller charts to maintain expected total area + // Want ScaledUVArea == TotalUVArea * UVScale^2 + double RebalanceScale = UVScale * FMathd::Sqrt(TotalUVArea / ScaledUVArea); + if (RebalanceScale < 1.01f) + { + // Stop if further rebalancing is minor + break; + } + NonuniformScale = RebalanceScale; + } +#endif + + // Sort charts from largest to smallest + struct FCompareCharts + { + FORCEINLINE bool operator()( const FUVIsland& A, const FUVIsland& B ) const + { + // Rect area + FVector2d ChartRectA = ( A.MaxUV - A.MinUV ) * A.UVScale; + FVector2d ChartRectB = ( B.MaxUV - B.MinUV ) * B.UVScale; + return ChartRectA.X * ChartRectA.Y> ChartRectB.X * ChartRectB.Y; + } + }; + Algo::IntroSort( Charts, FCompareCharts() ); +} + + + +// Hash function to use FMD5Hash in TMap +inline uint32 GetTypeHash(const FMD5Hash& Hash) +{ + uint32* HashAsInt32 = (uint32*)Hash.GetBytes(); + return HashAsInt32[0] ^ HashAsInt32[1] ^ HashAsInt32[2] ^ HashAsInt32[3]; +} + +bool FDynamicMeshStandardChartPacker::PackCharts(TArray& Charts, double UVScale, double& OutEfficiency, TAtomic& bAbort) +{ + ScaleCharts(Charts, UVScale); + + FAllocator2D BestChartRaster(FAllocator2D::EMode::UsedSegments, TextureResolution, TextureResolution, ELightmapUVVersion::Latest); + FAllocator2D ChartRaster(FAllocator2D::EMode::UsedSegments, TextureResolution, TextureResolution, ELightmapUVVersion::Latest); + FAllocator2D LayoutRaster(FAllocator2D::EMode::FreeSegments, TextureResolution, TextureResolution, ELightmapUVVersion::Latest); + + uint64 RasterizeCycles = 0; + uint64 FindCycles = 0; + + OutEfficiency = 0.0f; + LayoutRaster.Clear(); + + // Store the position where we found a spot for each unique raster + // so we can skip whole sections we know won't work out. + // This method is obviously more efficient with smaller charts + // but helps tremendously as the number of charts goes up for + // the same texture space. This helps counteract the slowdown + // induced by having more parts to place in the grid and is + // particularly useful for foliage. + TMap BestStartPos; + + // Reduce Insights CPU tracing to once per batch + const int32 BatchSize = 1024; + for (int32 ChartIndex = 0; ChartIndex IsBestRect = + [&BestRect](const FAllocator2D::FRect& Rect) + { + return ((Rect.X + Rect.W) + (Rect.Y + Rect.H)) <((BestRect.X + BestRect.W) + (BestRect.Y + BestRect.H)); + }; + + // simpler thing? + //TFunction IsBestRect = + // [this, &BestRect](const FAllocator2D::FRect& Rect) + //{ + // return Rect.X + Rect.Y * TextureResolution X; + RasterRect.Y = StartPos->Y; + } + + LayoutRaster.ResetStats(); + bFound = LayoutRaster.FindWithSegments(RasterRect, ChartRaster, IsBestRect); + if (bFound) + { + // Store only the best possible position in the hash table so we can start from there for other identical charts + BestStartPos.Add(RasterMD5, FVector2d(RasterRect.X, RasterRect.Y)); + + // Since the older version stops searching at Width - Rect.W instead of using the raster size, + // it means a perfect rasterized square of 2,2 won't fit a 2,2 hole at the end of a row if Rect.W = 3. + // Because of that, we have no choice to worsen our algorithm behavior for backward compatibility. + + // Once we know the best possible position, we'll continue our search from there with the original + // rect value if it differs from the raster rect to ensure we get the same result as the old algorithm. + //if (LayoutVersion = 0) + { + // Add chart to layout + OrientChart(Chart, BestOrientation); + + LayoutRaster.Alloc(BestRect, BestChartRaster); + + Chart.PackingBias.X += BestRect.X; + Chart.PackingBias.Y += BestRect.Y; + } + else + { + if (true) + { + UE_LOG(LogTemp, Log, TEXT("[LAYOUTUV_TRACE] Chart %d Found no orientation that fit\n"), ChartIndex); + } + + // Found no orientation that fit + return false; + } + } + } + + if (bAbort) + { + return false; + } + + const uint32 TotalTexels = TextureResolution * TextureResolution; + const uint32 UsedTexels = LayoutRaster.GetUsedTexels(); + + OutEfficiency = double(UsedTexels) / TotalTexels; + + return true; +} + + + + + + +void FDynamicMeshStandardChartPacker::OrientChart(FUVIsland& Chart, int32 Orientation) +{ + switch (Orientation) + { + case 0: + // 0 degrees + Chart.PackingScaleU = FVector2d(Chart.UVScale.X, 0); + Chart.PackingScaleV = FVector2d(0, Chart.UVScale.Y); + Chart.PackingBias = -Chart.MinUV.X * Chart.PackingScaleU - Chart.MinUV.Y * Chart.PackingScaleV + 0.5f; + break; + case 1: + // 0 degrees, flip x + Chart.PackingScaleU = FVector2d(-Chart.UVScale.X, 0); + Chart.PackingScaleV = FVector2d(0, Chart.UVScale.Y); + Chart.PackingBias = -Chart.MaxUV.X * Chart.PackingScaleU - Chart.MinUV.Y * Chart.PackingScaleV + 0.5f; + break; + case 2: + // 90 degrees + Chart.PackingScaleU = FVector2d(0, -Chart.UVScale.X); + Chart.PackingScaleV = FVector2d(Chart.UVScale.Y, 0); + Chart.PackingBias = -Chart.MaxUV.X * Chart.PackingScaleU - Chart.MinUV.Y * Chart.PackingScaleV + 0.5f; + break; + case 3: + // 90 degrees, flip x + Chart.PackingScaleU = FVector2d(0, Chart.UVScale.X); + Chart.PackingScaleV = FVector2d(Chart.UVScale.Y, 0); + Chart.PackingBias = -Chart.MinUV.X * Chart.PackingScaleU - Chart.MinUV.Y * Chart.PackingScaleV + 0.5f; + break; + case 4: + // 180 degrees + Chart.PackingScaleU = FVector2d(-Chart.UVScale.X, 0); + Chart.PackingScaleV = FVector2d(0, -Chart.UVScale.Y); + Chart.PackingBias = -Chart.MaxUV.X * Chart.PackingScaleU - Chart.MaxUV.Y * Chart.PackingScaleV + 0.5f; + break; + case 5: + // 180 degrees, flip x + Chart.PackingScaleU = FVector2d(Chart.UVScale.X, 0); + Chart.PackingScaleV = FVector2d(0, -Chart.UVScale.Y); + Chart.PackingBias = -Chart.MinUV.X * Chart.PackingScaleU - Chart.MaxUV.Y * Chart.PackingScaleV + 0.5f; + break; + case 6: + // 270 degrees + Chart.PackingScaleU = FVector2d(0, Chart.UVScale.X); + Chart.PackingScaleV = FVector2d(-Chart.UVScale.Y, 0); + Chart.PackingBias = -Chart.MinUV.X * Chart.PackingScaleU - Chart.MaxUV.Y * Chart.PackingScaleV + 0.5f; + break; + case 7: + // 270 degrees, flip x + Chart.PackingScaleU = FVector2d(0, -Chart.UVScale.X); + Chart.PackingScaleV = FVector2d(-Chart.UVScale.Y, 0); + Chart.PackingBias = -Chart.MaxUV.X * Chart.PackingScaleU - Chart.MaxUV.Y * Chart.PackingScaleV + 0.5f; + break; + } +} + + + +// Max of 2048x2048 due to precision +// Dilate in 28.4 fixed point. Half pixel dilation is conservative rasterization. +// Dilation same as Minkowski sum of triangle and square. +template +void InternalRasterizeTriangle(FAllocator2D& Shader, const FVector2f Points[3], int32 ScissorWidth, int32 ScissorHeight) +{ + const FVector2f HalfPixel(0.5f, 0.5f); + FVector2f p0 = Points[0] - HalfPixel; + FVector2f p1 = Points[1] - HalfPixel; + FVector2f p2 = Points[2] - HalfPixel; + + // Correct winding + float Facing = (p0.X - p1.X) * (p2.Y - p0.Y) - (p0.Y - p1.Y) * (p2.X - p0.X); + if (Facing <0.0f) + { + Swap(p0, p2); + } + + // 28.4 fixed point + const int32 X0 = (int32)(16.0f * p0.X + 0.5f); + const int32 X1 = (int32)(16.0f * p1.X + 0.5f); + const int32 X2 = (int32)(16.0f * p2.X + 0.5f); + + const int32 Y0 = (int32)(16.0f * p0.Y + 0.5f); + const int32 Y1 = (int32)(16.0f * p1.Y + 0.5f); + const int32 Y2 = (int32)(16.0f * p2.Y + 0.5f); + + // Bounding rect + int32 MinX = (FMath::Min3(X0, X1, X2) - Dilate + 15) / 16; + int32 MaxX = (FMath::Max3(X0, X1, X2) + Dilate + 15) / 16; + int32 MinY = (FMath::Min3(Y0, Y1, Y2) - Dilate + 15) / 16; + int32 MaxY = (FMath::Max3(Y0, Y1, Y2) + Dilate + 15) / 16; + + // Clip to image + MinX = FMath::Clamp(MinX, 0, ScissorWidth); + MaxX = FMath::Clamp(MaxX, 0, ScissorWidth); + MinY = FMath::Clamp(MinY, 0, ScissorHeight); + MaxY = FMath::Clamp(MaxY, 0, ScissorHeight); + + // Deltas + const int32 DX01 = X0 - X1; + const int32 DX12 = X1 - X2; + const int32 DX20 = X2 - X0; + + const int32 DY01 = Y0 - Y1; + const int32 DY12 = Y1 - Y2; + const int32 DY20 = Y2 - Y0; + + // Half-edge constants + int32 C0 = DY01 * X0 - DX01 * Y0; + int32 C1 = DY12 * X1 - DX12 * Y1; + int32 C2 = DY20 * X2 - DX20 * Y2; + + // Correct for fill convention + C0 += (DY01 <0 || (DY01 == 0 && DX01> 0)) ? 0 : -1; + C1 += (DY12 <0 || (DY12 == 0 && DX12> 0)) ? 0 : -1; + C2 += (DY20 <0 || (DY20 == 0 && DX20> 0)) ? 0 : -1; + + // Dilate edges + C0 += (abs(DX01) + abs(DY01)) * Dilate; + C1 += (abs(DX12) + abs(DY12)) * Dilate; + C2 += (abs(DX20) + abs(DY20)) * Dilate; + + for (int32 y = MinY; y = 0 && Edge2>= 0 && Edge3>= 0 + int32 IsInside; + IsInside = C0 + (DX01 * y - DY01 * x) * 16; + IsInside |= C1 + (DX12 * y - DY12 * x) * 16; + IsInside |= C2 + (DX20 * y - DY20 * x) * 16; + + if (IsInside>= 0) + { + Shader.SetBit(x, y); + } + } + } +} + + + + +void FDynamicMeshStandardChartPacker::RasterizeChart(const FUVIsland& Chart, uint32 RectW, uint32 RectH, FAllocator2D& OutChartRaster) +{ + // Bilinear footprint is -1 to 1 pixels. If packed geometrically, only a half pixel dilation + // would be needed to guarantee all charts were at least 1 pixel away, safe for bilinear filtering. + // Unfortunately, with pixel packing a full 1 pixel dilation is required unless chart edges exactly + // align with pixel centers. + + OutChartRaster.Clear(); + + for (int32 tid : Chart.Triangles) + { + FIndex3i UVTriangle = Overlay->GetTriangle(tid); + FVector2f Points[3]; + for (int k = 0; k <3; k++) + { + FVector2d UV = (FVector2d)Overlay->GetElement(UVTriangle[k]); + Points[k] = (FVector2f)(UV.X * Chart.PackingScaleU + UV.Y * Chart.PackingScaleV + Chart.PackingBias); + } + + InternalRasterizeTriangle<16>(OutChartRaster, Points, RectW, RectH); + } + + OutChartRaster.CreateUsedSegments(); +} + + + + +bool FDynamicMeshUVPacker::StandardPack() +{ + FDynamicMeshStandardChartPacker Packer; + + Packer.Mesh = UVOverlay->GetParentMesh(); + Packer.Overlay = UVOverlay; + + Packer.TextureResolution = this->TextureResolution; + Packer.bAllowFlips = this->bAllowFlips; + + FMeshConnectedComponents UVComponents(Packer.Mesh); + UVComponents.FindConnectedTriangles([&](int32 Triangle0, int32 Triangle1) { + return UVOverlay->AreTrianglesConnected(Triangle0, Triangle1); + }); + + int32 NumCharts = UVComponents.Num(); + TArray AllCharts; + AllCharts.SetNum(NumCharts); + for (int32 ci = 0; ci < NumCharts; ++ci) + { + FMeshConnectedComponents::FComponent& Component = UVComponents.GetComponent(ci); + FUVIsland& Chart = AllCharts[ci]; + + Chart.Id = ci + 1; + + Chart.Triangles = MoveTemp(Component.Indices); + + Chart.MinUV = FVector2d(FLT_MAX, FLT_MAX); + Chart.MaxUV = FVector2d(-FLT_MAX, -FLT_MAX); + Chart.UVArea = 0.0f; + Chart.WorldScale = FVector2d::Zero(); + + for (int32 tid : Chart.Triangles) + { + FIndex3i Triangle3D = Packer.Mesh->GetTriangle(tid); + FIndex3i TriangleUV = Packer.Overlay->GetTriangle(tid); + + FVector3d Positions[3]; + FVector2d UVs[3]; + + for (int k = 0; k < 3; k++) + { + Positions[k] = Packer.Mesh->GetVertex(Triangle3D[k]); + UVs[k] = (FVector2d)UVOverlay->GetElement(TriangleUV[k]); + + Chart.MinUV.X = FMathd::Min(Chart.MinUV.X, UVs[k].X); + Chart.MinUV.Y = FMathd::Min(Chart.MinUV.Y, UVs[k].Y); + Chart.MaxUV.X = FMathd::Max(Chart.MaxUV.X, UVs[k].X); + Chart.MaxUV.Y = FMathd::Max(Chart.MaxUV.Y, UVs[k].Y); + } + + FVector3d Edge1 = Positions[1] - Positions[0]; + FVector3d Edge2 = Positions[2] - Positions[0]; + double Area = 0.5f * (Edge1.Cross(Edge2)).Length(); + + FVector2d EdgeUV1 = UVs[1] - UVs[0]; + FVector2d EdgeUV2 = UVs[2] - UVs[0]; + double UVArea = 0.5f * FMathd::Abs(EdgeUV1.X * EdgeUV2.Y - EdgeUV1.Y * EdgeUV2.X); + + FVector2d UVLength; + UVLength.X = (EdgeUV2.Y * Edge1 - EdgeUV1.Y * Edge2).Length(); + UVLength.Y = (-EdgeUV2.X * Edge1 + EdgeUV1.X * Edge2).Length(); + + Chart.WorldScale += UVLength; + Chart.UVArea += UVArea; + } + + Chart.WorldScale /= FMathd::Max(Chart.UVArea, 1e-8f); + } + + + bool bPackingFound = Packer.FindBestPacking(AllCharts); + check(bPackingFound); + + + // Commit chart UVs + for (int32 i = 0; i IslandElements; + + for (int32 tid : Chart.Triangles) + { + FIndex3i Triangle = UVOverlay->GetTriangle(tid); + IslandElements.Add(Triangle.A); + IslandElements.Add(Triangle.B); + IslandElements.Add(Triangle.C); + } + + for (int32 elemid : IslandElements) + { + FVector2d UV = (FVector2d)UVOverlay->GetElement(elemid); + FVector2d TransformedUV = UV.X * Chart.PackingScaleU + UV.Y * Chart.PackingScaleV + Chart.PackingBias; + UVOverlay->SetElement(elemid, (FVector2f)TransformedUV); + } + + } + + return bPackingFound; +} + + + +bool FDynamicMeshUVPacker::StackPack() +{ + FDynamicMesh3* Mesh = UVOverlay->GetParentMesh(); + + double GutterWidth = (double)GutterSize / (double)TextureResolution; + + FMeshConnectedComponents UVComponents(Mesh); + UVComponents.FindConnectedTriangles([&](int32 Triangle0, int32 Triangle1) { + return UVOverlay->AreTrianglesConnected(Triangle0, Triangle1); + }); + + int32 NumCharts = UVComponents.Num(); + + // figure out maximum width and height of existing charts + TArray AllIslandBounds; + AllIslandBounds.SetNum(NumCharts); + double MaxWidth = 0, MaxHeight = 0; + for (int32 ci = 0; ci < NumCharts; ++ci) + { + FMeshConnectedComponents::FComponent& Component = UVComponents.GetComponent(ci); + + FAxisAlignedBox2d IslandBounds = FAxisAlignedBox2d::Empty(); + for (int32 tid : Component.Indices) + { + FVector2f UVTri[3]; + UVOverlay->GetTriElements(tid, UVTri[0], UVTri[1], UVTri[2]); + IslandBounds.Contain((FVector2d)UVTri[0]); + IslandBounds.Contain((FVector2d)UVTri[1]); + IslandBounds.Contain((FVector2d)UVTri[2]); + AllIslandBounds[ci] = IslandBounds; + } + + MaxWidth = FMathd::Max(IslandBounds.Width(), MaxWidth); + MaxHeight = FMathd::Max(IslandBounds.Height(), MaxHeight); + } + + // figure out uniform scale that will make them all fit + double TargetWidth = 1.0 - 2 * GutterWidth; + double TargetHeight = 1.0 - 2 * GutterWidth; + double WidthScale = TargetWidth / MaxWidth; + double HeightScale = TargetWidth / MaxHeight; + double UseUniformScale = (FMathd::Min(WidthScale, HeightScale) > 1) ? + FMathd::Min(WidthScale, HeightScale) : FMathd::Max(WidthScale, HeightScale); + + // transform them + TSet IslandElements; + for (int32 ci = 0; ci < NumCharts; ++ci) + { + FMeshConnectedComponents::FComponent& Component = UVComponents.GetComponent(ci); + + IslandElements.Reset(); + FAxisAlignedBox2d IslandBounds = AllIslandBounds[ci]; + for (int32 tid : Component.Indices) + { + FIndex3i UVTri = UVOverlay->GetTriangle(tid); + IslandElements.Add(UVTri[0]); + IslandElements.Add(UVTri[1]); + IslandElements.Add(UVTri[2]); + } + + for (int32 elemid : IslandElements) + { + FVector2d CurUV = (FVector2d)UVOverlay->GetElement(elemid); + FVector2d NewUV = (CurUV - IslandBounds.Min) * UseUniformScale; + UVOverlay->SetElement(elemid, (FVector2f)NewUV); + } + } + + return true; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshCurvatureMapBaker.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshCurvatureMapBaker.cpp new file mode 100644 index 000000000000..e172ae19179b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshCurvatureMapBaker.cpp @@ -0,0 +1,202 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Sampling/MeshCurvatureMapBaker.h" +#include "Sampling/SphericalFibonacci.h" +#include "Sampling/Gaussians.h" +#include "MeshCurvature.h" +#include "MeshWeights.h" +#include "MeshNormals.h" + +#include "Math/RandomStream.h" + + + +void FMeshCurvatureMapBaker::CacheDetailCurvatures(const FDynamicMesh3* DetailMesh) +{ + if (! Curvatures) + { + Curvatures = MakeShared(); + Curvatures->BuildAll(*DetailMesh); + } + check(Curvatures->Num() == DetailMesh->MaxVertexID()); + +} + + +void FMeshCurvatureMapBaker::Bake() +{ + const FMeshImageBakingCache* BakeCache = GetCache(); + check(BakeCache); + const FDynamicMesh3* DetailMesh = BakeCache->GetDetailMesh(); + + CacheDetailCurvatures(DetailMesh); + + ResultBuilder = MakeUnique>(); + ResultBuilder->SetDimensions(BakeCache->GetDimensions()); + + + Bake_Single(); + + + const FImageOccupancyMap& Occupancy = *BakeCache->GetOccupancyMap(); + + for (int64 k = 0; k < Occupancy.GutterTexels.Num(); k++) + { + TPair GutterTexel = Occupancy.GutterTexels[k]; + ResultBuilder->CopyPixel(GutterTexel.Value, GutterTexel.Key); + } + + if (BlurRadius > 0.01) + { + TDiscreteKernel2f BlurKernel2d; + TGaussian2f::MakeKernelFromRadius(BlurRadius, BlurKernel2d); + TArray AOBlurBuffer; + Occupancy.ParallelProcessingPass( + [&](int64 Index) { return FVector3f::Zero(); }, + [&](int64 LinearIdx, float Weight, FVector3f& CurValue) { CurValue += Weight * ResultBuilder->GetPixel(LinearIdx); }, + [&](int64 LinearIdx, float WeightSum, FVector3f& CurValue) { CurValue /= WeightSum; }, + [&](int64 LinearIdx, FVector3f& CurValue) { ResultBuilder->SetPixel(LinearIdx, CurValue); }, + [&](const FVector2i& TexelOffset) { return BlurKernel2d.EvaluateFromOffset(TexelOffset); }, + BlurKernel2d.IntRadius, + AOBlurBuffer); + } + +} + + +void FMeshCurvatureMapBaker::Bake_Single() +{ + const FMeshImageBakingCache* BakeCache = GetCache(); + const FDynamicMesh3* DetailMesh = BakeCache->GetDetailMesh(); + const FMeshVertexCurvatureCache& Cache = *Curvatures; + + + double MinPreClamp = -TNumericLimits::Max(); + double MaxPreClamp = TNumericLimits::Max(); + if (UseClampMode == EClampMode::Positive) + { + MinPreClamp = 0; + } + else if (UseClampMode == EClampMode::Negative) + { + MaxPreClamp = 0; + } + + + + auto GetCurvature = [&](int32 vid) { + switch (UseCurvatureType) + { + default: + case ECurvatureType::Mean: + return FMathd::Clamp(Cache[vid].Mean, MinPreClamp, MaxPreClamp); + case ECurvatureType::Gaussian: + return FMathd::Clamp(Cache[vid].Gaussian, MinPreClamp, MaxPreClamp); + case ECurvatureType::MaxPrincipal: + return FMathd::Clamp(Cache[vid].MaxPrincipal, MinPreClamp, MaxPreClamp); + case ECurvatureType::MinPrincipal: + return FMathd::Clamp(Cache[vid].MinPrincipal, MinPreClamp, MaxPreClamp); + } + }; + + FInterval1d CurvatureRange = Cache.MeanRange; + switch (UseCurvatureType) + { + default: + case ECurvatureType::Mean: + CurvatureRange = Cache.MeanRange; + break; + case ECurvatureType::Gaussian: + CurvatureRange = Cache.GaussianRange; + break; + case ECurvatureType::MaxPrincipal: + CurvatureRange = Cache.MaxPrincipalRange; + break; + case ECurvatureType::MinPrincipal: + CurvatureRange = Cache.MinPrincipalRange; + break; + } + + double ClampMax = CurvatureRange.MaxAbsExtrema(); + if (UseClampMode == EClampMode::Positive) + { + ClampMax = FMathd::Abs(CurvatureRange.Max); + } + else if (UseClampMode == EClampMode::Negative) + { + ClampMax = FMathd::Abs(CurvatureRange.Min); + } + + + ClampMax *= RangeScale; + double MinClamp = MinRangeScale * ClampMax; + FInterval1d ClampRange(MinClamp, ClampMax); + + // sample curvature of detail surface in tangent-space of base surface + auto CurvatureSampleFunction = [&](const FMeshImageBakingCache::FCorrespondenceSample& SampleData) + { + int32 DetailTriID = SampleData.DetailTriID; + if (DetailMesh->IsTriangle(DetailTriID)) + { + FIndex3i DetailTri = DetailMesh->GetTriangle(DetailTriID); + double CurvatureA = GetCurvature(DetailTri.A); + double CurvatureB = GetCurvature(DetailTri.B); + double CurvatureC = GetCurvature(DetailTri.C); + double InterpCurvature = SampleData.DetailBaryCoords.X * CurvatureA + + SampleData.DetailBaryCoords.Y * CurvatureB + + SampleData.DetailBaryCoords.Z * CurvatureC; + + return InterpCurvature; + } + return 0.0; + }; + + + + FVector3f NegativeColor, ZeroColor, PositiveColor; + GetColorMapRange(NegativeColor, ZeroColor, PositiveColor); + + BakeCache->EvaluateSamples([&](const FVector2i& Coords, const FMeshImageBakingCache::FCorrespondenceSample& Sample) + { + double Curvature = CurvatureSampleFunction(Sample); + + double Sign = FMathd::Sign(Curvature); + double T = ClampRange.GetT(FMathd::Abs(Curvature)); + + FVector3f CurvatureColor = (Sign < 0) ? + FVector3f::Lerp(ZeroColor, NegativeColor, T) + : FVector3f::Lerp(ZeroColor, PositiveColor, T); + + ResultBuilder->SetPixel(Coords, CurvatureColor); + }); +} + + +void FMeshCurvatureMapBaker::Bake_Multi() +{ + +} + + +void FMeshCurvatureMapBaker::GetColorMapRange(FVector3f& NegativeColor, FVector3f& ZeroColor, FVector3f& PositiveColor) +{ + switch (UseColorMode) + { + default: + case EColorMode::BlackGrayWhite: + NegativeColor = FVector3f(0, 0, 0); + ZeroColor = FVector3f(0.5, 0.5, 0.5); + PositiveColor = FVector3f(1, 1, 1); + break; + case EColorMode::RedGreenBlue: + NegativeColor = FVector3f(1, 0, 0); + ZeroColor = FVector3f(0, 1, 0); + PositiveColor = FVector3f(0, 0, 1); + break; + case EColorMode::RedBlue: + NegativeColor = FVector3f(1, 0, 0); + ZeroColor = FVector3f(0, 0, 0); + PositiveColor = FVector3f(0, 0, 1); + break; + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshImageBakingCache.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshImageBakingCache.cpp new file mode 100644 index 000000000000..fb2d7429389b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshImageBakingCache.cpp @@ -0,0 +1,261 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Sampling/MeshImageBakingCache.h" + + + +void FMeshImageBakingCache::SetDetailMesh(const FDynamicMesh3* Mesh, const FDynamicMeshAABBTree3* Spatial) +{ + check(Mesh); + DetailMesh = Mesh; + check(Spatial); + DetailSpatial = Spatial; + InvalidateSamples(); + InvalidateOccupancy(); +} + +void FMeshImageBakingCache::SetBakeTargetMesh(const FDynamicMesh3* Mesh) +{ + check(Mesh); + TargetMesh = Mesh; + InvalidateSamples(); + InvalidateOccupancy(); +} + + +void FMeshImageBakingCache::SetDimensions(FImageDimensions DimensionsIn) +{ + Dimensions = DimensionsIn; + InvalidateSamples(); + InvalidateOccupancy(); +} + + +void FMeshImageBakingCache::SetUVLayer(int32 UVLayerIn) +{ + UVLayer = UVLayerIn; + InvalidateSamples(); + InvalidateOccupancy(); +} + + +const FDynamicMeshNormalOverlay* FMeshImageBakingCache::GetDetailNormals() const +{ + check(DetailMesh && DetailMesh->HasAttributes()); + return DetailMesh->Attributes()->PrimaryNormals(); +} + + +const FDynamicMeshUVOverlay* FMeshImageBakingCache::GetBakeTargetUVs() const +{ + check(TargetMesh && TargetMesh->HasAttributes() && UVLayer < TargetMesh->Attributes()->NumUVLayers()); + return TargetMesh->Attributes()->GetUVLayer(UVLayer); +} + +const FDynamicMeshNormalOverlay* FMeshImageBakingCache::GetBakeTargetNormals() const +{ + check(TargetMesh && TargetMesh->HasAttributes()); + return TargetMesh->Attributes()->PrimaryNormals(); +} + +const FImageOccupancyMap* FMeshImageBakingCache::GetOccupancyMap() const +{ + check(IsCacheValid()); + return OccupancyMap.Get(); +} + +void FMeshImageBakingCache::InvalidateSamples() +{ + bSamplesValid = false; +} + +void FMeshImageBakingCache::InvalidateOccupancy() +{ + bOccupancyValid = false; +} + + + + + + + +/** + * Find point on Detail mesh that corresponds to point on Base mesh. + * If nearest point on Detail mesh is within DistanceThreshold, uses that point (cleanly handles coplanar/etc). + * Otherwise casts a ray in Normal direction. + * If Normal-direction ray misses, use reverse direction. + * If both miss, we return false, no correspondence found + */ +static bool GetDetailTrianglePoint( + const FDynamicMesh3& DetailMesh, + const FDynamicMeshAABBTree3& DetailSpatial, + const FVector3d& BasePoint, + const FVector3d& BaseNormal, + int32& DetailTriangleOut, + FVector3d& DetailTriBaryCoords, + double DistanceThreshold = FMathf::ZeroTolerance * 100.0f) +{ + // check if we are within on-surface tolerance, if so we use nearest point + IMeshSpatial::FQueryOptions OnSurfQueryOptions; + OnSurfQueryOptions.MaxDistance = DistanceThreshold; + double NearDistSqr = 0; + int32 NearestTriID = DetailSpatial.FindNearestTriangle(BasePoint, NearDistSqr, OnSurfQueryOptions); + if (DetailMesh.IsTriangle(NearestTriID)) + { + DetailTriangleOut = NearestTriID; + FDistPoint3Triangle3d DistQuery = TMeshQueries::TriangleDistance(DetailMesh, NearestTriID, BasePoint); + DetailTriBaryCoords = DistQuery.TriangleBaryCoords; + return true; + } + + // TODO: should we check normals here? inverse normal should probably not be considered valid + + // shoot rays forwards and backwards + FRay3d Ray(BasePoint, BaseNormal), BackwardsRay(BasePoint, -BaseNormal); + int32 HitTID = IndexConstants::InvalidID, BackwardHitTID = IndexConstants::InvalidID; + double HitDist, BackwardHitDist; + bool bHitForward = DetailSpatial.FindNearestHitTriangle(Ray, HitDist, HitTID); + bool bHitBackward = DetailSpatial.FindNearestHitTriangle(BackwardsRay, BackwardHitDist, BackwardHitTID); + + // use the backwards hit if it is closer than the forwards hit + if ((bHitBackward && bHitForward == false) || (bHitForward && bHitBackward && BackwardHitDist < HitDist)) + { + Ray = BackwardsRay; + HitTID = BackwardHitTID; + HitDist = BackwardHitDist; + } + + // if we got a valid ray hit, use it + if (DetailMesh.IsTriangle(HitTID)) + { + DetailTriangleOut = HitTID; + FIntrRay3Triangle3d IntrQuery = TMeshQueries::TriangleIntersection(DetailMesh, HitTID, Ray); + DetailTriBaryCoords = IntrQuery.TriangleBaryCoords; + return true; + } + + // if we get this far, both rays missed, so use absolute nearest point regardless of distance + //NearestTriID = DetailSpatial.FindNearestTriangle(BasePoint, NearDistSqr); + //if (DetailMesh.IsTriangle(NearestTriID)) + //{ + // DetailTriangleOut = NearestTriID; + // FDistPoint3Triangle3d DistQuery = TMeshQueries::TriangleDistance(DetailMesh, NearestTriID, BasePoint); + // DetailTriBaryCoords = DistQuery.TriangleBaryCoords; + // return true; + //} + + return false; +} + + + + + + +bool FMeshImageBakingCache::ValidateCache() +{ + check(TargetMesh && DetailMesh && DetailSpatial); + check(Dimensions.GetWidth() > 0 && Dimensions.GetHeight() > 0); + + const FDynamicMesh3* Mesh = TargetMesh; + const FDynamicMeshUVOverlay* UVOverlay = GetBakeTargetUVs(); + const FDynamicMeshNormalOverlay* NormalOverlay = GetBakeTargetNormals(); + + // make UV-space version of mesh + if (bOccupancyValid == false) + { + FDynamicMesh3 FlatMesh(EMeshComponents::FaceGroups); + for (int32 tid : Mesh->TriangleIndicesItr()) + { + if (UVOverlay->IsSetTriangle(tid)) + { + FVector2f A, B, C; + UVOverlay->GetTriElements(tid, A, B, C); + int32 VertA = FlatMesh.AppendVertex(FVector3d(A.X, A.Y, 0)); + int32 VertB = FlatMesh.AppendVertex(FVector3d(B.X, B.Y, 0)); + int32 VertC = FlatMesh.AppendVertex(FVector3d(C.X, C.Y, 0)); + int32 NewTriID = FlatMesh.AppendTriangle(VertA, VertB, VertC, tid); + } + } + + // calculate occupancy map + OccupancyMap = MakeUnique(); + OccupancyMap->Initialize(Dimensions); + OccupancyMap->ComputeFromUVSpaceMesh(FlatMesh, [&](int32 TriangleID) { return FlatMesh.GetTriangleGroup(TriangleID); }); + + bOccupancyValid = true; + } + + + if (bSamplesValid == false) + { + // this sampler finds the correspondence between base surface and detail surface + TMeshSurfaceUVSampler DetailMeshSampler; + DetailMeshSampler.Initialize(Mesh, UVOverlay, EMeshSurfaceSamplerQueryType::TriangleAndUV, FCorrespondenceSample(), + [Mesh, NormalOverlay, this](const FMeshUVSampleInfo& SampleInfo, FCorrespondenceSample& ValueOut) + { + //FVector3d BaseTriNormal = Mesh->GetTriNormal(SampleInfo.TriangleIndex); + NormalOverlay->GetTriBaryInterpolate(SampleInfo.TriangleIndex, &SampleInfo.BaryCoords[0], &ValueOut.BaseNormal[0]); + ValueOut.BaseNormal.Normalize(); + FVector3d RayDir = ValueOut.BaseNormal; + + ValueOut.BaseSample = SampleInfo; + + // find detail mesh triangle point + bool bFoundTri = GetDetailTrianglePoint(*DetailMesh, *DetailSpatial, SampleInfo.SurfacePoint, RayDir, + ValueOut.DetailTriID, ValueOut.DetailBaryCoords); + if (!bFoundTri) + { + ValueOut.DetailTriID = FDynamicMesh3::InvalidID; + } + }); + + + SampleMap.Resize(Dimensions.GetWidth(), Dimensions.GetHeight()); + + // calculate interior texels + ParallelFor(Dimensions.Num(), [&](int64 LinearIdx) + { + if (OccupancyMap->IsInterior(LinearIdx) == false) + { + return; + } + + FVector2d UVPosition = (FVector2d)OccupancyMap->TexelQueryUV[LinearIdx]; + int32 UVTriangleID = OccupancyMap->TexelQueryTriangle[LinearIdx]; + + FCorrespondenceSample Sample; + DetailMeshSampler.SampleUV(UVTriangleID, UVPosition, Sample); + + SampleMap[LinearIdx] = Sample; + }); + + bSamplesValid = true; + } + + return IsCacheValid(); +} + + + +void FMeshImageBakingCache::EvaluateSamples( + TFunctionRef SampleFunction, + bool bParallel) const +{ + check(IsCacheValid()); + + ParallelFor(Dimensions.Num(), [&](int64 LinearIdx) + { + if (OccupancyMap->IsInterior(LinearIdx) == false) + { + return; + } + FVector2i Coords = Dimensions.GetCoords(LinearIdx); + + const FCorrespondenceSample& Sample = SampleMap[LinearIdx]; + + SampleFunction(Coords, Sample); + + }, bParallel ? EParallelForFlags::ForceSingleThread : EParallelForFlags::None); +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshNormalMapBaker.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshNormalMapBaker.cpp new file mode 100644 index 000000000000..13429b948f31 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshNormalMapBaker.cpp @@ -0,0 +1,62 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Sampling/MeshNormalMapBaker.h" + + +void FMeshNormalMapBaker::Bake() +{ + const FMeshImageBakingCache* BakeCache = GetCache(); + check(BakeCache); + const FDynamicMesh3* DetailMesh = BakeCache->GetDetailMesh(); + const FDynamicMeshNormalOverlay* DetailNormalOverlay = BakeCache->GetDetailNormals(); + check(DetailNormalOverlay); + + auto NormalSampleFunction = [&](const FMeshImageBakingCache::FCorrespondenceSample& SampleData) + { + int32 DetailTriID = SampleData.DetailTriID; + if (DetailMesh->IsTriangle(DetailTriID)) + { + // get tangents on base mesh + FVector3d BaseTangentX, BaseTangentY; + BaseMeshTangents->GetInterpolatedTriangleTangent( + SampleData.BaseSample.TriangleIndex, + SampleData.BaseSample.BaryCoords, + BaseTangentX, BaseTangentY); + + // sample normal on detail mesh + FVector3d DetailNormal; + DetailNormalOverlay->GetTriBaryInterpolate(DetailTriID, &SampleData.DetailBaryCoords[0], &DetailNormal.X); + DetailNormal.Normalize(); + + // compute normal in tangent space + double dx = DetailNormal.Dot(BaseTangentX); + double dy = DetailNormal.Dot(BaseTangentY); + double dz = DetailNormal.Dot(SampleData.BaseNormal); + + return FVector3f((float)dx, (float)dy, (float)dz); + } + return FVector3f::UnitZ(); + }; + + + NormalsBuilder = MakeUnique>(); + NormalsBuilder->SetDimensions(BakeCache->GetDimensions()); + + + BakeCache->EvaluateSamples([&](const FVector2i& Coords, const FMeshImageBakingCache::FCorrespondenceSample& Sample) + { + FVector3f RelativeDetailNormal = NormalSampleFunction(Sample); + FVector3f MapNormal = (RelativeDetailNormal + FVector3f::One()) * 0.5; + NormalsBuilder->SetPixel(Coords, MapNormal); + }); + + const FImageOccupancyMap& Occupancy = *BakeCache->GetOccupancyMap(); + + for (int64 k = 0; k < Occupancy.GutterTexels.Num(); k++) + { + TPair GutterTexel = Occupancy.GutterTexels[k]; + NormalsBuilder->CopyPixel(GutterTexel.Value, GutterTexel.Key); + } + + +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshOcclusionMapBaker.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshOcclusionMapBaker.cpp new file mode 100644 index 000000000000..b99354050d6d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshOcclusionMapBaker.cpp @@ -0,0 +1,131 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Sampling/MeshOcclusionMapBaker.h" +#include "Sampling/SphericalFibonacci.h" +#include "Sampling/Gaussians.h" + +#include "Math/RandomStream.h" + + +void FMeshOcclusionMapBaker::Bake() +{ + const FMeshImageBakingCache* BakeCache = GetCache(); + check(BakeCache); + const FDynamicMesh3* DetailMesh = BakeCache->GetDetailMesh(); + const FDynamicMeshAABBTree3* DetailSpatial = BakeCache->GetDetailSpatial(); + const FDynamicMeshNormalOverlay* DetailNormalOverlay = BakeCache->GetDetailNormals(); + check(DetailNormalOverlay); + + double BiasDotThreshold = FMathd::Cos(FMathd::Clamp(90.0 - BiasAngleDeg, 0.0, 90.0) * FMathd::DegToRad); + + // precompute ray directions for AO + TSphericalFibonacci Points(2 * NumOcclusionRays); + TArray RayDirections; + for (int32 k = 0; k < Points.Num(); ++k) + { + FVector3d P = Points[k]; + if (P.Z > 0) + { + RayDirections.Add(P.Normalized()); + } + } + + // + FRandomStream RotationGen(31337); + FCriticalSection RotationLock; + auto GetRandomRotation = [&RotationGen, &RotationLock]() { + RotationLock.Lock(); + double Angle = RotationGen.GetFraction() * FMathd::TwoPi; + RotationLock.Unlock(); + return Angle; + }; + + + auto OcclusionSampleFunction = [&](const FMeshImageBakingCache::FCorrespondenceSample& SampleData) + { + int32 DetailTriID = SampleData.DetailTriID; + if (DetailMesh->IsTriangle(DetailTriID)) + { + FIndex3i DetailTri = DetailMesh->GetTriangle(DetailTriID); + //FVector3d DetailTriNormal = DetailMesh.GetTriNormal(DetailTriID); + FVector3d DetailTriNormal; + DetailNormalOverlay->GetTriBaryInterpolate(DetailTriID, &SampleData.DetailBaryCoords[0], &DetailTriNormal.X); + DetailTriNormal.Normalize(); + + FVector3d DetailBaryCoords = SampleData.DetailBaryCoords; + FVector3d DetailPos = DetailMesh->GetTriBaryPoint(DetailTriID, DetailBaryCoords.X, DetailBaryCoords.Y, DetailBaryCoords.Z); + DetailPos += 10.0f * FMathf::ZeroTolerance * DetailTriNormal; + FFrame3d SurfaceFrame(DetailPos, DetailTriNormal); + + double RotationAngle = GetRandomRotation(); + SurfaceFrame.Rotate(FQuaterniond(SurfaceFrame.Z(), RotationAngle, false)); + + IMeshSpatial::FQueryOptions QueryOptions; + QueryOptions.MaxDistance = MaxDistance; + + double AccumOcclusion = 0; + double TotalWeight = 0; + for (FVector3d SphereDir : RayDirections) + { + FRay3d OcclusionRay(DetailPos, SurfaceFrame.FromFrameVector(SphereDir)); + check(OcclusionRay.Direction.Dot(DetailTriNormal) > 0); + + // Have weight of point fall off as it becomes more coplanar with face. + // This reduces faceting artifacts that we would otherwise see because geometry does not vary smoothly + double PointWeight = 1.0; + double BiasDot = OcclusionRay.Direction.Dot(DetailTriNormal); + if (BiasDot < BiasDotThreshold) + { + PointWeight = FMathd::Lerp(0.0, 1.0, FMathd::Clamp(BiasDot / BiasDotThreshold, 0.0, 1.0)); + PointWeight *= PointWeight; + } + TotalWeight += PointWeight; + + if (DetailSpatial->TestAnyHitTriangle(OcclusionRay, QueryOptions)) + { + AccumOcclusion += PointWeight; + } + } + + AccumOcclusion = (TotalWeight > 0.0001) ? (AccumOcclusion / TotalWeight) : 0.0; + return AccumOcclusion; + } + return 0.0; + }; + + OcclusionBuilder = MakeUnique>(); + OcclusionBuilder->SetDimensions(BakeCache->GetDimensions()); + + BakeCache->EvaluateSamples([&](const FVector2i& Coords, const FMeshImageBakingCache::FCorrespondenceSample& Sample) + { + double Occlusion = OcclusionSampleFunction(Sample); + FVector3f OcclusionColor = FMathd::Clamp(1.0f - (float)Occlusion, 0.0f, 1.0f) * FVector3f::One(); + OcclusionBuilder->SetPixel(Coords, OcclusionColor); + }); + + const FImageOccupancyMap& Occupancy = *BakeCache->GetOccupancyMap(); + + for (int64 k = 0; k < Occupancy.GutterTexels.Num(); k++) + { + TPair GutterTexel = Occupancy.GutterTexels[k]; + OcclusionBuilder->CopyPixel(GutterTexel.Value, GutterTexel.Key); + } + + if (BlurRadius > 0.01) + { + TDiscreteKernel2f BlurKernel2d; + TGaussian2f::MakeKernelFromRadius(BlurRadius, BlurKernel2d); + TArray AOBlurBuffer; + Occupancy.ParallelProcessingPass( + [&](int64 Index) { return 0.0f; }, + [&](int64 LinearIdx, float Weight, float& CurValue) { CurValue += Weight * OcclusionBuilder->GetPixel(LinearIdx).X; }, + [&](int64 LinearIdx, float WeightSum, float& CurValue) { CurValue /= WeightSum; }, + [&](int64 LinearIdx, float& CurValue) { OcclusionBuilder->SetPixel(LinearIdx, FVector3f(CurValue, CurValue, CurValue)); }, + [&](const FVector2i& TexelOffset) { return BlurKernel2d.EvaluateFromOffset(TexelOffset); }, + BlurKernel2d.IntRadius, + AOBlurBuffer); + } + + + +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshPropertyMapBaker.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshPropertyMapBaker.cpp new file mode 100644 index 000000000000..462c632d4588 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshPropertyMapBaker.cpp @@ -0,0 +1,130 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Sampling/MeshPropertyMapBaker.h" + + +static FVector3f NormalToColor(const FVector3d Normal) +{ + return (FVector3f)((Normal + FVector3d::One()) * 0.5); +} + +static FVector3f UVToColor(const FVector2d UV) +{ + double X = FMathd::Clamp(UV.X, 0.0, 1.0); + double Y = FMathd::Clamp(UV.Y, 0.0, 1.0); + return (FVector3f)FVector3d(X, Y, 0); +} + +static FVector3f PositionToColor(const FVector3d Position, const FAxisAlignedBox3d SafeBounds) +{ + double X = (Position.X - SafeBounds.Min.X) / SafeBounds.Width(); + double Y = (Position.Y - SafeBounds.Min.Y) / SafeBounds.Height(); + double Z = (Position.Z - SafeBounds.Min.Z) / SafeBounds.Depth(); + return (FVector3f)FVector3d(X, Y, Z); +} + + +void FMeshPropertyMapBaker::Bake() +{ + const FMeshImageBakingCache* BakeCache = GetCache(); + check(BakeCache); + const FDynamicMesh3* DetailMesh = BakeCache->GetDetailMesh(); + const FDynamicMeshNormalOverlay* DetailNormalOverlay = BakeCache->GetDetailNormals(); + check(DetailNormalOverlay); + const FDynamicMeshUVOverlay* DetailUVOverlay = DetailMesh->Attributes()->PrimaryUV(); + + FAxisAlignedBox3d Bounds = DetailMesh->GetBounds(); + for (int32 j = 0; j < 3; ++j) + { + if (Bounds.Diagonal()[j] < FMathf::ZeroTolerance) + { + Bounds.Min[j] = Bounds.Center()[j] - FMathf::ZeroTolerance; + Bounds.Max[j] = Bounds.Center()[j] + FMathf::ZeroTolerance; + + } + } + + FVector3f DefaultValue(0, 0, 0); + switch (this->Property) + { + case EMeshPropertyBakeType::Position: + DefaultValue = PositionToColor(Bounds.Center(), Bounds); + break; + default: + case EMeshPropertyBakeType::FacetNormal: + case EMeshPropertyBakeType::Normal: + DefaultValue = NormalToColor(FVector3d::UnitZ()); + break; + case EMeshPropertyBakeType::UVPosition: + DefaultValue = UVToColor(FVector2d::Zero()); + break; + } + + auto PropertySampleFunction = [&](const FMeshImageBakingCache::FCorrespondenceSample& SampleData) + { + FVector3f Color = DefaultValue; + int32 DetailTriID = SampleData.DetailTriID; + if (DetailMesh->IsTriangle(DetailTriID)) + { + switch (this->Property) + { + case EMeshPropertyBakeType::Position: + { + FVector3d Position = DetailMesh->GetTriBaryPoint(DetailTriID, SampleData.DetailBaryCoords[0], SampleData.DetailBaryCoords[1], SampleData.DetailBaryCoords[2]); + Color = PositionToColor(Position, Bounds); + } + break; + default: + case EMeshPropertyBakeType::FacetNormal: + { + FVector3d FacetNormal = DetailMesh->GetTriNormal(DetailTriID); + Color = NormalToColor(FacetNormal); + } + break; + break; + case EMeshPropertyBakeType::Normal: + { + if (DetailNormalOverlay->IsSetTriangle(DetailTriID)) + { + FVector3d DetailNormal; + DetailNormalOverlay->GetTriBaryInterpolate(DetailTriID, &SampleData.DetailBaryCoords[0], &DetailNormal.X); + DetailNormal.Normalize(); + Color = NormalToColor(DetailNormal); + } + } + break; + case EMeshPropertyBakeType::UVPosition: + { + if (DetailUVOverlay && DetailUVOverlay->IsSetTriangle(DetailTriID)) + { + FVector2d DetailUV; + DetailUVOverlay->GetTriBaryInterpolate(DetailTriID, &SampleData.DetailBaryCoords[0], &DetailUV.X); + Color = UVToColor(DetailUV); + } + } + break; + } + } + return Color; + }; + + + ResultBuilder = MakeUnique>(); + ResultBuilder->SetDimensions(BakeCache->GetDimensions()); + + BakeCache->EvaluateSamples([&](const FVector2i& Coords, const FMeshImageBakingCache::FCorrespondenceSample& Sample) + { + FVector3f Color = PropertySampleFunction(Sample); + ResultBuilder->SetPixel(Coords, Color); + }); + + const FImageOccupancyMap& Occupancy = *BakeCache->GetOccupancyMap(); + + for (int64 k = 0; k < Occupancy.GutterTexels.Num(); k++) + { + TPair GutterTexel = Occupancy.GutterTexels[k]; + ResultBuilder->CopyPixel(GutterTexel.Value, GutterTexel.Key); + } + + +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshResampleImageBaker.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshResampleImageBaker.cpp new file mode 100644 index 000000000000..ca79c1cd86f3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Sampling/MeshResampleImageBaker.cpp @@ -0,0 +1,46 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Sampling/MeshResampleImageBaker.h" + +void FMeshResampleImageBaker::Bake() +{ + const FMeshImageBakingCache* BakeCache = GetCache(); + check(BakeCache); + const FDynamicMesh3* DetailMesh = BakeCache->GetDetailMesh(); + + check(DetailUVOverlay); + + FVector4f DefaultValue(0, 0, 0, 1.0); + + auto PropertySampleFunction = [&](const FMeshImageBakingCache::FCorrespondenceSample& SampleData) + { + FVector4f Color = DefaultValue; + int32 DetailTriID = SampleData.DetailTriID; + if (DetailMesh->IsTriangle(SampleData.DetailTriID) && DetailUVOverlay) + { + FVector2d DetailUV; + DetailUVOverlay->GetTriBaryInterpolate(DetailTriID, &SampleData.DetailBaryCoords[0], &DetailUV.X); + + Color = SampleFunction(DetailUV); + } + return Color; + }; + + ResultBuilder = MakeUnique>(); + ResultBuilder->SetDimensions(BakeCache->GetDimensions()); + + BakeCache->EvaluateSamples([&](const FVector2i& Coords, const FMeshImageBakingCache::FCorrespondenceSample& Sample) + { + FVector4f Color = PropertySampleFunction(Sample); + ResultBuilder->SetPixel(Coords, Color); + }); + + const FImageOccupancyMap& Occupancy = *BakeCache->GetOccupancyMap(); + + for (int64 k = 0; k < Occupancy.GutterTexels.Num(); k++) + { + TPair GutterTexel = Occupancy.GutterTexels[k]; + ResultBuilder->CopyPixel(GutterTexel.Value, GutterTexel.Key); + } + +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/MeshSimpleShapeApproximation.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/MeshSimpleShapeApproximation.cpp new file mode 100644 index 000000000000..fe3fbfda64b4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/MeshSimpleShapeApproximation.cpp @@ -0,0 +1,415 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ShapeApproximation/MeshSimpleShapeApproximation.h" +#include "Async/ParallelFor.h" + +#include "MinVolumeSphere3.h" +#include "MinVolumeBox3.h" +#include "FitCapsule3.h" +//#include "DynamicMeshAABBTree3.h" + +#include "ShapeApproximation/ShapeDetection3.h" +#include "MeshQueries.h" +#include "Operations/MeshConvexHull.h" +#include "Operations/MeshProjectionHull.h" + + + +void FMeshSimpleShapeApproximation::DetectAndCacheSimpleShapeType(const FDynamicMesh3* SourceMesh, FSourceMeshCache& CacheOut) +{ + if (UE::Geometry::IsBoxMesh(*SourceMesh, CacheOut.DetectedBox)) + { + CacheOut.DetectedType = EDetectedSimpleShapeType::Box; + } + else if (UE::Geometry::IsSphereMesh(*SourceMesh, CacheOut.DetectedSphere)) + { + CacheOut.DetectedType = EDetectedSimpleShapeType::Sphere; + } + else if (UE::Geometry::IsCapsuleMesh(*SourceMesh, CacheOut.DetectedCapsule)) + { + CacheOut.DetectedType = EDetectedSimpleShapeType::Capsule; + } +} + + + + +void FMeshSimpleShapeApproximation::InitializeSourceMeshes(const TArray& InputMeshSet) +{ + SourceMeshes = InputMeshSet; + SourceMeshCaches.Reset(); + SourceMeshCaches.SetNum(SourceMeshes.Num()); + + ParallelFor(SourceMeshes.Num(), [&](int32 k) { + DetectAndCacheSimpleShapeType( SourceMeshes[k], SourceMeshCaches[k]); + }); + +} + + + + + + + +bool FMeshSimpleShapeApproximation::GetDetectedSimpleShape( + const FSourceMeshCache& Cache, + FSimpleShapeSet3d& ShapeSetOut, + FCriticalSection& ShapeSetLock) +{ + if (Cache.DetectedType == EDetectedSimpleShapeType::Sphere && bDetectSpheres) + { + ShapeSetLock.Lock(); + ShapeSetOut.Spheres.Add(Cache.DetectedSphere); + ShapeSetLock.Unlock(); + return true; + } + else if (Cache.DetectedType == EDetectedSimpleShapeType::Box && bDetectBoxes) + { + ShapeSetLock.Lock(); + ShapeSetOut.Boxes.Add(Cache.DetectedBox); + ShapeSetLock.Unlock(); + return true; + } + else if (Cache.DetectedType == EDetectedSimpleShapeType::Capsule && bDetectCapsules) + { + ShapeSetLock.Lock(); + ShapeSetOut.Capsules.Add(Cache.DetectedCapsule); + ShapeSetLock.Unlock(); + return true; + } + + return false; +} + + + + + +struct FSimpleShapeFitsResult +{ + bool bHaveSphere = false; + FSphere3d Sphere; + + bool bHaveBox = false; + FOrientedBox3d Box; + + bool bHaveCapsule = false; + FCapsule3d Capsule; + + bool bHaveConvex = false; + FDynamicMesh3 Convex; +}; + + + +static void ComputeSimpleShapeFits(const FDynamicMesh3& Mesh, + bool bSphere, bool bBox, bool bCapsule, bool bConvex, FSimpleShapeFitsResult& FitResult) +{ + TArray ToLinear, FromLinear; + if (bSphere || bBox || bCapsule) + { + FromLinear.SetNum(Mesh.VertexCount()); + int32 LinearIndex = 0; + for (int32 vid : Mesh.VertexIndicesItr()) + { + FromLinear[LinearIndex++] = vid; + } + } + + FitResult.bHaveBox = false; + if (bBox) + { + FMinVolumeBox3d MinBoxCalc; + bool bMinBoxOK = MinBoxCalc.Solve(FromLinear.Num(), + [&](int32 Index) { return Mesh.GetVertex(FromLinear[Index]); }); + if (bMinBoxOK && MinBoxCalc.IsSolutionAvailable()) + { + FitResult.bHaveBox = true; + MinBoxCalc.GetResult(FitResult.Box); + } + } + + FitResult.bHaveSphere = false; + if (bSphere) + { + FMinVolumeSphere3d MinSphereCalc; + bool bMinSphereOK = MinSphereCalc.Solve(FromLinear.Num(), + [&](int32 Index) { return Mesh.GetVertex(FromLinear[Index]); }); + if (bMinSphereOK && MinSphereCalc.IsSolutionAvailable()) + { + FitResult.bHaveSphere = true; + MinSphereCalc.GetResult(FitResult.Sphere); + } + } + + FitResult.bHaveCapsule = false; + if (bCapsule) + { + FitResult.bHaveCapsule = TFitCapsule3::Solve(FromLinear.Num(), + [&](int32 Index) { return Mesh.GetVertex(FromLinear[Index]); }, FitResult.Capsule); + } + + // todo: once we have computed convex we can use it to compute box + FitResult.bHaveConvex = false; + if (bConvex) + { + FMeshConvexHull Hull(&Mesh); + if (Hull.Compute()) + { + FitResult.bHaveConvex = true; + FitResult.Convex = MoveTemp(Hull.ConvexHull); + } + } +} + + + + + +void FMeshSimpleShapeApproximation::Generate_AlignedBoxes(FSimpleShapeSet3d& ShapeSetOut) +{ + FCriticalSection GeometryLock; + ParallelFor(SourceMeshes.Num(), [&](int32 idx) + { + if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock)) + { + return; + } + + FAxisAlignedBox3d Bounds = SourceMeshes[idx]->GetBounds(); + + FBoxShape3d NewBox; + NewBox.Box = FOrientedBox3d(Bounds); + + GeometryLock.Lock(); + ShapeSetOut.Boxes.Add(NewBox); + GeometryLock.Unlock(); + }); +} + + + +void FMeshSimpleShapeApproximation::Generate_OrientedBoxes(FSimpleShapeSet3d& ShapeSetOut) +{ + FCriticalSection GeometryLock; + ParallelFor(SourceMeshes.Num(), [&](int32 idx) + { + if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock)) + { + return; + } + + const FDynamicMesh3& SourceMesh = *SourceMeshes[idx]; + FSimpleShapeFitsResult FitResult; + ComputeSimpleShapeFits(SourceMesh, false, true, false, false, FitResult); + + if (FitResult.bHaveBox) + { + GeometryLock.Lock(); + ShapeSetOut.Boxes.Add(FBoxShape3d(FitResult.Box)); + GeometryLock.Unlock(); + } + }); +} + +void FMeshSimpleShapeApproximation::Generate_MinimalSpheres(FSimpleShapeSet3d& ShapeSetOut) +{ + FCriticalSection GeometryLock; + ParallelFor(SourceMeshes.Num(), [&](int32 idx) + { + if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock)) + { + return; + } + + const FDynamicMesh3& SourceMesh = *SourceMeshes[idx]; + FSimpleShapeFitsResult FitResult; + ComputeSimpleShapeFits(SourceMesh, true, false, false, false, FitResult); + + if (FitResult.bHaveSphere) + { + GeometryLock.Lock(); + ShapeSetOut.Spheres.Add(FSphereShape3d(FitResult.Sphere)); + GeometryLock.Unlock(); + } + }); +} + +void FMeshSimpleShapeApproximation::Generate_Capsules(FSimpleShapeSet3d& ShapeSetOut) +{ + FCriticalSection GeometryLock; + ParallelFor(SourceMeshes.Num(), [&](int32 idx) + { + if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock)) + { + return; + } + + const FDynamicMesh3& SourceMesh = *SourceMeshes[idx]; + FSimpleShapeFitsResult FitResult; + ComputeSimpleShapeFits(SourceMesh, false, false, true, false, FitResult); + + if (FitResult.bHaveCapsule) + { + GeometryLock.Lock(); + ShapeSetOut.Capsules.Add(FCapsuleShape3d(FitResult.Capsule)); + GeometryLock.Unlock(); + } + }); +} + + + +void FMeshSimpleShapeApproximation::Generate_ConvexHulls(FSimpleShapeSet3d& ShapeSetOut) +{ + FCriticalSection GeometryLock; + ParallelFor(SourceMeshes.Num(), [&](int32 idx) + { + if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock)) + { + return; + } + + const FDynamicMesh3& SourceMesh = *SourceMeshes[idx]; + FMeshConvexHull Hull(&SourceMesh); + + Hull.bPostSimplify = bSimplifyHulls; + Hull.MaxTargetFaceCount = HullTargetFaceCount; + + if (Hull.Compute()) + { + FConvexShape3d NewConvex; + NewConvex.Mesh = MoveTemp(Hull.ConvexHull); + GeometryLock.Lock(); + ShapeSetOut.Convexes.Add(NewConvex); + GeometryLock.Unlock(); + } + }); +} + + + + + + +void FMeshSimpleShapeApproximation::Generate_ProjectedHulls(FSimpleShapeSet3d& ShapeSetOut, EProjectedHullAxisMode AxisMode) +{ + FCriticalSection GeometryLock; + ParallelFor(SourceMeshes.Num(), [&](int32 idx) + { + if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock)) + { + return; + } + + const FDynamicMesh3& Mesh = *SourceMeshes[idx]; + + FFrame3d ProjectionPlane(FVector3d::Zero(), FVector3d::UnitY()); + if (AxisMode == EProjectedHullAxisMode::SmallestBoxDimension) + { + FAxisAlignedBox3d Bounds = Mesh.GetBounds(); + int32 AxisIndex = Bounds.Diagonal().MinAbsElementIndex(); + check(Bounds.Diagonal().MinAbsElement() == Bounds.Diagonal()[AxisIndex]); + ProjectionPlane = FFrame3d(FVector3d::Zero(), FVector3d::MakeUnit(AxisIndex)); + } + else if (AxisMode == EProjectedHullAxisMode::SmallestVolume) + { + FMeshProjectionHull HullX(&Mesh); + HullX.ProjectionFrame = FFrame3d(FVector3d::Zero(), FVector3d::UnitX()); + HullX.MinThickness = FMathd::Max(MinDimension, 0); + bool bHaveX = HullX.Compute(); + FMeshProjectionHull HullY(&Mesh); + HullY.ProjectionFrame = FFrame3d(FVector3d::Zero(), FVector3d::UnitY()); + HullY.MinThickness = FMathd::Max(MinDimension, 0); + bool bHaveY = HullY.Compute(); + FMeshProjectionHull HullZ(&Mesh); + HullZ.ProjectionFrame = FFrame3d(FVector3d::Zero(), FVector3d::UnitZ()); + HullZ.MinThickness = FMathd::Max(MinDimension, 0); + bool bHaveZ = HullZ.Compute(); + int32 MinIdx = FMathd::Min3Index( + (bHaveX) ? TMeshQueries::GetVolumeArea(HullX.ConvexHull3D).X : TNumericLimits::Max(), + (bHaveY) ? TMeshQueries::GetVolumeArea(HullY.ConvexHull3D).X : TNumericLimits::Max(), + (bHaveZ) ? TMeshQueries::GetVolumeArea(HullZ.ConvexHull3D).X : TNumericLimits::Max()); + ProjectionPlane = (MinIdx == 0) ? HullX.ProjectionFrame : ((MinIdx == 1) ? HullY.ProjectionFrame : HullZ.ProjectionFrame); + } + else + { + ProjectionPlane = FFrame3d(FVector3d::Zero(), FVector3d::MakeUnit((int32)AxisMode)); + } + + FMeshProjectionHull Hull(&Mesh); + Hull.ProjectionFrame = ProjectionPlane; + Hull.MinThickness = FMathd::Max(MinDimension, 0); + Hull.bSimplifyPolygon = bSimplifyHulls; + Hull.MinEdgeLength = HullSimplifyTolerance; + Hull.DeviationTolerance = HullSimplifyTolerance; + + if (Hull.Compute()) + { + FConvexShape3d NewConvex; + NewConvex.Mesh = MoveTemp(Hull.ConvexHull3D); + GeometryLock.Lock(); + ShapeSetOut.Convexes.Add(NewConvex); + GeometryLock.Unlock(); + } + }); +} + + + + +void FMeshSimpleShapeApproximation::Generate_MinVolume(FSimpleShapeSet3d& ShapeSetOut) +{ + FCriticalSection GeometryLock; + ParallelFor(SourceMeshes.Num(), [&](int32 idx) + { + if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock)) + { + return; + } + + const FDynamicMesh3& SourceMesh = *SourceMeshes[idx]; + + FOrientedBox3d AlignedBox = FOrientedBox3d(SourceMesh.GetBounds()); + + FSimpleShapeFitsResult FitResult; + ComputeSimpleShapeFits(SourceMesh, true, true, true, false, FitResult); + + double Volumes[4]; + Volumes[0] = AlignedBox.Volume(); + Volumes[1] = (FitResult.bHaveBox) ? FitResult.Box.Volume() : TNumericLimits::Max(); + Volumes[2] = (FitResult.bHaveSphere) ? FitResult.Sphere.Volume() : TNumericLimits::Max(); + Volumes[3] = (FitResult.bHaveCapsule) ? FitResult.Capsule.Volume() : TNumericLimits::Max(); + + int32 MinVolIndex = 0; + for (int32 k = 1; k < 4; ++k) + { + if (Volumes[k] < Volumes[MinVolIndex]) + { + MinVolIndex = k; + } + } + + if (Volumes[MinVolIndex] < TNumericLimits::Max()) + { + GeometryLock.Lock(); + switch (MinVolIndex) + { + case 0: + ShapeSetOut.Boxes.Add(FBoxShape3d(AlignedBox)); + break; + case 1: + ShapeSetOut.Boxes.Add(FBoxShape3d(FitResult.Box)); + break; + case 2: + ShapeSetOut.Spheres.Add(FSphereShape3d(FitResult.Sphere)); + break; + case 3: + ShapeSetOut.Capsules.Add(FCapsuleShape3d(FitResult.Capsule)); + break; + } + GeometryLock.Unlock(); + } + }); +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/ShapeDetection3.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/ShapeDetection3.cpp new file mode 100644 index 000000000000..d7fef684f1b6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/ShapeDetection3.cpp @@ -0,0 +1,260 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ShapeApproximation/ShapeDetection3.h" +#include "Sampling/VectorSetAnalysis.h" +#include "DynamicMesh3.h" +#include "MeshQueries.h" +#include "FitCapsule3.h" + + +bool UE::Geometry::IsSphereMesh(const FDynamicMesh3& Mesh, FSphere3d& SphereOut, double RelativeDeviationTol) +{ + // assume that we aren't going to count it as a sphere unless it has 4 slices/sections, which means at least 10 vertices + if (Mesh.VertexCount() < 10) + { + return false; + } + + // compute bounding box and centroid + FAxisAlignedBox3d Bounds = FAxisAlignedBox3d::Empty(); + FVector3d Centroid = FVector3d::Zero(); + for (FVector3d Position : Mesh.VerticesItr()) + { + Centroid += Position; + Bounds.Contain(Position); + } + Centroid *= 1.0 / (double)Mesh.VertexCount(); + SphereOut.Center = Centroid; + + // Early out if bbox is not sufficiently cubical. + // Need to more permissive here for low-poly meshes because depending on orientation the + // AABB can deviate quite a bit. This threshold has been tested down to 4 verts per sphere slice/span + // (3 makes a diamond which is arguably no longer a sphere) + double MaxBoxSkewRatio = (Mesh.VertexCount() < 50) ? 1.25 : 1.1; + if (Bounds.MinDim() <= 0 || (Bounds.MaxDim() / Bounds.MinDim()) > MaxBoxSkewRatio) + { + return false; + } + + // incrementally improve sphere fit for a few iterations + double UseFitTolerance = FMathf::ZeroTolerance; + for (int32 Iteration = 0; Iteration < 5; ++Iteration) + { + FVector3d PrevCenter = SphereOut.Center; + + double AvgLen = 0; + FVector3d AvgLenDeriv = FVector3d::Zero(); // gradient of AvgLen wrt sphere center + for (FVector3d Position : Mesh.VerticesItr()) + { + FVector3d Delta = Position - SphereOut.Center; + double DeltaLen = Delta.Length(); + if (DeltaLen > 0) + { + AvgLen += DeltaLen; + AvgLenDeriv -= (1.0 / DeltaLen) * Delta; + } + } + AvgLen *= 1.0 / (double)Mesh.VertexCount(); + AvgLenDeriv *= 1.0 / (double)Mesh.VertexCount(); + + SphereOut.Center = Centroid + AvgLen * AvgLenDeriv; + SphereOut.Radius = AvgLen; + + if (SphereOut.Center.DistanceSquared(PrevCenter) < UseFitTolerance) + { + break; + } + } + + // We need to make sure this is actually a sphere. Cannot rely on vertices because + // they may all actually lie on a sphere. However if this a sphere, each edge should be + // a chord of a circle with the sphere radius. So compare deviation from sphere radius + // at the center of each mesh edge, with the analytic chord height, or "saggita", + // computed with formula sagitta = r - sqrt(r*r - l*l), where l = chordlen/2 + double UseRadius = SphereOut.Radius; + double DeviationTol = 2.0 * UseRadius * RelativeDeviationTol; + for (int32 EdgeID : Mesh.EdgeIndicesItr()) + { + FVector3d A, B; + Mesh.GetEdgeV(EdgeID, A, B); + + double HalfChordLen = A.Distance(B) * 0.5; + double MaxChordHeight = UseRadius - FMathd::Sqrt(UseRadius*UseRadius - HalfChordLen*HalfChordLen); // "sagitta" height + + double MidpointSignedDist = SphereOut.SignedDistance( (A+B)*0.5 ); + if (FMathd::Abs(MidpointSignedDist) > (MaxChordHeight + DeviationTol) ) + { + return false; + } + } + + return true; +} + + + +bool UE::Geometry::IsBoxMesh(const FDynamicMesh3& Mesh, FOrientedBox3d& BoxOut, double AngleToleranceDeg) +{ + // minimal box has at least 6 vertices + if (Mesh.VertexCount() < 6) + { + return false; + } + + double PerpDotTolerance = FMathd::Cos((90.0 - AngleToleranceDeg) * FMathd::DegToRad); + double ParallelDotTolerance = FMathd::Cos(AngleToleranceDeg * FMathd::DegToRad); + + // test a subset of internal mesh edges to see if we can find faces with opening angles + // that are not ~0 or ~90 degrees, if so this is not a box. This will immediately reject + // most non-box meshes without having to do expensive normal clustering first + int32 NumEdges = Mesh.EdgeCount(); + int32 Step = FMathd::Max(Mesh.EdgeCount() / 10, 2); + for (int32 k = 0; k < NumEdges; k += Step) + { + if (Mesh.IsEdge(k)) + { + FIndex2i EdgeT = Mesh.GetEdgeT(k); + if (EdgeT.B != FDynamicMesh3::InvalidID) + { + double Dot = Mesh.GetTriNormal(EdgeT.A).Dot(Mesh.GetTriNormal(EdgeT.B)); + Dot = FMathd::Abs(Dot); + if (Dot > PerpDotTolerance && Dot < ParallelDotTolerance) + { + return false; + } + } + } + } + + // cluster normals + FVectorSetAnalysis3d Vectors; + Vectors.Initialize(Mesh.TriangleIndicesItr(), + [&](int32 TriangleID) { return Mesh.GetTriNormal(TriangleID); }, + Mesh.TriangleCount(), true); + + // A box should have precisely 6 normals, in 3 parallel pairs + Vectors.GreedyClusterVectors(AngleToleranceDeg); + if (Vectors.NumClusters() != 6) + { + return false; + } + + // Check that each of those 6 normals is either perpendicular or parallel-with-reversed-sign from all the others + // Simultaneously find the 3 unique directions/axes + FIndex3i UniqueAxes(-1, -1, -1); + int32 UniqueCount = 0; + bool bDone[6] = { false,false,false,false,false,false }; + for (int32 k = 0; k < 6; ++k) + { + if (bDone[k]) continue; + + FVector3d Normal0 = Vectors.ClusterVectors[k]; + int32 ParallelPair = -1; + + for (int32 j = k + 1; j < 6; ++j) + { + double Dot = Normal0.Dot(Vectors.ClusterVectors[j]); + if (FMathd::Abs(Dot) > PerpDotTolerance) + { + if (Dot > -ParallelDotTolerance) // if dot is not zero, it needs to be -1 + { + return false; + } + ParallelPair = j; + bDone[j] = true; + break; + } + } + + // accumulate unique axis if we haven't seen this one and we haven't found 3 already + if (UniqueCount < 3) + { + int32 UniqueIdx = (ParallelPair == -1) ? k : FMathd::Min(k, ParallelPair); + UniqueAxes[UniqueCount++] = UniqueIdx; + } + } + + // if we found the 3 unique axes, it's a box and we know it's orientation, so just fit minimal + // container aligned to box axes, and shift center point to center of that oriented-AABB + if (UniqueCount == 3) + { + // would be nice to cycle these so that X = most-aligned-with-X, etc, or longest? + FVector3d X = Vectors.ClusterVectors[UniqueAxes[0]]; + FVector3d Y = Vectors.ClusterVectors[UniqueAxes[1]]; + FVector3d Z = Vectors.ClusterVectors[UniqueAxes[2]]; + // compute AABB in the frame of the box + FQuaterniond Rotation(FMatrix3d(X, Y, Z, false)); + FMatrix3d UnorientRotation = Rotation.Inverse().ToRotationMatrix(); + FAxisAlignedBox3d FitBox = FAxisAlignedBox3d::Empty(); + for (FVector3d VtxPos : Mesh.VerticesItr()) + { + VtxPos = UnorientRotation * VtxPos; + FitBox.Contain(VtxPos); + } + // recenter output OBB to the center of oriented AABB + FVector3d Extents = FitBox.Extents(); + FVector3d Center = Rotation * FitBox.Center(); + BoxOut = FOrientedBox3d( FFrame3d(Center, Rotation), Extents); + return true; + } + + return false; +} + + + +bool UE::Geometry::IsCapsuleMesh(const FDynamicMesh3& Mesh, FCapsule3d& CapsuleOut, double RelativeDeviationTol) +{ + // minimal 4-slice capsule has at least 10 vertices + if (Mesh.VertexCount() < 10) + { + return false; + } + // any more early-out tests we can do here? Not immediately obvious... + + // fit a capsule using TFitCapsule3. If mesh is not compact this (currently) requires linearize vertices, unfortunately + bool bFitCapsule = false; + if (Mesh.IsCompactV()) + { + bFitCapsule = TFitCapsule3::Solve(Mesh.VertexCount(), + [&](int32 Index) { return Mesh.GetVertex(Index); }, CapsuleOut); + } + else + { + TArray LinearVertices; + LinearVertices.Reserve(Mesh.VertexCount()); + for (FVector3d Position : Mesh.VerticesItr()) + { + LinearVertices.Add(Position); + } + bFitCapsule = TFitCapsule3::Solve(LinearVertices.Num(), + [&](int32 Index) { return LinearVertices[Index]; }, CapsuleOut); + } + if (!bFitCapsule) + { + return false; + } + + // See IsSphereMesh() for explanation of logic here, essentially we are checking that chordal deviation + // along edges is within tolerance. This works because endcaps are spheres and so sphere test applies, + // and along middle of capsule the deviation should be zero, but this is not currently measured. + // If false positives occur due to this, can check it by computing segment parameter, in t=[0,1] range distance should be epsilon-ish + double UseRadius = CapsuleOut.Radius; + double DeviationTol = 2.0 * UseRadius * RelativeDeviationTol; + for (int32 EdgeID : Mesh.EdgeIndicesItr()) + { + FVector3d A, B; + Mesh.GetEdgeV(EdgeID, A, B); + + double HalfChordLen = A.Distance(B) * 0.5; + double MaxChordHeight = UseRadius - FMathd::Sqrt(UseRadius * UseRadius - HalfChordLen * HalfChordLen); // "sagitta" height + + double MidpointSignedDist = CapsuleOut.SignedDistance( (A + B)*0.5 ); + if (FMathd::Abs(MidpointSignedDist) > (MaxChordHeight + DeviationTol)) + { + return false; + } + } + + return true; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/SimpleShapeSet3.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/SimpleShapeSet3.cpp new file mode 100644 index 000000000000..a2a6ba69966b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/SimpleShapeSet3.cpp @@ -0,0 +1,346 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ShapeApproximation/SimpleShapeSet3.h" + +#include "MeshQueries.h" +#include "Intersection/ContainmentQueries3.h" + + + +// FSimpleShapeElementKey identifies an element of a FSimpleShapeSet3d, via the Type +// of that element, and the Index into the per-element-type arrays. +struct FSimpleShapeElementKey +{ + // type of element + ESimpleShapeType Type; + // index of this element in per-type element lists inside a FSimpleShapeSet3d + int32 Index; + + // volume of the element, used for sorting/etc + double Volume; + + FSimpleShapeElementKey() = default; + + FSimpleShapeElementKey(ESimpleShapeType TypeIn, int32 IndexIn, double VolumeIn) + : Type(TypeIn), Index(IndexIn), Volume(VolumeIn) + {} +}; + + + +static void FilterContained(const FSimpleShapeSet3d& Geometry, const FSphereShape3d& Sphere, const TArray& Elements, int32 k, TArray& RemovedInOut) +{ + int32 N = Elements.Num(); + for (int32 j = k + 1; j < N; ++j) + { + if (RemovedInOut[j] == false) + { + bool bContained = false; + int32 ElemIdx = Elements[j].Index; + if (Elements[j].Type == ESimpleShapeType::Sphere) + { + bContained = UE::Geometry::IsInside(Sphere.Sphere, Geometry.Spheres[ElemIdx].Sphere); + } + else if (Elements[j].Type == ESimpleShapeType::Box) + { + bContained = UE::Geometry::IsInside(Sphere.Sphere, Geometry.Boxes[ElemIdx].Box); + } + else if (Elements[j].Type == ESimpleShapeType::Capsule) + { + bContained = UE::Geometry::IsInside(Sphere.Sphere, Geometry.Capsules[ElemIdx].Capsule); + } + else if (Elements[j].Type == ESimpleShapeType::Convex) + { + bContained = UE::Geometry::IsInside(Sphere.Sphere, Geometry.Convexes[ElemIdx].Mesh.VerticesItr()); + } + else + { + ensure(false); // not implemented yet? + } + + if (bContained) + { + RemovedInOut[j] = true; + } + } + } +} + + + + +static void FilterContained(const FSimpleShapeSet3d& Geometry, const FCapsuleShape3d& Capsule, const TArray& Elements, int32 k, TArray& RemovedInOut) +{ + int32 N = Elements.Num(); + for (int32 j = k + 1; j < N; ++j) + { + if (RemovedInOut[j] == false) + { + bool bContained = false; + int32 ElemIdx = Elements[j].Index; + if (Elements[j].Type == ESimpleShapeType::Sphere) + { + bContained = UE::Geometry::IsInside(Capsule.Capsule, Geometry.Spheres[ElemIdx].Sphere); + } + else if (Elements[j].Type == ESimpleShapeType::Box) + { + bContained = UE::Geometry::IsInside(Capsule.Capsule, Geometry.Boxes[ElemIdx].Box); + } + else if (Elements[j].Type == ESimpleShapeType::Capsule) + { + bContained = UE::Geometry::IsInside(Capsule.Capsule, Geometry.Capsules[ElemIdx].Capsule); + } + else if (Elements[j].Type == ESimpleShapeType::Convex) + { + bContained = UE::Geometry::IsInside(Capsule.Capsule, Geometry.Convexes[ElemIdx].Mesh.VerticesItr()); + } + else + { + ensure(false); + } + + if (bContained) + { + RemovedInOut[j] = true; + } + } + } +} + + + + +static void FilterContained(const FSimpleShapeSet3d& Geometry, const FBoxShape3d& Box, const TArray& Elements, int32 k, TArray& RemovedInOut) +{ + int32 N = Elements.Num(); + for (int32 j = k + 1; j < N; ++j) + { + if (RemovedInOut[j] == false) + { + bool bContained = false; + int32 ElemIdx = Elements[j].Index; + if (Elements[j].Type == ESimpleShapeType::Sphere) + { + bContained = UE::Geometry::IsInside(Box.Box, Geometry.Spheres[ElemIdx].Sphere); + } + else if (Elements[j].Type == ESimpleShapeType::Box) + { + bContained = UE::Geometry::IsInside(Box.Box, Geometry.Boxes[ElemIdx].Box); + } + else if (Elements[j].Type == ESimpleShapeType::Capsule) + { + bContained = UE::Geometry::IsInside(Box.Box, Geometry.Capsules[ElemIdx].Capsule); + } + else if (Elements[j].Type == ESimpleShapeType::Convex) + { + bContained = UE::Geometry::IsInside(Box.Box, Geometry.Convexes[ElemIdx].Mesh.VerticesItr()); + } + else + { + ensure(false); + } + + if (bContained) + { + RemovedInOut[j] = true; + } + } + } +} + + + + + +static void FilterContained(const FSimpleShapeSet3d& Geometry, const FConvexShape3d& Convex, const TArray& Elements, int32 k, TArray& RemovedInOut) +{ + TArray Planes; + for (int32 tid : Convex.Mesh.TriangleIndicesItr()) + { + FVector3d Normal, Centroid; double Area; + Convex.Mesh.GetTriInfo(tid, Normal, Area, Centroid); + Planes.Add(FHalfspace3d(Normal, Centroid)); + } + + int32 N = Elements.Num(); + for (int32 j = k + 1; j < N; ++j) + { + if (RemovedInOut[j] == false) + { + bool bContained = false; + int32 ElemIdx = Elements[j].Index; + if (Elements[j].Type == ESimpleShapeType::Sphere) + { + bContained = UE::Geometry::IsInsideHull(Planes, Geometry.Spheres[ElemIdx].Sphere); + } + else if (Elements[j].Type == ESimpleShapeType::Box) + { + bContained = UE::Geometry::IsInsideHull(Planes, Geometry.Boxes[ElemIdx].Box); + } + else if (Elements[j].Type == ESimpleShapeType::Capsule) + { + bContained = UE::Geometry::IsInsideHull(Planes, Geometry.Capsules[ElemIdx].Capsule); + } + else if (Elements[j].Type == ESimpleShapeType::Convex) + { + bContained = UE::Geometry::IsInsideHull(Planes, Geometry.Convexes[ElemIdx].Mesh.VerticesItr()); + } + else + { + ensure(false); + } + + if (bContained) + { + RemovedInOut[j] = true; + } + } + } +} + + + +static void GetElementsList(FSimpleShapeSet3d& GeometrySet, TArray& Elements) +{ + for (int32 k = 0; k < GeometrySet.Spheres.Num(); ++k) + { + Elements.Add(FSimpleShapeElementKey(ESimpleShapeType::Sphere, k, GeometrySet.Spheres[k].Sphere.Volume())); + } + for (int32 k = 0; k < GeometrySet.Boxes.Num(); ++k) + { + Elements.Add(FSimpleShapeElementKey(ESimpleShapeType::Box, k, GeometrySet.Boxes[k].Box.Volume())); + } + for (int32 k = 0; k < GeometrySet.Capsules.Num(); ++k) + { + Elements.Add(FSimpleShapeElementKey(ESimpleShapeType::Capsule, k, GeometrySet.Capsules[k].Capsule.Volume())); + } + for (int32 k = 0; k < GeometrySet.Convexes.Num(); ++k) + { + FVector2d VolArea = TMeshQueries::GetVolumeArea(GeometrySet.Convexes[k].Mesh); + Elements.Add(FSimpleShapeElementKey(ESimpleShapeType::Convex, k, VolArea.X)); + } +} + +static void GetElementsSortedByDecreasing(FSimpleShapeSet3d& GeometrySet, TArray& Elements) +{ + GetElementsList(GeometrySet, Elements); + // sort by decreasing volume + Elements.Sort([](const FSimpleShapeElementKey& A, const FSimpleShapeElementKey& B) { return A.Volume > B.Volume; }); +} + + +void FSimpleShapeSet3d::RemoveContainedGeometry() +{ + TArray Elements; + GetElementsSortedByDecreasing(*this, Elements); + + int32 N = Elements.Num(); + TArray Removed; + Removed.Init(false, N); + + // remove contained elements + for (int32 k = 0; k < N; ++k) + { + if (Removed[k]) continue; + + ESimpleShapeType ElemType = Elements[k].Type; + int32 ElemIdx = (int32)Elements[k].Index; + + if (ElemType == ESimpleShapeType::Sphere) + { + FilterContained(*this, Spheres[ElemIdx], Elements, k, Removed); + } + else if (ElemType == ESimpleShapeType::Capsule) + { + FilterContained(*this, Capsules[ElemIdx], Elements, k, Removed); + } + else if (ElemType == ESimpleShapeType::Box) + { + FilterContained(*this, Boxes[ElemIdx], Elements, k, Removed); + } + else if (ElemType == ESimpleShapeType::Convex) + { + FilterContained(*this, Convexes[ElemIdx], Elements, k, Removed); + } + else + { + ensure(false); + } + } + + + // build a new shape set + FSimpleShapeSet3d NewSet; + for (int32 k = 0; k < N; ++k) + { + if (Removed[k] == false) + { + ESimpleShapeType ElemType = Elements[k].Type; + int32 ElemIdx = Elements[k].Index; + + switch (ElemType) + { + case ESimpleShapeType::Sphere: + NewSet.Spheres.Add(Spheres[ElemIdx]); + break; + case ESimpleShapeType::Box: + NewSet.Boxes.Add(Boxes[ElemIdx]); + break; + case ESimpleShapeType::Capsule: + NewSet.Capsules.Add(Capsules[ElemIdx]); + break; + case ESimpleShapeType::Convex: + NewSet.Convexes.Add(Convexes[ElemIdx]); // todo movetemp here... + break; + } + } + } + + // replace our lists with new set + Spheres = MoveTemp(NewSet.Spheres); + Boxes = MoveTemp(NewSet.Boxes); + Capsules = MoveTemp(NewSet.Capsules); + Convexes = MoveTemp(NewSet.Convexes); +} + + + + + +void FSimpleShapeSet3d::FilterByVolume(int32 MaximumCount) +{ + TArray Elements; + GetElementsSortedByDecreasing(*this, Elements); + if (Elements.Num() <= MaximumCount) + { + return; + } + + FSimpleShapeSet3d NewSet; + for (int32 k = 0; k < MaximumCount; ++k) + { + ESimpleShapeType ElemType = Elements[k].Type; + int32 ElemIdx = Elements[k].Index; + + switch (ElemType) + { + case ESimpleShapeType::Sphere: + NewSet.Spheres.Add(Spheres[ElemIdx]); + break; + case ESimpleShapeType::Box: + NewSet.Boxes.Add(Boxes[ElemIdx]); + break; + case ESimpleShapeType::Capsule: + NewSet.Capsules.Add(Capsules[ElemIdx]); + break; + case ESimpleShapeType::Convex: + NewSet.Convexes.Add(Convexes[ElemIdx]); // todo movetemp here... + break; + } + } + + Spheres = MoveTemp(NewSet.Spheres); + Boxes = MoveTemp(NewSet.Boxes); + Capsules = MoveTemp(NewSet.Capsules); + Convexes = MoveTemp(NewSet.Convexes); +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/ConstrainedMeshDeformer.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/ConstrainedMeshDeformer.cpp index 4d0fb445c207..98c1356107ab 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/ConstrainedMeshDeformer.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/ConstrainedMeshDeformer.cpp @@ -10,3 +10,9 @@ TUniquePtr UE::MeshDeformation::ConstructCo return Deformer; } + +TUniquePtr UE::MeshDeformation::ConstructSoftMeshDeformer(const FDynamicMesh3& DynamicMesh) +{ + TUniquePtr Deformer(new FSoftMeshDeformer(DynamicMesh)); + return Deformer; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformationSolver.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformationSolver.cpp index f7fa5ee7da93..43ff2677d2d7 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformationSolver.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformationSolver.cpp @@ -1,8 +1,8 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "ConstrainedMeshDeformationSolver.h" +#include "Solvers/LaplacianMatrixAssembly.h" #include "LaplacianOperators.h" - #include "MatrixSolver.h" #include "ConstrainedPoissonSolver.h" @@ -130,8 +130,14 @@ void FConstrainedMeshDeformationSolver::AddConstraint(const int32 VtxId, const d bConstraintPositionsDirty = true; bConstraintWeightsDirty = true; + FConstraintPosition NewConstraint; + NewConstraint.ElementID = VtxId; + NewConstraint.ConstraintIndex = Index; + NewConstraint.Position = Pos; + NewConstraint.Weight = Weight; + NewConstraint.bPostFix = bPostFix; - ConstraintPositionMap.Add(TTuple(Index, FConstraintPosition(Pos, bPostFix))); + ConstraintPositionMap.Add(TTuple(Index, NewConstraint)); ConstraintWeightMap.Add(TTuple(Index, Weight)); } } @@ -152,12 +158,14 @@ bool FConstrainedMeshDeformationSolver::UpdateConstraintPosition(const int32 Vtx if (Index != FDynamicMesh3::InvalidID && Index < InternalVertexCount) { - bConstraintPositionsDirty = true; - - // Add should over-write any existing value for this key - ConstraintPositionMap.Add(TTuple(Index, FConstraintPosition(Pos, bPostFix))); - - Result = ConstraintWeightMap.Contains(VtxId); + FConstraintPosition* Found = ConstraintPositionMap.Find(Index); + if (ensure(Found != nullptr)) + { + Found->Position = Pos; + Found->bPostFix = bPostFix; + bConstraintPositionsDirty = true; + Result = true; + } } return Result; } @@ -335,3 +343,155 @@ bool FConstrainedMeshDeformationSolver::SetTolerance(double Tol) } return false; } + + + + + + + + + + + + +FSoftMeshDeformationSolver::FSoftMeshDeformationSolver(const FDynamicMesh3& DynamicMesh) +{ + EMatrixSolverType MatrixSolverType = EMatrixSolverType::LU; + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + // compute linearization so we can store constraints at linearized indices + VtxLinearization.Reset(DynamicMesh); + + const TArray& ToMeshV = VtxLinearization.ToId(); + const TArray& ToIndex = VtxLinearization.ToIndex(); + const int32 NumVerts = VtxLinearization.NumVerts(); + + FEigenSparseMatrixAssembler LaplacianAssembler(NumVerts, NumVerts); + + UE::MeshDeformation::ConstructFullCotangentLaplacian(DynamicMesh, VtxLinearization, LaplacianAssembler, + UE::MeshDeformation::ECotangentWeightMode::ClampedMagnitude, + UE::MeshDeformation::ECotangentAreaMode::VoronoiArea); + + FSparseMatrixD CotangentLaplacian; + LaplacianAssembler.ExtractResult(CotangentLaplacian); + + checkSlow(CotangentLaplacian.rows() == CotangentLaplacian.cols()); + + TUniquePtr LTLPtr(new FSparseMatrixD(CotangentLaplacian.rows(), CotangentLaplacian.cols())); + FSparseMatrixD& LTLMatrix = *(LTLPtr); + + // Construct the Biharmonic system matrix + // Note that if Laplacian was symmetric (eg if uniform/etc) then LTLMatrix = CotangentLaplacian * CotangentLaplacian + LTLMatrix = CotangentLaplacian.transpose() * CotangentLaplacian; + ConstrainedSolver.Reset(new FConstrainedSolver(LTLPtr, MatrixSolverType)); +} + +FSoftMeshDeformationSolver::~FSoftMeshDeformationSolver() +{ +} + + + +void FSoftMeshDeformationSolver::UpdateSolverConstraints() +{ + if (bConstraintWeightsDirty) + { + ConstrainedSolver->SetConstraintWeights(ConstraintMap); + bConstraintWeightsDirty = false; + } + + if (bConstraintPositionsDirty) + { + ConstrainedSolver->SetContraintPositions(ConstraintMap); + bConstraintPositionsDirty = false; + } +} + + + +void FSoftMeshDeformationSolver::AddConstraint(const int32 VtxId, const double Weight, const FVector3d& Position, const bool bPostFix) +{ + if (ensure(VtxLinearization.IsValidId(VtxId)) == false) return; + int32 Index = VtxLinearization.GetIndex(VtxId); + + FPositionConstraint NewConstraint; + NewConstraint.ElementID = VtxId; + NewConstraint.ConstraintIndex = Index; + NewConstraint.Position = Position; + NewConstraint.Weight = Weight; + NewConstraint.bPostFix = bPostFix; + + ConstraintMap.Add(Index, NewConstraint); + + bConstraintPositionsDirty = true; + bConstraintWeightsDirty = true; +} + + +bool FSoftMeshDeformationSolver::UpdateConstraintPosition(const int32 VtxId, const FVector3d& NewPosition, const bool bPostFix) +{ + if (ensure(VtxLinearization.IsValidId(VtxId)) == false) return false; + int32 Index = VtxLinearization.GetIndex(VtxId); + + FPositionConstraint* Found = ConstraintMap.Find(Index); + if (ensure(Found != nullptr)) + { + Found->Position = NewPosition; + Found->bPostFix = bPostFix; + bConstraintPositionsDirty = true; + return true; + } + return false; +} + + +bool FSoftMeshDeformationSolver::UpdateConstraintWeight(const int32 VtxId, const double NewWeight) +{ + if (ensure(VtxLinearization.IsValidId(VtxId)) == false) return false; + int32 Index = VtxLinearization.GetIndex(VtxId); + + FPositionConstraint* Found = ConstraintMap.Find(Index); + if (ensure(Found != nullptr)) + { + Found->Weight = NewWeight; + bConstraintWeightsDirty = true; + return true; + } + return false; +} + + +bool FSoftMeshDeformationSolver::IsConstrained(const int32 VtxId) const +{ + if (VtxLinearization.IsValidId(VtxId) == false) return false; + int32 Index = VtxLinearization.GetIndex(VtxId); + return ConstraintMap.Contains(Index); +} + + +void FSoftMeshDeformationSolver::ClearConstraints() +{ + ConstraintMap.Empty(); + bConstraintPositionsDirty = true; + bConstraintWeightsDirty = true; +} + + + +void FSoftMeshDeformationSolver::UpdateLaplacianScale(double UniformScale) +{ + LaplacianScale = UniformScale; +} + +bool FSoftMeshDeformationSolver::HasLaplacianScale() const +{ + return LaplacianScale != 1.0; +} + +double FSoftMeshDeformationSolver::GetLaplacianScale(int32 Index) const +{ + return LaplacianScale; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformationSolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformationSolver.h index a7e0a5e662eb..43406e150d8b 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformationSolver.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformationSolver.h @@ -18,6 +18,9 @@ class FConstrainedSolver; * FConstrainedMeshDeformationSolver is an implmentation of IConstrainedMeshSolver that solves * Mesh Deformation problems by using quadratic energy functions based on the vertex-graph Laplacians. * + * All constraints are "soft", ie they are included in the system as weighted quadratic energies + * rather than had constraints. + * * Both the Laplacian weighting scheme/type and The Sparse Matrix Solver type are configurable. */ class DYNAMICMESH_API FConstrainedMeshDeformationSolver : public UE::Solvers::IConstrainedMeshSolver @@ -110,4 +113,93 @@ protected: // Sparse Matrix that holds L^T * B where B has the boundary terms. FSparseMatrixD BoundaryOperator; +}; + + + + + + + +/** + * FSoftMeshDeformationSolver is an implmentation of IConstrainedLaplacianMeshSolver that solves + * Mesh Deformation problems by using quadratic energy functions based on the vertex-graph Laplacians. + * + * The primary difference with FConstrainedMeshDeformationSolver is that boundary vertices do not + * receive special treatment, they are included in the system and solved in the same way. As a result + * it is generally necessary to add constraints for boundary vertices. + * + * All constraints are "soft", ie they are included in the system as weighted quadratic energies + * rather than had constraints. + * + * Voronoi-Area Clamped Cotangent weights are used for the Laplacian, and an LU Sover + */ +class DYNAMICMESH_API FSoftMeshDeformationSolver : public UE::Solvers::IConstrainedLaplacianMeshSolver +{ +public: + typedef UE::Solvers::FPositionConstraint FPositionConstraint; + + FSoftMeshDeformationSolver(const FDynamicMesh3& DynamicMesh); + virtual ~FSoftMeshDeformationSolver(); + + // + // IConstrainedMeshSolver API + // + + // Add constraint associated with given vertex id. Boundary vertices will be ignored + void AddConstraint(const int32 VtxId, const double Weight, const FVector3d& Position, const bool bPostFix) override; + + // Update the position of an existing constraint. Bool return if a corresponding constraint weight exists. Boundary vertices will be ignored (and return false). + bool UpdateConstraintPosition(const int32 VtxId, const FVector3d& Position, const bool bPostFix) override; + + // The underlying solver will have to refactor the matrix if this is done. Bool return if a corresponding constraint position exists. Boundary vertices will be ignored (and return false). + bool UpdateConstraintWeight(const int32 VtxId, const double Weight) override; + + // Clear all constraints associated with this smoother + void ClearConstraints() override; + + // do not support these + void ClearConstraintWeights() override { check(false); } + void ClearConstraintPositions() override { check(false); } + + // Test if for constraint associated with given vertex id. Will return false for any boundary vert. + bool IsConstrained(const int32 VtxId) const override; + + virtual bool Deform(TArray& PositionBuffer) override { return false; } + + + // + // IConstrainedLaplacianMeshSolver API + // + + + // Update global scale applied to Laplacian vectors before solve. + virtual void UpdateLaplacianScale(double UniformScale); + + +public: + + // Sync constraints with internal solver. If in the process any internal matrix factoring is dirty, it will be rebuilt. + // Note: this is called from within the Deform() method. Only call this method if you want to trigger the matrix refactor yourself. + void UpdateSolverConstraints(); + + +protected: + + // The Key (int32) here is the vertex index not vertex ID + // making it the same as the matrix row + TMap ConstraintMap; + + bool bConstraintPositionsDirty = true; + bool bConstraintWeightsDirty = true; + + // Used to map between VtxId and vertex Index in linear vector.. + FVertexLinearization VtxLinearization; + + // Actual solver that manages the various linear algebra bits. + TUniquePtr ConstrainedSolver; + + double LaplacianScale = 1.0; + bool HasLaplacianScale() const; + double GetLaplacianScale(int32 LinearVtxIndex) const; }; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformers.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformers.cpp index bd96226ab47e..77f36daa05b1 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformers.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformers.cpp @@ -72,3 +72,100 @@ bool FConstrainedMeshDeformer::Deform(TArray& PositionBuffer) return bSuccess; } + + + + + + + + + + + +FSoftMeshDeformer::FSoftMeshDeformer(const FDynamicMesh3& DynamicMesh) + : FSoftMeshDeformationSolver(DynamicMesh) +{ + int32 NumVertices = VtxLinearization.NumVerts(); + LaplacianVectors = FSOAPositions(NumVertices); + + // The current vertex positions + + // Note: the OriginalInteriorPositions are being stored as member data + // for use if the solver is iterative. + OriginalPositions.SetZero(NumVertices); + const TArray& ToVtxId = VtxLinearization.ToId(); + for (int32 i = 0; i < NumVertices; ++i) + { + OriginalPositions.SetXYZ(i, DynamicMesh.GetVertex(ToVtxId[i])); + } + + // The biharmonic part of the constrained solver + // Biharmonic := Laplacian^{T} * Laplacian + + const auto& Biharmonic = ConstrainedSolver->Biharmonic(); + + // Compute the Laplacian Vectors + // := Biharmonic * VertexPostion + // In the case of the cotangent laplacian this can be identified as the mean curvature * normal. + for (int32 i = 0; i < 3; ++i) + { + LaplacianVectors.Array(i) = Biharmonic * OriginalPositions.Array(i); + } +} + + +bool FSoftMeshDeformer::Deform(TArray& PositionBuffer) +{ + // Update constraints. This only trigger solver rebuild if the weights were updated. + UpdateSolverConstraints(); + + // Allocate space for the result as a struct of arrays + FSOAPositions SolutionVector(VtxLinearization.NumVerts()); + + // solve linear system + bool bSuccess = false; + if (HasLaplacianScale()) + { + FSOAPositions ScaledLaplacians = LaplacianVectors; + int32 NumVectors = ScaledLaplacians.Num(); + for (int32 k = 0; k < NumVectors; ++k) + { + ScaledLaplacians.Set(k, GetLaplacianScale(k) * ScaledLaplacians.Get(k)); + } + bSuccess = ConstrainedSolver->SolveWithGuess(OriginalPositions, ScaledLaplacians, SolutionVector); + } + else + { + // NB: the original positions will only be used if the underlying solver type is iterative + bSuccess = ConstrainedSolver->SolveWithGuess(OriginalPositions, LaplacianVectors, SolutionVector); + } + + // Move any vertices to match bPostFix constraints + for (const auto& Constraint : ConstraintMap) + { + const int32 Index = Constraint.Key; + if (Constraint.Value.bPostFix) + { + SolutionVector.SetXYZ(Index, Constraint.Value.Position); + } + } + + // Allocate Position Buffer for random access writes + int32 MaxVtxId = VtxLinearization.ToId().Num(); + PositionBuffer.Empty(MaxVtxId); + PositionBuffer.AddUninitialized(MaxVtxId); + + // Export the computed positions + int32 NumVertices = VtxLinearization.NumVerts(); + const TArray& ToVtxId = VtxLinearization.ToId(); + for (int32 i = 0; i < NumVertices; ++i) + { + const int32 VtxId = ToVtxId[i]; + PositionBuffer[VtxId] = FVector3d(SolutionVector.X(i), SolutionVector.Y(i), SolutionVector.Z(i)); + } + + // the matrix solve state + return bSuccess; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformers.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformers.h index a5659d39e253..71c24f7dd3ca 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformers.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedMeshDeformers.h @@ -28,3 +28,26 @@ private: FSOAPositions LaplacianVectors; FSOAPositions OriginalInteriorPositions; }; + + + +/** + * FSoftMeshDeformer solves detail-preserving mesh deformation problems with arbitrary position constraints. + * The initial Mesh Laplacians are defined as Biharmonic * VtxPositions + * A direct solver is used, currently LU decomposition. + * Clamped Cotangent weights with Voronoi area are used. + * Boundary Vertices are *not* fixed, they are included in the system and so should have soft constraints set similar to any other vertex + */ +class DYNAMICMESH_API FSoftMeshDeformer : public FSoftMeshDeformationSolver +{ +public: + FSoftMeshDeformer(const FDynamicMesh3& DynamicMesh); + ~FSoftMeshDeformer() override {} + + bool Deform(TArray& PositionBuffer) override; + +private: + + FSOAPositions LaplacianVectors; + FSOAPositions OriginalPositions; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedPoissonSolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedPoissonSolver.h index 929323c8607a..72b68a397bbd 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedPoissonSolver.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/ConstrainedPoissonSolver.h @@ -96,6 +96,42 @@ public: UpdateSolverWithContraints(); } + + // Updates the diagonal weights matrix. + void SetConstraintWeights(const TMap& ConstraintMap) + { + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + ClearWeights(); + + std::vector MatrixTripleList; + MatrixTripleList.reserve(ConstraintMap.Num()); + + for (const auto& ConstraintPair : ConstraintMap) + { + const int32 i = ConstraintPair.Key; // row id + double Weight = ConstraintPair.Value.Weight; + + checkSlow(i < SymmetricMatrixPtr->cols()); + + // the soft constrained system uses the square of the weight. + Weight *= Weight; + + //const int32 i = ToIndex[VertId]; + MatrixTripleList.push_back(MatrixTripletT(i, i, Weight)); + + } + + // Construct matrix with weights on the diagonal for the constrained verts ( and zero everywhere else) + WeightsSqrdMatrix.setFromTriplets(MatrixTripleList.begin(), MatrixTripleList.end()); + WeightsSqrdMatrix.makeCompressed(); + + // The solver matrix will have to be updated and re-factored + UpdateSolverWithContraints(); + } + + // Updates the positional source term void SetContraintPositions(const TMap& PositionMap) { diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/FSparseMatrixD.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/FSparseMatrixD.h index e3313fd640ce..7d3e66749fdc 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/FSparseMatrixD.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/FSparseMatrixD.h @@ -5,6 +5,8 @@ #include "CoreMinimal.h" #include "Util/ElementLinearization.h" // TVector3Arrays +#include "Solvers/MatrixInterfaces.h" +#include "Solvers/LaplacianMatrixAssembly.h" // According to http://eigen.tuxfamily.org/index.php?title=Main_Page // SimplicialCholesky, AMD ordering, and constrained_cg are disabled. @@ -35,6 +37,45 @@ typedef Eigen::SparseMatrix FSparseMatrixD; + +// +// Extension of TSparseMatrixAssembler suitable for eigen sparse matrix +// +class FEigenSparseMatrixAssembler : public UE::Solvers::TSparseMatrixAssembler +{ +public: + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + TUniquePtr Matrix; + std::vector EntryTriplets; + + FEigenSparseMatrixAssembler(int32 RowsI, int32 ColsJ) + { + Matrix = MakeUnique(RowsI, ColsJ); + + ReserveEntriesFunc = [this](int32 NumElements) + { + EntryTriplets.reserve(NumElements); + }; + + AddEntryFunc = [this](int32 i, int32 j, double Value) + { + EntryTriplets.push_back(MatrixTripletT(i, j, Value)); + }; + } + + void ExtractResult(FSparseMatrixD& Result) + { + Matrix->setFromTriplets(EntryTriplets.begin(), EntryTriplets.end()); + Matrix->makeCompressed(); + + Result.swap(*Matrix); + } +}; + + + /** * A struct of arrays representation used to hold vertex positions * in three vectors that can interface with the eigen library diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/LaplacianOperators.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/LaplacianOperators.cpp index 189272e95c55..68323868b197 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/LaplacianOperators.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/LaplacianOperators.cpp @@ -24,42 +24,6 @@ THIRD_PARTY_INCLUDES_END -// -// Extension of TSparseMatrixAssembler suitable for eigen sparse matrix -// -class FEigenSparseMatrixAssembler : public UE::Solvers::TSparseMatrixAssembler -{ -public: - typedef FSparseMatrixD::Scalar ScalarT; - typedef Eigen::Triplet MatrixTripletT; - - TUniquePtr Matrix; - std::vector EntryTriplets; - - FEigenSparseMatrixAssembler(int32 RowsI, int32 ColsJ) - { - Matrix = MakeUnique(RowsI, ColsJ); - - ReserveEntriesFunc = [this](int32 NumElements) - { - EntryTriplets.reserve(NumElements); - }; - - AddEntryFunc = [this](int32 i, int32 j, double Value) - { - EntryTriplets.push_back(MatrixTripletT(i, j, Value)); - }; - } - - void ExtractResult(FSparseMatrixD& Result) - { - Matrix->setFromTriplets(EntryTriplets.begin(), EntryTriplets.end()); - Matrix->makeCompressed(); - - Result.swap(*Matrix); - } -}; - void ConstructUniformLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, FSparseMatrixD& LaplacianInterior, FSparseMatrixD& LaplacianBoundary) diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/MeshUVSolver.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/MeshUVSolver.cpp index e778ac38c403..486d4398322d 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/MeshUVSolver.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Solvers/Internal/MeshUVSolver.cpp @@ -91,8 +91,9 @@ static void ConstructNaturalConformalLaplacianSystem( int32 N = NumVerts; FSparseMatrixD AreaMatrix(2 * N, 2 * N); FMeshBoundaryLoops Loops(&DynamicMesh, true); - for (const FEdgeLoop& Loop : Loops.Loops) + for (FEdgeLoop& Loop : Loops.Loops) { + Algo::Reverse(Loop.Vertices); // reverse loop to handle UE mesh orientation int32 NumV = Loop.GetVertexCount(); for (int32 k = 0; k < NumV; ++k) { diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/GroupTopology.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/GroupTopology.h index 0ecffeef28c5..a22656e2bd73 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/GroupTopology.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/GroupTopology.h @@ -128,6 +128,12 @@ public: virtual ~FGroupTopology() {} + /** + * Keeps the structures of topology in place but points the mesh pointers to + * a new cloned mesh. If the given mesh is not cloned, the topology will no + * longer be consistent. + */ + void RetargetOnClonedMesh(const FDynamicMesh3* Mesh); /** * Build the group topology graph. diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshCurvature.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshCurvature.h index 61a5b30e0b85..c333ef439dd2 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshCurvature.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshCurvature.h @@ -43,4 +43,34 @@ namespace UE DYNAMICMESH_API double GaussianCurvature(const FDynamicMesh3& Mesh, int32 VertexIndex, TFunctionRef VertexPositionFunc); } -} \ No newline at end of file +} + + +/** + * FMeshVertexCurvatureCache calculates and stores various per-vertex Curvature types + * for a Mesh, as well as some statistics for those values. + */ +class DYNAMICMESH_API FMeshVertexCurvatureCache +{ +public: + + struct FVertexCurvature + { + double Mean = 0; + double Gaussian = 0; + double MaxPrincipal = 0; + double MinPrincipal = 0; + }; + + TArray Curvatures; + + FInterval1d MeanRange; + FInterval1d GaussianRange; + FInterval1d MaxPrincipalRange; + FInterval1d MinPrincipalRange; + + int32 Num() const { return Curvatures.Num(); } + const FVertexCurvature& operator[](int32 Index) const { return Curvatures[Index]; } + + void BuildAll(const FDynamicMesh3& Mesh); +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/GroupEdgeInserter.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/GroupEdgeInserter.h new file mode 100644 index 000000000000..802443d4497f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/GroupEdgeInserter.h @@ -0,0 +1,115 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "GroupTopology.h" + +class FProgressCancel; +class FDynamicMesh3; +class FGroupTopology; + +/** + * Used to insert group edges and group edge loops. + */ +class DYNAMICMESH_API FGroupEdgeInserter +{ +public: + + enum class EInsertionMode + { + Retriangulate, + PlaneCut + }; + + /** Parameters for an InsertEdgeLoops() call. */ + struct FEdgeLoopInsertionParams + { + /** Both of these get updated in the operation */ + FDynamicMesh3* Mesh = nullptr; + FGroupTopology* Topology = nullptr; + + /** Edge loops will be inserted perpendicular to this group edge */ + int32 GroupEdgeID = FDynamicMesh3::InvalidID; + + /** + * Inputs can be proportions in the range (0,1), or aboslute lengths. + * As the name suggests, they must already be sorted. + */ + const TArray* SortedInputLengths = nullptr; + bool bInputsAreProportions = true; + + /** + * One of the endpoints of the group edge, from which the arc lengths + * or proportions should be measured + */ + int32 StartCornerID = FDynamicMesh3::InvalidID; + + /** + * When inserting edges, this is the distance that a desired new point can be to + * use a nearby vertex rather than splitting an edge to create a new one. + */ + double VertexTolerance = KINDA_SMALL_NUMBER * 10; + + /** + * Determines how the edge is inserted: by using a cutting plane to cut existing triangles + * along the path, or deleting triangles and retriangulating. Retriangulation will keep + * geometry nice but currently loses the UV's, and it obviously breaks for non-planar groups. + */ + EInsertionMode Mode = EInsertionMode::Retriangulate; + }; + + bool InsertEdgeLoops(const FEdgeLoopInsertionParams& Params, TSet* NewEids, FProgressCancel* Progress); + + + /** Point along a group edge that is used as a start/endpoint for an inserted group edge. */ + struct FGroupEdgeSplitPoint + { + /** Either vertex ID or edge ID of the point. */ + int32 ElementID; + + /** Whether the point is an edge or vertex. */ + bool bIsVertex; + + /** + * When using a cutting plane, tangent that is used to help position the plane. For edges, + * it is just an edge vector, but for vertices, it is the average of the adjacent edge vectors + * of the group boundary. + */ + FVector3d Tangent; + + /** + * Only relevant for edges. The range (0,1) parameter that determines where the edge + * should be split (the order of endpoints is given by the mesh structure). + */ + double EdgeTValue; + }; + + /** Parameters for an InsertGroupEdge() call */ + struct FGroupEdgeInsertionParams + { + /** These are both modified in the operation */ + FDynamicMesh3* Mesh = nullptr; + FGroupTopology* Topology = nullptr; + + /** Group across which the cut is inserted. */ + int32 GroupID = FDynamicMesh3::InvalidID; + FGroupTopology::FGroupBoundary* GroupBoundary = nullptr; + + FGroupEdgeSplitPoint StartPoint; + FGroupEdgeSplitPoint EndPoint; + + /** + * When inserting edges, this is the distance that a desired new point can be to + * use a nearby vertex rather than splitting an edge to create a new one (used when + * inserting using plane cut). + */ + double VertexTolerance = KINDA_SMALL_NUMBER * 10; + + EInsertionMode Mode = EInsertionMode::Retriangulate; + }; + + bool InsertGroupEdge(FGroupEdgeInsertionParams& Params, TSet* NewEids, FProgressCancel* Progress); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/InsetMeshRegion.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/InsetMeshRegion.h index 2f5e80bb3606..98f724d1748b 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/InsetMeshRegion.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/InsetMeshRegion.h @@ -6,7 +6,7 @@ #include "VectorTypes.h" #include "GeometryTypes.h" #include "MeshRegionBoundaryLoops.h" - +#include "ProjectionTargets.h" class FDynamicMesh3; class FDynamicMeshChangeTracker; @@ -43,6 +43,22 @@ public: /** quads on the stitch loop are planar-projected and scaled by this amount */ float UVScaleFactor = 1.0f; + /** reproject positions onto input surface */ + bool bReproject = true; + + + /** update positions of any non-boundary vertices in inset regions (via laplacian solve) */ + bool bSolveRegionInteriors = true; + + /** determines how strongly laplacian solve constraints are enforced. 0 means hard constraint. Valid range [0,1] */ + float Softness = 0.0; + + /** Linear attenuation of area correction factor, valid range [0,1], 0 means ignore area correction entirely */ + float AreaCorrection = 1.0; + + /** projection target */ + FMeshProjectionTarget* ProjectionTarget; + /** If set, change tracker will be updated based on edit */ TUniquePtr ChangeTracker; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MeshConvexHull.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MeshConvexHull.h index 5b366d3155b5..a19e8ec7b153 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MeshConvexHull.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MeshConvexHull.h @@ -16,9 +16,6 @@ public: /** Input Mesh */ const FDynamicMesh3* Mesh; - /** If true, high-precision rational number types are used rather than doubles. This is more accurate but slower. */ - bool bUseExactComputation = true; - /** If true, output convex hull is simplified down to MaxTargetFaceCount */ bool bPostSimplify = false; /** Target triangle count of the output Convex Hull */ diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MeshProjectionHull.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MeshProjectionHull.h new file mode 100644 index 000000000000..3f97b3b906af --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MeshProjectionHull.h @@ -0,0 +1,56 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MathUtil.h" +#include "VectorTypes.h" +#include "GeometryTypes.h" +#include "Polygon2.h" +#include "DynamicMesh3.h" + +/** + * Calculate a Convex Hull for a Mesh by first Projecting all vertices to a plane, computing a + * 2D convex polygon that contains them, and then sweeping that 2D hull to create an extruded 3D volume. + */ +class DYNAMICMESH_API FMeshProjectionHull +{ +public: + /** Input Mesh */ + const FDynamicMesh3* Mesh; + + /** Input 3D Frame/Plane */ + FFrame3d ProjectionFrame; + + /** If true, 2D convex hull is simplified using MinEdgeLength and DeviationTolerance */ + bool bSimplifyPolygon = false; + /** Minimum Edge Length of the simplified 2D Convex Hull */ + double MinEdgeLength = 0.01; + /** Deviation Tolerance of the simplified 2D Convex Hull */ + double DeviationTolerance = 0.1; + + /** Minimum thickness of extrusion. If extrusion length is smaller than this amount, box is expanded symmetrically */ + double MinThickness = 0.0; + + + /** Calculated convex hull polygon */ + FPolygon2d ConvexHull2D; + + /** Simplified convex hull polygon. Not initialized if bSimplifyPolygon == false */ + FPolygon2d SimplifiedHull2D; + + /** Output swept-polygon convex hull */ + FDynamicMesh3 ConvexHull3D; + +public: + FMeshProjectionHull(const FDynamicMesh3* MeshIn) + { + Mesh = MeshIn; + } + + /** + * Calculate output 2D Convex Polygon and Swept-Polygon 3D Mesh for vertices of input Mesh + * @return true on success + */ + bool Compute(); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Parameterization/DynamicMeshUVEditor.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Parameterization/DynamicMeshUVEditor.h index ff345e8522e5..d0b9ad15f23d 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Parameterization/DynamicMeshUVEditor.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Parameterization/DynamicMeshUVEditor.h @@ -43,6 +43,17 @@ public: */ void CreateUVLayer(int32 UVLayerIndex); + /** + * Create new UV island for each Triangle, by planar projection onto plane of Triangle. No transforms/etc are applied. + */ + void SetPerTriangleUVs(const TArray& Triangles, double ScaleFactor = 1.0, FUVEditResult* Result = nullptr); + + /** + * Create new UV island for given Triangles, and set UVs by planar projection to ProjectionFrame. No transforms/etc are applied. + */ + void SetPerTriangleUVs(double ScaleFactor = 1.0, FUVEditResult* Result = nullptr); + + /** * Create new UV island for given Triangles, and set UVs by planar projection to ProjectionFrame. No transforms/etc are applied. */ diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Parameterization/MeshUVPacking.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Parameterization/MeshUVPacking.h new file mode 100644 index 000000000000..e6ac95153deb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Parameterization/MeshUVPacking.h @@ -0,0 +1,46 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DynamicMesh3.h" +#include "DynamicMeshAttributeSet.h" + + +/** + * FDynamicMeshUVPacker implements various strategies for packing UV islands in a + * UV Overlay. The island topology and UV unwraps must already be created, this + * class simply scales/rotates/translates the islands to fit. + */ +class DYNAMICMESH_API FDynamicMeshUVPacker +{ +public: + /** The UV Overlay we will be repacking */ + FDynamicMeshUVOverlay* UVOverlay = nullptr; + + /** Resolution of the target texture. This is used to convert pixel gutter/border thickness to UV space */ + int32 TextureResolution = 512; + + /** Thickness of gutter/border in pixel dimensions. Not supported by all packing methods */ + float GutterSize = 1.0; + + /** If true, islands can be flipped in addition to rotate/translate/scale */ + bool bAllowFlips = false; + + explicit FDynamicMeshUVPacker(FDynamicMeshUVOverlay* UVOverlay); + + + /** + * Standard UE4 UV layout, similar to that used for Lightmap UVs. + * All UV islands are packed into standard positive-unit-square. + * Only supports single-pixel border size. + */ + bool StandardPack(); + + /** + * Uniformly scale all UV islands so that the largest fits in positive-unit-square, + * and translate each islands separately so that it's bbox-min is at the origin. + * So the islands are "stacked" and all fit in the unit box. + */ + bool StackPack(); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshCurvatureMapBaker.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshCurvatureMapBaker.h new file mode 100644 index 000000000000..1fa5ddd9e0a7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshCurvatureMapBaker.h @@ -0,0 +1,86 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Sampling/MeshImageBaker.h" +#include "Sampling/MeshImageBakingCache.h" +#include "Image/ImageBuilder.h" + +class FMeshVertexCurvatureCache; + +class DYNAMICMESH_API FMeshCurvatureMapBaker : public FMeshImageBaker +{ +public: + virtual ~FMeshCurvatureMapBaker() {} + + // + // Options + // + + enum class ECurvatureType + { + Mean = 0, + Gaussian = 1, + MaxPrincipal = 2, + MinPrincipal = 3 + }; + ECurvatureType UseCurvatureType = ECurvatureType::Mean; + + enum class EColorMode + { + BlackGrayWhite = 0, + RedGreenBlue = 1, + RedBlue = 2 + }; + EColorMode UseColorMode = EColorMode::RedGreenBlue; + + enum class EClampMode + { + FullRange = 0, + Positive = 1, + Negative = 2 + }; + EClampMode UseClampMode = EClampMode::FullRange; + + double RangeScale = 1.0; + double MinRangeScale = 0.0; + + double BlurRadius = 0.0; + + + // + // Required input data, can be provided, will be computed otherwise + // + + TSharedPtr Curvatures; + + + // + // Compute functions + // + + /** Calculate bake result */ + virtual void Bake() override; + + /** populate Curvatures member if valid data has not been provided */ + void CacheDetailCurvatures(const FDynamicMesh3* DetailMesh); + + // + // Output + // + + const TUniquePtr>& GetResult() const { return ResultBuilder; } + + TUniquePtr> TakeResult() { return MoveTemp(ResultBuilder); } + + +protected: + TUniquePtr> ResultBuilder; + + void Bake_Single(); + + void Bake_Multi(); + + + void GetColorMapRange(FVector3f& NegativeColor, FVector3f& ZeroColor, FVector3f& PositiveColor); +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshImageBaker.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshImageBaker.h new file mode 100644 index 000000000000..3c18e410f8f4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshImageBaker.h @@ -0,0 +1,31 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Sampling/MeshImageBakingCache.h" + + +class DYNAMICMESH_API FMeshImageBaker +{ +public: + virtual ~FMeshImageBaker() {} + + void SetCache(const FMeshImageBakingCache* CacheIn) + { + ImageBakeCache = CacheIn; + } + + const FMeshImageBakingCache* GetCache() const + { + return ImageBakeCache; + } + + + virtual void Bake() = 0; + + +protected: + + const FMeshImageBakingCache* ImageBakeCache; + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshImageBakingCache.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshImageBakingCache.h new file mode 100644 index 000000000000..08c8b3ce06e5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshImageBakingCache.h @@ -0,0 +1,76 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DynamicMesh3.h" +#include "DynamicMeshAttributeSet.h" +#include "DynamicMeshAABBTree3.h" +#include "Sampling/MeshSurfaceSampler.h" +#include "Spatial/DenseGrid2.h" +#include "Image/ImageDimensions.h" +#include "Image/ImageOccupancyMap.h" + +class DYNAMICMESH_API FMeshImageBakingCache +{ +public: + + void SetDetailMesh(const FDynamicMesh3* Mesh, const FDynamicMeshAABBTree3* Spatial); + void SetBakeTargetMesh(const FDynamicMesh3* Mesh); + + void SetDimensions(FImageDimensions Dimensions); + void SetUVLayer(int32 UVLayer); + + + FImageDimensions GetDimensions() const { return Dimensions; } + int32 GetUVLayer() const { return UVLayer; } + + const FDynamicMesh3* GetBakeTargetMesh() const { return TargetMesh; } + const FDynamicMeshUVOverlay* GetBakeTargetUVs() const; + const FDynamicMeshNormalOverlay* GetBakeTargetNormals() const; + + const FDynamicMesh3* GetDetailMesh() const { return DetailMesh; } + const FDynamicMeshAABBTree3* GetDetailSpatial() const { return DetailSpatial; } + const FDynamicMeshNormalOverlay* GetDetailNormals() const; + + + bool IsCacheValid() const { return bOccupancyValid && bSamplesValid; } + + bool ValidateCache(); + + + struct FCorrespondenceSample + { + FMeshUVSampleInfo BaseSample; + FVector3d BaseNormal; + + int32 DetailTriID; + FVector3d DetailBaryCoords; + }; + + + void EvaluateSamples(TFunctionRef SampleFunction, + bool bParallel = true) const; + + const FImageOccupancyMap* GetOccupancyMap() const; + + +protected: + const FDynamicMesh3* DetailMesh = nullptr; + const FDynamicMeshAABBTree3* DetailSpatial = nullptr; + const FDynamicMesh3* TargetMesh = nullptr; + + FImageDimensions Dimensions; + int32 UVLayer; + + + TDenseGrid2 SampleMap; + bool bSamplesValid = false; + void InvalidateSamples(); + + + TUniquePtr OccupancyMap; + bool bOccupancyValid = false; + void InvalidateOccupancy(); + +}; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshNormalMapBaker.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshNormalMapBaker.h new file mode 100644 index 000000000000..137ff715016f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshNormalMapBaker.h @@ -0,0 +1,41 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + + +#include "Sampling/MeshImageBaker.h" +#include "Sampling/MeshImageBakingCache.h" +#include "Image/ImageBuilder.h" +#include "MeshTangents.h" + + +class DYNAMICMESH_API FMeshNormalMapBaker : public FMeshImageBaker +{ +public: + virtual ~FMeshNormalMapBaker() {} + + // + // Required input data + // + + const TMeshTangents* BaseMeshTangents; + + + // + // Compute functions + // + + virtual void Bake() override; + + // + // Output + // + + const TUniquePtr>& GetResult() const { return NormalsBuilder; } + + TUniquePtr> TakeResult() { return MoveTemp(NormalsBuilder); } + +protected: + TUniquePtr> NormalsBuilder; + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshOcclusionMapBaker.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshOcclusionMapBaker.h new file mode 100644 index 000000000000..9eabf036cd59 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshOcclusionMapBaker.h @@ -0,0 +1,44 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + + +#include "Sampling/MeshImageBaker.h" +#include "Sampling/MeshImageBakingCache.h" +#include "Image/ImageBuilder.h" + + +class DYNAMICMESH_API FMeshOcclusionMapBaker : public FMeshImageBaker +{ +public: + virtual ~FMeshOcclusionMapBaker() {} + + // + // Options + // + + int32 NumOcclusionRays = 32; + double MaxDistance = TNumericLimits::Max(); + double BiasAngleDeg = 15.0; + + double BlurRadius = 0.0; + + + // + // Compute functions + // + + virtual void Bake() override; + + // + // Output + // + + const TUniquePtr>& GetResult() const { return OcclusionBuilder; } + + TUniquePtr> TakeResult() { return MoveTemp(OcclusionBuilder); } + +protected: + TUniquePtr> OcclusionBuilder; + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshPropertyMapBaker.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshPropertyMapBaker.h new file mode 100644 index 000000000000..b94010286c3f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshPropertyMapBaker.h @@ -0,0 +1,47 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + + +#include "Sampling/MeshImageBaker.h" +#include "Sampling/MeshImageBakingCache.h" +#include "Image/ImageBuilder.h" + + +enum class EMeshPropertyBakeType +{ + Position = 1, + Normal = 2, + FacetNormal = 3, + UVPosition = 4 +}; + + +class DYNAMICMESH_API FMeshPropertyMapBaker : public FMeshImageBaker +{ +public: + virtual ~FMeshPropertyMapBaker() {} + + // + // Options + // + + EMeshPropertyBakeType Property = EMeshPropertyBakeType::Normal; + + // + // Compute functions + // + + virtual void Bake() override; + + // + // Output + // + + const TUniquePtr>& GetResult() const { return ResultBuilder; } + + TUniquePtr> TakeResult() { return MoveTemp(ResultBuilder); } + +protected: + TUniquePtr> ResultBuilder; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshResampleImageBaker.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshResampleImageBaker.h new file mode 100644 index 000000000000..243c65351682 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Sampling/MeshResampleImageBaker.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + + +#include "Sampling/MeshImageBaker.h" +#include "Sampling/MeshImageBakingCache.h" +#include "Image/ImageBuilder.h" + + +class DYNAMICMESH_API FMeshResampleImageBaker : public FMeshImageBaker +{ +public: + virtual ~FMeshResampleImageBaker() {} + + // + // Required input data + // + + TFunction SampleFunction = [](FVector2d Position) { return FVector4f::Zero(); }; + const FDynamicMeshUVOverlay* DetailUVOverlay = nullptr; + + // + // Compute functions + // + + virtual void Bake() override; + + // + // Output + // + + const TUniquePtr>& GetResult() const { return ResultBuilder; } + + TUniquePtr> TakeResult() { return MoveTemp(ResultBuilder); } + +protected: + TUniquePtr> ResultBuilder; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ShapeApproximation/MeshSimpleShapeApproximation.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ShapeApproximation/MeshSimpleShapeApproximation.h new file mode 100644 index 000000000000..44d051f5f3d6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ShapeApproximation/MeshSimpleShapeApproximation.h @@ -0,0 +1,165 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" +#include "ShapeApproximation/SimpleShapeSet3.h" + + +/** + * EDetectedSimpleShapeType is used to identify auto-detected simple shapes for a mesh/etc + */ +enum class EDetectedSimpleShapeType +{ + /** Object is not a simple shape */ + None = 0, + /** Object has been identified as a sphere */ + Sphere = 2, + /** Object has been identified as a box */ + Box = 4, + /** Object has been identified as a capsule */ + Capsule = 8, + /** Object has been identified as a Convex */ + Convex = 16 +}; + + + +/** + * FMeshSimpleShapeApproximation can calculate various "simple" shape approximations for a set of meshes, + * by fitting various primitives/hulls/etc to each mesh. The assumption is that the input mesh(es) are + * already partitioned into pieces. + * + * There are various Generate_X() functions which apply different strategies, generally to fit a containing + * simple shape or hull to the mesh. However in addition to these explicit strategies, input meshes that + * are very close to approximations of spheres/boxes/capsules (ie basically meshed versions of these primitives) + * can be identified and used directly, skipping the fitting process. + * + */ +class DYNAMICMESH_API FMeshSimpleShapeApproximation +{ +public: + + // + // configuration parameters + // + + /** Should spheres be auto-detected */ + bool bDetectSpheres = true; + /** Should boxes be auto-detected */ + bool bDetectBoxes = true; + /** Should capsules be auto-detected */ + bool bDetectCapsules = true; + /** Should convex be auto-detected */ + bool bDetectConvexes = true; + + /** minimal dimension of fit shapes, eg thickness/radius/etc (currently only enforced in certain cases) */ + double MinDimension = 0.0; + + /** should hulls be simplified as a post-process */ + bool bSimplifyHulls = true; + /** target number of triangles when simplifying 3D convex hulls */ + int32 HullTargetFaceCount = 50; + /** simplification tolerance when simplifying 2D convex hulls, eg for swept/projected hulls */ + double HullSimplifyTolerance = 1.0; + + // + // setup/initialization + // + + + /** + * Initialize internal mesh sets. This also detects/caches the precise simple shape fits controlled + * by bDetectSpheres/etc above, so those cannot be modified without calling InitializeSourceMeshes() again. + * The TSharedPtrs are stored, rather than making a copy of the input meshes + * @param bIsParallelSafe if true + */ + void InitializeSourceMeshes(const TArray& InputMeshSet); + + + // + // approximation generators + // + + /** + * Fit containing axis-aligned boxes to each input mesh and store in ShapeSetOut + */ + void Generate_AlignedBoxes(FSimpleShapeSet3d& ShapeSetOut); + + /** + * Fit containing minimal-volume oriented boxes to each input mesh and store in ShapeSetOut + */ + void Generate_OrientedBoxes(FSimpleShapeSet3d& ShapeSetOut); + + /** + * Fit containing minimal-volume spheres to each input mesh and store in ShapeSetOut + */ + void Generate_MinimalSpheres(FSimpleShapeSet3d& ShapeSetOut); + + /** + * Fit containing approximate-minimum-volume capsules to each input mesh and store in ShapeSetOut + * @warning the capsule is fit by first fitting a line to the vertices, and then containing the points, so the fit can deviate quite a bit from a truly "minimal" capsule + */ + void Generate_Capsules(FSimpleShapeSet3d& ShapeSetOut); + + /** + * Calculate 3D Convex Hulls for each input mesh and store in ShapeSetOut. + * Each convex hull is stored as a triangle mesh, and optionally simplified if bSimplifyHulls=true + */ + void Generate_ConvexHulls(FSimpleShapeSet3d& ShapeSetOut); + + + /** Type/Mode for deciding 3D axis to use in Generate_ProjectedHulls() */ + enum class EProjectedHullAxisMode + { + /** Use Unit X axis */ + X = 0, + /** Use Unit Y axis */ + Y = 1, + /** Use Unit Z axis */ + Z = 2, + /** Use X/Y/Z axis with smallest axis-aligned-bounding-box dimension */ + SmallestBoxDimension = 3, + /** Compute projected hull for each of X/Y/Z axes and use the one that has the smallest volume */ + SmallestVolume = 4 + }; + + /** + * Calculate Projected Convex Hulls for each input mesh and store in ShapeSetOut. + * A Projected Hull is computed by first projecting all the mesh vertices to a plane, computing a 2D convex hull polygon, + * and then sweeping the polygon in 3D to contain all the mesh vertices. The 2D convex hull polygons + * are optionally simplified if bSimplifyHulls=true. + * @param AxisMode which axis to use for the planar projection + */ + void Generate_ProjectedHulls(FSimpleShapeSet3d& ShapeSetOut, EProjectedHullAxisMode AxisMode); + + + /** + * Fit containing axis-aligned box, oriented box, capsule, and sphere to each input mesh, and + * store the one with smallest volume in ShapeSetOut + */ + void Generate_MinVolume(FSimpleShapeSet3d& ShapeSetOut); + + + +protected: + TArray SourceMeshes; + + struct FSourceMeshCache + { + EDetectedSimpleShapeType DetectedType = EDetectedSimpleShapeType::None; + + FSphere3d DetectedSphere; + FOrientedBox3d DetectedBox; + FCapsule3d DetectedCapsule; + }; + TArray SourceMeshCaches; + + + void DetectAndCacheSimpleShapeType(const FDynamicMesh3* SourceMesh, FSourceMeshCache& CacheOut); + bool GetDetectedSimpleShape(const FSourceMeshCache& Cache, + FSimpleShapeSet3d& ShapeSetOut, FCriticalSection& ShapeSetLock); +}; + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ShapeApproximation/ShapeDetection3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ShapeApproximation/ShapeDetection3.h new file mode 100644 index 000000000000..ae7ca9cd9162 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ShapeApproximation/ShapeDetection3.h @@ -0,0 +1,45 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "VectorTypes.h" +#include "FrameTypes.h" +#include "SphereTypes.h" +#include "OrientedBoxTypes.h" +#include "CapsuleTypes.h" +#include "DynamicMesh3.h" + +namespace UE +{ + namespace Geometry + { + /** + * Detect if input Mesh is a meshed approximation of an analytic Sphere, and if so return best guess in SphereOut. + * Fits a sphere to input points with several rounds of incremental improvement, then measures chordal deviation of edge midpoints. + * @param RelativeDeviationTol distances from edge midpoints to sphere surface are allowed to deviate by 2*Radius*RelativeDeviationTol + * @return true if mesh is a Sphere and SphereOut is initialized + */ + bool DYNAMICMESH_API IsSphereMesh(const FDynamicMesh3& Mesh, FSphere3d& SphereOut, double RelativeDeviationTol = 0.025); + + + /** + * Detect if input Mesh is a meshed box, and if so return analytic box in BoxOut. + * Clusters face normals, looking to find 6 unique normals grouped into 3 opposite-direction pairs. + * If this configuraiton is found, computing minimal box is trivial. + * @param AngleToleranceDeg normals are allowed to deviate by this amount and still be considered coplanar + * @return true if mesh is a Box and BoxOut is initialized + */ + bool DYNAMICMESH_API IsBoxMesh(const FDynamicMesh3& Mesh, FOrientedBox3d& BoxOut, double AngleToleranceDeg = 0.1); + + + /** + * Detect if input Mesh is a meshed approximation of an analytic Capsule, and if so return best guess in CapsuleOut. + * Fits a capsule to input points, then measures chordal deviation of edge midpoints. + * @param RelativeDeviationTol distances from edge midpoints to capsule surface are allowed to deviate by 2*Radius*RelativeDeviationTol + * @return true if mesh is a Capsule and CapsuleOut is initialized + */ + bool DYNAMICMESH_API IsCapsuleMesh(const FDynamicMesh3& Mesh, FCapsule3d& CapsuleOut, double RelativeDeviationTol = 0.025); + + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ShapeApproximation/SimpleShapeSet3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ShapeApproximation/SimpleShapeSet3.h new file mode 100644 index 000000000000..233ac2145a54 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ShapeApproximation/SimpleShapeSet3.h @@ -0,0 +1,129 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "OrientedBoxTypes.h" +#include "SegmentTypes.h" +#include "CapsuleTypes.h" +#include "SphereTypes.h" +#include "DynamicMesh3.h" + + +/** + * Supported/known types of Simple Shapes + */ +enum class ESimpleShapeType +{ + Sphere = 2, + Box = 4, + Capsule = 8, + Convex = 16 +}; +ENUM_CLASS_FLAGS(ESimpleShapeType); + + +/** + * FSphereShape is a 3D sphere + */ +class DYNAMICMESH_API FSphereShape3d +{ +public: + FSphere3d Sphere; + + FSphereShape3d() = default; + + FSphereShape3d(const FSphere3d& SphereIn) + : Sphere(SphereIn) + { + } + + ESimpleShapeType GetShapeType() const { return ESimpleShapeType::Sphere; } +}; + + +/** + * FBoxShape is a 3D oriented box + */ +struct DYNAMICMESH_API FBoxShape3d +{ + FOrientedBox3d Box; + + FBoxShape3d() = default; + + FBoxShape3d(const FOrientedBox3d& BoxIn) + : Box(BoxIn) + { + } + + ESimpleShapeType GetShapeType() const { return ESimpleShapeType::Box; } +}; + + +/** + * FCapsuleShape is a 3D oriented capsule/sphyl + */ +struct DYNAMICMESH_API FCapsuleShape3d +{ + FCapsule3d Capsule; + + FCapsuleShape3d() = default; + + FCapsuleShape3d(const FCapsule3d& CapsuleIn) + : Capsule(CapsuleIn) + { + } + + ESimpleShapeType GetShapeType() const { return ESimpleShapeType::Capsule; } +}; + + +/** + * FConvexShape is a 3D convex hull, currently stored as a triangle mesh + */ +struct DYNAMICMESH_API FConvexShape3d +{ + FDynamicMesh3 Mesh; + + FConvexShape3d() = default; + + FConvexShape3d(const FDynamicMesh3& MeshIn) + : Mesh(MeshIn) + { + } + + FConvexShape3d(FDynamicMesh3&& MeshIn) + : Mesh(MoveTemp(MeshIn)) + { + } + + ESimpleShapeType GetShapeType() const { return ESimpleShapeType::Convex; } +}; + + +/** + * FSimpleShapeSet stores a set of simple geometry shapes useful for things like collision detection/etc. + * Various functions set-processing operations are supported. + */ +struct DYNAMICMESH_API FSimpleShapeSet3d +{ + TArray Spheres; + TArray Boxes; + TArray Capsules; + TArray Convexes; + + /** @return total number of elements in all sets */ + int32 TotalElementsNum() const { return Spheres.Num() + Boxes.Num() + Capsules.Num() + Convexes.Num(); } + + /** + * Remove any of the elements that are fully contained in larger elements + */ + void RemoveContainedGeometry(); + + /** + * Sort the elements by volume and then discard all but the largest MaximumCount elements + */ + void FilterByVolume(int32 MaximumCount); +}; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Solvers/ConstrainedMeshDeformer.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Solvers/ConstrainedMeshDeformer.h index 9d89b8329f02..4c62623d4e6d 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Solvers/ConstrainedMeshDeformer.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Solvers/ConstrainedMeshDeformer.h @@ -56,5 +56,18 @@ namespace UE */ TUniquePtr DYNAMICMESH_API ConstructConstrainedMeshDeformer(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& DynamicMesh); + + /** + * Construct a Mesh Deformer object for the given mesh that uses Biharmonic Laplacian Mesh Deformation to solve + * for the deformed vertex positions. + * + * Similar to ConstructConstrainedMeshDeformer() however (1) a Voronoi-Area Weighted Clamped Cotangent Laplacian + * is always used and (2) the boundary positions are included in the system. This allows for the solution of + * deformation problems where the boundary also moves, however it also means that constraints should be added + * for all boundary vertices or the deformation may be unstable. + * + */ + TUniquePtr DYNAMICMESH_API ConstructSoftMeshDeformer(const FDynamicMesh3& DynamicMesh); + } } \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Solvers/ConstrainedMeshSolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Solvers/ConstrainedMeshSolver.h index 094d8645c652..96f0804145be 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Solvers/ConstrainedMeshSolver.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Solvers/ConstrainedMeshSolver.h @@ -52,7 +52,18 @@ namespace UE }; + /** + * Extension of IConstrainedMeshSolver that supports manipulating the underlying Laplacian vectors + * used in Laplacian-based Deformation Solvers. + */ + class DYNAMICMESH_API IConstrainedLaplacianMeshSolver : public IConstrainedMeshSolver + { + public: + virtual ~IConstrainedLaplacianMeshSolver() {} + // Update global scale applied to Laplacian vectors before solve. + virtual void UpdateLaplacianScale(double UniformScale) = 0; + }; /** diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Intersection/ContainmentQueries3.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Intersection/ContainmentQueries3.cpp index 7469c191cfb0..6a116ede785f 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Intersection/ContainmentQueries3.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Intersection/ContainmentQueries3.cpp @@ -64,44 +64,6 @@ bool UE::Geometry::IsInside(const TCapsule3& OuterCapsule, const TOrie - - -template -bool IntersectsHalfSpace(const FVector3& NormalIntoSpace, const FVector3& PlanePoint, const TOrientedBox3& Box) -{ - RealType PlaneConstant = NormalIntoSpace.Dot(PlanePoint); - RealType Center = NormalIntoSpace.Dot(Box.Frame.Origin) - PlaneConstant; - FVector3 X, Y, Z; - Box.Frame.GetAxes(X, Y, Z); - RealType Radius = - TMathUtil::Abs(Box.Extents.X * NormalIntoSpace.Dot(X)) + - TMathUtil::Abs(Box.Extents.Y * NormalIntoSpace.Dot(Y)) + - TMathUtil::Abs(Box.Extents.Z * NormalIntoSpace.Dot(Z)); - return (Center + Radius >= 0); -} - - -template -bool IntersectsHalfSpace(const FVector3& NormalIntoSpace, const FVector3& PlanePoint, const TSphere3& Sphere) -{ - RealType PlaneConstant = NormalIntoSpace.Dot(PlanePoint); - RealType ProjectedCenter = NormalIntoSpace.Dot(Sphere.Center) - PlaneConstant; - return (ProjectedCenter + Sphere.Radius >= 0); -} - - - -template -bool IntersectsHalfSpace(const FVector3& NormalIntoSpace, const FVector3& PlanePoint, const TCapsule3& Capsule) -{ - RealType PlaneConstant = NormalIntoSpace.Dot(PlanePoint); - RealType ProjectedP0 = NormalIntoSpace.Dot(Capsule.Segment.StartPoint()) - PlaneConstant; - RealType ProjectedP1 = NormalIntoSpace.Dot(Capsule.Segment.EndPoint()) - PlaneConstant; - return ( TMathUtil::Max(ProjectedP0, ProjectedP1) + Capsule.Radius >= 0); -} - - - template bool UE::Geometry::IsInside(const TOrientedBox3& OuterBox, const TOrientedBox3& InnerBox) { @@ -111,11 +73,11 @@ bool UE::Geometry::IsInside(const TOrientedBox3& OuterBox, const TOrie OuterBox.Frame.GetAxes(Axes[0], Axes[1], Axes[2]); for (int32 k = 0; k < 3; ++k) { - if (IntersectsHalfSpace(Axes[k], Frame.Origin + Extents[k]*Axes[k], InnerBox)) + if (UE::Geometry::TestIntersection(THalfspace3(Axes[k], Frame.Origin + Extents[k]*Axes[k]), InnerBox)) { return false; } - if (IntersectsHalfSpace(-Axes[k], Frame.Origin - Extents[k]*Axes[k], InnerBox)) + if (UE::Geometry::TestIntersection(THalfspace3(-Axes[k], Frame.Origin - Extents[k]*Axes[k]), InnerBox)) { return false; } @@ -133,11 +95,11 @@ bool UE::Geometry::IsInside(const TOrientedBox3& OuterBox, const TSphe OuterBox.Frame.GetAxes(Axes[0], Axes[1], Axes[2]); for (int32 k = 0; k < 3; ++k) { - if (IntersectsHalfSpace(Axes[k], Frame.Origin + Extents[k]*Axes[k], InnerSphere)) + if (UE::Geometry::TestIntersection(THalfspace3(Axes[k], Frame.Origin + Extents[k]*Axes[k]), InnerSphere)) { return false; } - if (IntersectsHalfSpace(-Axes[k], Frame.Origin - Extents[k]*Axes[k], InnerSphere)) + if (UE::Geometry::TestIntersection(THalfspace3(-Axes[k], Frame.Origin - Extents[k]*Axes[k]), InnerSphere)) { return false; } @@ -156,11 +118,11 @@ bool UE::Geometry::IsInside(const TOrientedBox3& OuterBox, const TCaps OuterBox.Frame.GetAxes(Axes[0], Axes[1], Axes[2]); for (int32 k = 0; k < 3; ++k) { - if (IntersectsHalfSpace(Axes[k], Frame.Origin + Extents[k]*Axes[k], InnerCapsule)) + if (UE::Geometry::TestIntersection(THalfspace3(Axes[k], Frame.Origin + Extents[k]*Axes[k]), InnerCapsule)) { return false; } - if (IntersectsHalfSpace(-Axes[k], Frame.Origin - Extents[k]*Axes[k], InnerCapsule)) + if (UE::Geometry::TestIntersection(THalfspace3(-Axes[k], Frame.Origin - Extents[k]*Axes[k]), InnerCapsule)) { return false; } diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Intersection/IntersectionQueries3.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Intersection/IntersectionQueries3.cpp new file mode 100644 index 000000000000..02f1674b7076 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Intersection/IntersectionQueries3.cpp @@ -0,0 +1,52 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Intersection/IntersectionQueries3.h" + + + + + +template +bool UE::Geometry::TestIntersection(const THalfspace3& Halfspace, const TSphere3& Sphere) +{ + RealType ProjectedCenter = Halfspace.Normal.Dot(Sphere.Center) - Halfspace.Constant; + return (ProjectedCenter + Sphere.Radius >= 0); +} + + +template +bool UE::Geometry::TestIntersection(const THalfspace3& Halfspace, const TCapsule3& Capsule) +{ + RealType ProjectedP0 = Halfspace.Normal.Dot(Capsule.Segment.StartPoint()) - Halfspace.Constant; + RealType ProjectedP1 = Halfspace.Normal.Dot(Capsule.Segment.EndPoint()) - Halfspace.Constant; + return (TMathUtil::Max(ProjectedP0, ProjectedP1) + Capsule.Radius >= 0); +} + + + +template +bool UE::Geometry::TestIntersection(const THalfspace3& Halfspace, const TOrientedBox3& Box) +{ + RealType Center = Halfspace.Normal.Dot(Box.Frame.Origin) - Halfspace.Constant; + FVector3 X, Y, Z; + Box.Frame.GetAxes(X, Y, Z); + RealType Radius = + TMathUtil::Abs(Box.Extents.X * Halfspace.Normal.Dot(X)) + + TMathUtil::Abs(Box.Extents.Y * Halfspace.Normal.Dot(Y)) + + TMathUtil::Abs(Box.Extents.Z * Halfspace.Normal.Dot(Z)); + return (Center + Radius >= 0); +} + + +namespace UE +{ + namespace Geometry + { + template bool GEOMETRICOBJECTS_API TestIntersection(const THalfspace3& Halfspace, const TSphere3& InnerSphere); + template bool GEOMETRICOBJECTS_API TestIntersection(const THalfspace3& Halfspace, const TSphere3& InnerSphere); + template bool GEOMETRICOBJECTS_API TestIntersection(const THalfspace3& Halfspace, const TCapsule3& InnerSphere); + template bool GEOMETRICOBJECTS_API TestIntersection(const THalfspace3& Halfspace, const TCapsule3& InnerSphere); + template bool GEOMETRICOBJECTS_API TestIntersection(const THalfspace3& Halfspace, const TOrientedBox3& InnerSphere); + template bool GEOMETRICOBJECTS_API TestIntersection(const THalfspace3& Halfspace, const TOrientedBox3& InnerSphere); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/GeometrySet3.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/GeometrySet3.cpp index 7f2052c48089..1923769b0f44 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/GeometrySet3.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/GeometrySet3.cpp @@ -19,8 +19,6 @@ void FGeometrySet3::Reset(bool bPoints, bool bCurves) } } - - void FGeometrySet3::AddPoint(int PointID, const FVector3d& Point) { check(PointIDToIndex.Contains(PointID) == false); @@ -40,6 +38,41 @@ void FGeometrySet3::AddCurve(int CurveID, const FPolyline3d& Polyline) CurveIDToIndex.Add(CurveID, NewIndex); } +void FGeometrySet3::RemovePoint(int PointID) +{ + const int* IndexPointer = PointIDToIndex.Find(PointID); + check(IndexPointer != nullptr); + int32 Index = *IndexPointer; + Points.RemoveAt(Index); + PointIDToIndex.Remove(PointID); + + // Since we store things in a simple array, the indices have shifted + for (TPair& Entry : PointIDToIndex) + { + if (Entry.Value > Index) + { + --Entry.Value; + } + } +} + +void FGeometrySet3::RemoveCurve(int CurveID) +{ + const int* IndexPointer = CurveIDToIndex.Find(CurveID); + check(IndexPointer != nullptr); + int32 Index = *IndexPointer; + Curves.RemoveAt(Index); + CurveIDToIndex.Remove(CurveID); + + // Since we store things in a simple array, the indices have shifted + for (TPair& Entry : CurveIDToIndex) + { + if (Entry.Value > Index) + { + --Entry.Value; + } + } +} void FGeometrySet3::UpdatePoint(int PointID, const FVector3d& Point) { diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/BoxTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/BoxTypes.h index 67d1aa67f555..247ab96e8096 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/BoxTypes.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/BoxTypes.h @@ -41,6 +41,11 @@ struct TInterval1 return Max - Min; } + RealType MaxAbsExtrema() const + { + return TMathUtil::Max(TMathUtil::Abs(Min), TMathUtil::Abs(Max)); + } + void Contain(const RealType& V) { if (V < Min) @@ -306,6 +311,13 @@ struct TAxisAlignedBox3 FVector3(-TNumericLimits::Max(), -TNumericLimits::Max(), -TNumericLimits::Max())); } + static TAxisAlignedBox3 Infinite() + { + return TAxisAlignedBox3( + FVector3(-TNumericLimits::Max(), -TNumericLimits::Max(), -TNumericLimits::Max()), + FVector3(TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max()) ); + } + FVector3 Center() const { return FVector3( @@ -446,6 +458,11 @@ struct TAxisAlignedBox3 return TMathUtil::Max(Width(), TMathUtil::Max(Height(), Depth())); } + RealType MinDim() const + { + return TMathUtil::Min(Width(), TMathUtil::Min(Height(), Depth())); + } + FVector3 Diagonal() const { return FVector3(Max.X - Min.X, Max.Y - Min.Y, Max.Z - Min.Z); diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/CapsuleTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/CapsuleTypes.h index 170e472bd42b..67f5efdedc3c 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/CapsuleTypes.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/CapsuleTypes.h @@ -82,6 +82,23 @@ public: } + /** + * @return minimum squared distance from Point to Capsule surface for points outside capsule, 0 for points inside + */ + inline T DistanceSquared(const FVector3& Point) const + { + const T PosDistance = TMathUtil::Max(SignedDistance(Point), (T)0); + return PosDistance * PosDistance; + } + + /** + * @return signed distance from Point to Capsule surface. Points inside capsule return negative distance. + */ + inline T SignedDistance(const FVector3& Point) const + { + T SqrDist = Segment.DistanceSquared(Point); + return TMathUtil::Sqrt(SqrDist) - Radius; + } // // Sphere utility functions diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Generators/CapsuleGenerator.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Generators/CapsuleGenerator.h new file mode 100644 index 000000000000..7b3bdb7e0dc7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Generators/CapsuleGenerator.h @@ -0,0 +1,240 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MeshShapeGenerator.h" +#include "OrientedBoxTypes.h" +#include "Util/IndexUtil.h" + +/** + * Generate a Capsule mesh, with UVs wrapped cylindrically. + * This is basically a "stretched" standard sphere triangulation, where we have + * a set of quad strips "around" the sphere with a disc-shaped cap at each pole. + * For the Capsule we duplicate the equatorial ring, creating two separate hemispherical + * caps which are joined with a single quad strip. + * + * The Capsule line segment is oriented along +Z, with the start point at (0,0,0) and + * the end point at (0,0,SegmentLength). So the lower hemispherical cap is below the origin, + * ie the bottom pole is at (0,0,-Radius) and the top pole is at (0,0,SegmentLength+Radius). + */ +class /*GEOMETRICOBJECTS_API*/ FCapsuleGenerator : public FMeshShapeGenerator +{ +public: + /** Radius of capsule */ + double Radius = 1.0; + + /** Length of capsule line segment, so total height is SegmentLength + 2*Radius */ + double SegmentLength = 1.0; + + /** Number of vertices along the 90-degree arc from the pole to edge of spherical cap. */ + int NumHemisphereArcSteps = 5; + + /** Number of vertices along each circle */ + int NumCircleSteps = 3; + + /** If true, each quad gets a separate polygroup, otherwise the entire mesh is a single polygroup */ + bool bPolygroupPerQuad = false; + +private: + static FVector3d SphericalToCartesian(double r, double theta, double phi) + { + double Sphi = sin(phi); + double Cphi = cos(phi); + double Ctheta = cos(theta); + double Stheta = sin(theta); + + return FVector3d(r * Ctheta * Sphi, r * Stheta * Sphi, r * Cphi); + } + + void GenerateVertices() + { + auto SetVertex = [this](int32 VtxIdx, + FVector3d Pos, FVector3f Normal ) + { + Vertices[VtxIdx] = Pos; + Normals[VtxIdx] = Normal; + NormalParentVertex[VtxIdx] = VtxIdx; + }; + { + const double Dphi = FMathd::HalfPi / double(NumHemisphereArcSteps - 1); + const double Dtheta = FMathd::TwoPi / double(NumCircleSteps); + + int32 VtxIdx = 0; + double Phi, Theta; + int32 p, t; + + FVector3d Offset(0, 0, SegmentLength); + + // add points for first arc section + for (p = 1, Phi = Dphi; p < NumHemisphereArcSteps; ++p, Phi += Dphi) // NB: this skips the poles. + { + for (t = 0, Theta = 0; t < NumCircleSteps; ++t, ++VtxIdx, Theta += Dtheta) + { + FVector3d Normal = SphericalToCartesian(1., Theta, Phi); + SetVertex(VtxIdx, Normal * Radius + Offset, FVector3f(Normal)); + } + } + + // add points for second arc section + for (p = 1, Phi = FMathd::HalfPi; p < NumHemisphereArcSteps; ++p, Phi += Dphi) // NB: this skips the poles. + { + for (t = 0, Theta = 0; t < NumCircleSteps; ++t, ++VtxIdx, Theta += Dtheta) + { + FVector3d Normal = SphericalToCartesian(1., Theta, Phi); + SetVertex(VtxIdx, Normal * Radius, FVector3f(Normal)); + } + } + // add a single point at the North Pole + SetVertex(VtxIdx++, FVector3d::UnitZ() * Radius + Offset, FVector3f::UnitZ()); + // add a single point at the South Pole + SetVertex(VtxIdx++, -FVector3d::UnitZ() * Radius, -FVector3f::UnitZ()); + } + } + + void GenerateUVVertices() + { + // generate the UV's + int32 NumPhi = (2 * NumHemisphereArcSteps); + const float DUVphi = 1.0 / float(NumPhi - 1); + const float DUVtheta = -1.0 / float(NumCircleSteps); + + int32 UVIdx = 0; + int32 p,t; + float UVPhi, UVTheta; + for ( p = 1, UVPhi = DUVphi; p < NumPhi - 1; ++p, UVPhi += DUVphi) + { + for (t = 0, UVTheta = 1; t < NumCircleSteps; ++t, ++UVIdx, UVTheta += DUVtheta) + { + UVs[UVIdx] = FVector2f(UVTheta, UVPhi); + UVParentVertex[UVIdx] = (p - 1) * NumCircleSteps + t; + } + UVs[UVIdx] = FVector2f(UVTheta, UVPhi); + UVParentVertex[UVIdx] = (p - 1) * NumCircleSteps; // Wrap around + ++UVIdx; + } + int32 NorthPoleVtxIdx = (NumPhi - 2) * NumCircleSteps; + for (t = 0, UVTheta = 1 + DUVtheta; t < NumCircleSteps; ++t, ++UVIdx, UVTheta += DUVtheta) + { + UVs[UVIdx] = FVector2f(UVTheta, 0.0); + UVParentVertex[UVIdx] = NorthPoleVtxIdx; + } + int32 SouthPoleVtxIdx = NorthPoleVtxIdx + 1; + for (t = 0, UVTheta = 1 + DUVtheta; t < NumCircleSteps; ++t, ++UVIdx, UVTheta += DUVtheta) + { + UVs[UVIdx] = FVector2f(UVTheta, 1.0); + UVParentVertex[UVIdx] = SouthPoleVtxIdx; + } + } + + using CornerIndices = FVector3i; + void OutputTriangle(int TriIdx, int PolyIdx, CornerIndices Corners, CornerIndices UVCorners) + { + SetTriangle(TriIdx, Corners.X, Corners.Y, Corners.Z); + SetTrianglePolygon(TriIdx, PolyIdx); + SetTriangleUVs(TriIdx, UVCorners.X, UVCorners.Y, UVCorners.Z); + SetTriangleNormals(TriIdx, Corners.X, Corners.Y, Corners.Z); + } + + void OutputEquatorialTriangles() + { + int32 NumPhi = (2 * NumHemisphereArcSteps); + int32 TriIdx = 0, PolyIdx = 0; + + // Generate equatorial triangles + int32 Corners[4] = { 0, 1, NumCircleSteps + 1, NumCircleSteps}; + int32 UVCorners[4] = { 0, 1, NumCircleSteps + 2, NumCircleSteps + 1}; + for (int32 p = 1; p < NumPhi - 2; ++p) + { + for (int32 t = 0; t < NumCircleSteps - 1; ++t) + { + // convert each quad into 2 triangles. + OutputTriangle(TriIdx++, PolyIdx, + {Corners[0], Corners[1], Corners[2]}, + {UVCorners[0], UVCorners[1], UVCorners[2]}); + OutputTriangle(TriIdx++, PolyIdx, + {Corners[2], Corners[3], Corners[0]}, + {UVCorners[2], UVCorners[3], UVCorners[0]}); + for (int32& i : Corners) ++i; + for (int32& i : UVCorners) ++i; + if (bPolygroupPerQuad) + { + PolyIdx++; + } + } + OutputTriangle(TriIdx++, PolyIdx, + {Corners[0], Corners[1] - NumCircleSteps, Corners[2] - NumCircleSteps}, + {UVCorners[0] , UVCorners[1], UVCorners[2] }); + OutputTriangle(TriIdx++, PolyIdx, + {Corners[2] - NumCircleSteps, Corners[3], Corners[0]}, + {UVCorners[2], UVCorners[3], UVCorners[0]}); + for (int32& i : Corners) ++i; + for (int32& i : UVCorners) i += 2; + if (bPolygroupPerQuad) + { + PolyIdx++; + } + } + } + + void OutputPolarTriangles() + { + int32 NumPhi = (2 * NumHemisphereArcSteps); + const int32 NumEquatorialVtx = (NumPhi - 2) * NumCircleSteps; + const int32 NumEquatorialUVVtx = (NumPhi - 2) * (NumCircleSteps + 1); + const int32 NorthPoleVtxIdx = NumEquatorialVtx; + const int32 SouthPoleVtxIdx = NumEquatorialVtx + 1; + int32 PolyIdx = (NumCircleSteps * (NumPhi - 3)); + int32 TriIdx = PolyIdx * 2; + if (bPolygroupPerQuad == false) + { + PolyIdx = 0; + } + + // Triangles that connect to north pole + for (int32 t = 0; t < NumCircleSteps; ++t) + { + OutputTriangle(TriIdx++, PolyIdx, + {t, NorthPoleVtxIdx, (t + 1) % NumCircleSteps}, + {t, NumEquatorialUVVtx + t, t + 1}); + if (bPolygroupPerQuad) + { + PolyIdx++; + } + } + + // Triangles that connect to South pole + const int32 Offset = NumEquatorialVtx - NumCircleSteps; + const int32 OffsetUV = NumEquatorialUVVtx - (NumCircleSteps + 1); + for (int32 t = 0; t < NumCircleSteps; ++t) + { + OutputTriangle(TriIdx++, PolyIdx, + {t + Offset, ((t + 1) % NumCircleSteps) + Offset, SouthPoleVtxIdx}, + {t + OffsetUV, t + 1 + OffsetUV , NumEquatorialUVVtx + NumCircleSteps + t}); + if (bPolygroupPerQuad) + { + PolyIdx++; + } + } + } +public: + /** Generate the mesh */ + FMeshShapeGenerator& Generate() override + { + // enforce sane values for vertex counts + NumHemisphereArcSteps = FMath::Max(NumHemisphereArcSteps, 2); + int32 NumPhi = (2 * NumHemisphereArcSteps); + NumCircleSteps = FMath::Max(NumCircleSteps, 3); + const int32 NumVertices = (NumPhi - 2) * NumCircleSteps + 2; + const int32 NumUVs = (NumPhi - 2) * (NumCircleSteps + 1) + (2 * NumCircleSteps); + const int32 NumTris = (NumPhi - 2) * NumCircleSteps * 2; + SetBufferSizes(NumVertices, NumTris, NumUVs, NumVertices); + + GenerateVertices(); + GenerateUVVertices(); + OutputEquatorialTriangles(); + OutputPolarTriangles(); + return *this; + } + + +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Generators/SphereGenerator.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Generators/SphereGenerator.h index d7dd2b13b4d8..f757bc12b099 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Generators/SphereGenerator.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Generators/SphereGenerator.h @@ -156,6 +156,10 @@ private: const int32 SouthPoleVtxIdx = NumEquatorialVtx + 1; int32 PolyIdx = NumTheta * (NumPhi - 3); int32 TriIdx = PolyIdx * 2; + if (bPolygroupPerQuad == false) + { + PolyIdx = 0; + } // Triangles that connect to north pole for (int32 t = 0; t < NumTheta; ++t) diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/HalfspaceTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/HalfspaceTypes.h new file mode 100644 index 000000000000..31011544addc --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/HalfspaceTypes.h @@ -0,0 +1,45 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Math/UnrealMath.h" +#include "VectorTypes.h" + +/* + * 3D Halfspace stored as parameters to Plane Equation (Normal, Normal.Dot(PointOnPlane)) + * The Normal points "into" the halfspace, ie X is inside if (Normal.Dot(X) - Constant) >= 0 + */ +template +struct THalfspace3 +{ +public: + /** Normal vector of 3D plane that defines Halfspace */ + FVector3 Normal = FVector3::UnitY(); + /** Distance along Normal that defines position of Halfspace */ + T Constant = (T)0; + + THalfspace3() = default; + + THalfspace3(const FVector3& PlaneNormalIn, T ConstantIn) + : Normal(PlaneNormalIn), Constant(ConstantIn) {} + + THalfspace3(T NormalX, T NormalY, T NormalZ, T ConstantIn) + : Normal(NormalX,NormalY,NormalZ), Constant(ConstantIn) {} + + + /** Construct a Halfspace from the plane Normal and a Point lying on the plane */ + THalfspace3(const FVector3& PlaneNormalIn, const FVector3& PlanePoint) + : Normal(PlaneNormalIn), Constant(Normal.Dot(PlanePoint)) {} + + + /** @return true if Halfspace contains given Point */ + bool Contains(const FVector3& Point) const + { + return (Normal.Dot(Point) - Constant) >= 0; + } + +}; + + +typedef THalfspace3 FHalfspace3f; +typedef THalfspace3 FHalfspace3d; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Image/ImageBuilder.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Image/ImageBuilder.h new file mode 100644 index 000000000000..04a79ae6001e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Image/ImageBuilder.h @@ -0,0 +1,140 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MathUtil.h" +#include "VectorTypes.h" +#include "Image/ImageDimensions.h" +#include "Spatial/DenseGrid2.h" + +/** + * TImageBuilder is used to create and populate a 2D image with a templated "pixel" type. + */ +template +class TImageBuilder +{ +protected: + FImageDimensions Dimensions; + TDenseGrid2 Image; + +public: + + void SetDimensions(FImageDimensions DimensionsIn) + { + Dimensions = DimensionsIn; + Image.Resize(Dimensions.GetWidth(), Dimensions.GetHeight(), true); + } + + const FImageDimensions& GetDimensions() const + { + return Dimensions; + } + + /** + * Clear all Pixels in the current Mip to the clear/default color for the texture build type + */ + void Clear(const PixelType& ClearValue) + { + Image.AssignAll(ClearValue); + } + + + /** + * Get the Pixel at the given X/Y coordinates + */ + const PixelType& GetPixel(const FVector2i& ImageCoords) const + { + int64 LinearIndex = Dimensions.GetIndex(ImageCoords); + return Image[LinearIndex]; + } + + + /** + * Get the Pixel at the given linear index + */ + const PixelType& GetPixel(int64 LinearIndex) const + { + return Image[LinearIndex]; + } + + + /** + * Set the Pixel at the given X/Y coordinates to the given PixelType + */ + void SetPixel(const FVector2i& ImageCoords, const PixelType& NewValue) + { + int64 LinearIndex = Dimensions.GetIndex(ImageCoords); + Image[LinearIndex] = NewValue; + } + + + /** + * Set the Pixel at the given linear index to the given PixelType + */ + void SetPixel(int64 LinearIndex, const PixelType& NewValue) + { + SetPixel(Dimensions.GetCoords(LinearIndex), NewValue); + } + + + /** + * Copy Pixel value from one linear index to another + */ + void CopyPixel(int64 FromLinearIndex, int64 ToLinearIndex) + { + Image[ToLinearIndex] = Image[FromLinearIndex]; + } + + + /** + * Sample the image value at floating-point pixel coords with Bilinear interpolation + */ + template + PixelType BilinearSample(const FVector2d& PixelCoords, const PixelType& InvalidValue) const + { + double X = PixelCoords.X; + double Y = PixelCoords.Y; + + int X0 = (int)X, X1 = X0 + 1; + int Y0 = (int)Y, Y1 = Y0 + 1; + + // make sure we are in range + if (X0 < 0 || X1 >= Dimensions.GetWidth() || + Y0 < 0 || Y0 >= Dimensions.GetHeight()) + { + return InvalidValue; + } + + // convert double coords to [0,1] range + double Ax = PixelCoords.X - (double)X0; + double Ay = PixelCoords.Y - (double)Y0; + double OneMinusAx = 1.0 - Ax; + double OneMinusAy = 1.0 - Ay; + + PixelType V00 = GetPixel(FVector2i(X0, Y0)); + PixelType V10 = GetPixel(FVector2i(X1, Y0)); + PixelType V01 = GetPixel(FVector2i(X0, Y1)); + PixelType V11 = GetPixel(FVector2i(X1, Y1)); + + return V00 * (ScalarType)(OneMinusAx * OneMinusAy) + + V01 * (ScalarType)(OneMinusAx * Ay) + + V10 * (ScalarType)(Ax * OneMinusAy) + + V11 * (ScalarType)(Ax * Ay); + } + + + /** + * Sample the image value at floating-point UV coords with Bilinear interpolation. + * The UV coords are assumed to be in range [0,1]x[0,1], and that this maps to the [0,Width]x[0,Height] image pixel rectangle. + */ + template + PixelType BilinearSampleUV(const FVector2d& UVCoords, const PixelType& InvalidValue) const + { + FVector2d PixelCoords( + UVCoords.X * (double)Dimensions.GetWidth(), + UVCoords.Y * (double)Dimensions.GetHeight()); + + return BilinearSample(PixelCoords, InvalidValue); + } + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Image/ImageOccupancyMap.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Image/ImageOccupancyMap.h index 58f71336d36a..2e903fe2f9a4 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Image/ImageOccupancyMap.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Image/ImageOccupancyMap.h @@ -151,7 +151,7 @@ public: TFunctionRef WeightFunction, int32 FilterWidth, TArray& PassBuffer - ) + ) const { int64 N = Dimensions.Num(); PassBuffer.SetNum(N); diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/ContainmentQueries3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/ContainmentQueries3.h index 3b3a20b042ec..c28135397d68 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/ContainmentQueries3.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/ContainmentQueries3.h @@ -7,6 +7,9 @@ #include "SphereTypes.h" #include "CapsuleTypes.h" #include "OrientedBoxTypes.h" +#include "HalfspaceTypes.h" +#include "Intersection/IntersectionQueries3.h" + namespace UE { @@ -29,7 +32,7 @@ namespace UE bool IsInside(const TSphere3& OuterSphere, const TOrientedBox3& InnerBox); /** @return true if all all points in range-based for over EnumerablePts are inside OuterSphere */ - template + template().begin())> bool IsInside(const TSphere3& OuterSphere, EnumerablePointsType EnumerablePts) { for (FVector3 Point : EnumerablePts) @@ -61,7 +64,7 @@ namespace UE bool IsInside(const TCapsule3& OuterCapsule, const TOrientedBox3& InnerBox); /** @return true if all all points in range-based for over EnumerablePts are inside OuterCapsule */ - template + template().begin())> bool IsInside(const TCapsule3& OuterCapsule, EnumerablePointsType EnumerablePts) { for (FVector3 Point : EnumerablePts) @@ -93,7 +96,7 @@ namespace UE bool IsInside(const TOrientedBox3& OuterBox, const TCapsule3& InnerCapsule); /** @return true if all all points in range-based for over EnumerablePts are inside OuterBox */ - template + template().begin())> bool IsInside(const TOrientedBox3& OuterBox, EnumerablePointsType EnumerablePts) { for (FVector3 Point : EnumerablePts) @@ -106,5 +109,104 @@ namespace UE return true; } + + + // + // Convex Hull/Volume containment queries + // + + /** + * Test if the convex volume defined by a set of Halfspaces contains InnerSphere. + * Each Halfspace normal should point "outwards", ie it defines the outer halfspace that is not inside the Convex Volume. + * @return false if InnerSphere intersects any of the Halfspaces + */ + template + bool IsInsideHull(TArrayView> Halfspaces, const TSphere3& InnerSphere); + + /** + * Test if the convex volume defined by a set of Halfspaces contains InnerCapsule. + * Each Halfspace normal should point "outwards", ie it defines the outer halfspace that is not inside the Convex Volume. + * @return false if InnerCapsule intersects any of the Halfspaces + */ + template + bool IsInsideHull(TArrayView> Halfspaces, const TCapsule3& InnerCapsule); + + /** + * Test if the convex volume defined by a set of Halfspaces contains InnerBox. + * Each Halfspace normal should point "outwards", ie it defines the outer halfspace that is not inside the Convex Volume. + * @return false if InnerBox intersects any of the Halfspaces + */ + template + bool IsInsideHull(TArrayView> Halfspaces, const TOrientedBox3& InnerBox); + + /** + * Test if the convex volume defined by a set of Halfspaces contains InnerSphere. + * Each Halfspace normal should point "outwards", ie it defines the outer halfspace that is not inside the Convex Volume. + * @return false any of the Halfspaces contain any of the Points + */ + template().begin())> + bool IsInsideHull(TArrayView> Halfspaces, EnumerablePointsType EnumerablePts); + + } +} + + + +template +bool UE::Geometry::IsInsideHull(TArrayView> Halfspaces, const TSphere3& InnerSphere) +{ + for (const THalfspace3& Halfspace : Halfspaces) + { + if (UE::Geometry::TestIntersection(Halfspace, InnerSphere)) + { + return false; + } + } + return true; +} + + +template +bool UE::Geometry::IsInsideHull(TArrayView> Halfspaces, const TCapsule3& InnerCapsule) +{ + for (const THalfspace3& Halfspace : Halfspaces) + { + if (UE::Geometry::TestIntersection(Halfspace, InnerCapsule)) + { + return false; + } + } + return true; +} + + +template +bool UE::Geometry::IsInsideHull(TArrayView> Halfspaces, const TOrientedBox3& InnerBox) +{ + for (const THalfspace3& Halfspace : Halfspaces) + { + if (UE::Geometry::TestIntersection(Halfspace, InnerBox)) + { + return false; + } + } + return true; +} + + +template +bool UE::Geometry::IsInsideHull(TArrayView> Halfspaces, EnumerablePointsType EnumerablePts) +{ + for (const THalfspace3& Halfspace : Halfspaces) + { + for (FVector3 Point : EnumerablePts) + { + if (Halfspace.Contains(Point)) + { + return false; + } + } + } + return true; } \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntersectionQueries3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntersectionQueries3.h new file mode 100644 index 000000000000..9c08c1a42c74 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntersectionQueries3.h @@ -0,0 +1,34 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "VectorTypes.h" +#include "SphereTypes.h" +#include "CapsuleTypes.h" +#include "HalfspaceTypes.h" +#include "OrientedBoxTypes.h" + +namespace UE +{ + namespace Geometry + { + // + // Halfspace Intersection Queries + // + + /** @return true if Halfspace and Sphere intersect */ + template + bool TestIntersection(const THalfspace3& Halfspace, const TSphere3& Sphere); + + /** @return true if Halfspace and Capsule intersect */ + template + bool TestIntersection(const THalfspace3& Halfspace, const TCapsule3& Capsule); + + /** @return true if Halfspace and Box intersect */ + template + bool TestIntersection(const THalfspace3& Halfspace, const TOrientedBox3& Box); + + + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MathUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MathUtil.h index e1806ed2d3ae..8613a9123fa2 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MathUtil.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MathUtil.h @@ -147,8 +147,10 @@ public: static inline RealType SignNonZero(const RealType Value); static inline RealType Max(const RealType A, const RealType B); static inline RealType Max3(const RealType A, const RealType B, const RealType C); + static inline int32 Max3Index(const RealType A, const RealType B, const RealType C); static inline RealType Min(const RealType A, const RealType B); static inline RealType Min3(const RealType A, const RealType B, const RealType C); + static inline int32 Min3Index(const RealType A, const RealType B, const RealType C); /** compute min and max of a,b,c with max 3 comparisons (sometimes 2) */ static inline void MinMax(RealType A, RealType B, RealType C, RealType& MinOut, RealType& MaxOut); static inline RealType Sqrt(const RealType Value); @@ -229,6 +231,19 @@ RealType TMathUtil::Max3(const RealType A, const RealType B, const Rea return Max(Max(A, B), C); } +template +int32 TMathUtil::Max3Index(const RealType A, const RealType B, const RealType C) +{ + if (A >= B) + { + return (A >= C) ? 0 : 2; + } + else + { + return (B >= C) ? 1 : 2; + } +} + template RealType TMathUtil::Min(const RealType A, const RealType B) { @@ -241,6 +256,19 @@ RealType TMathUtil::Min3(const RealType A, const RealType B, const Rea return Min(Min(A, B), C); } +template +int32 TMathUtil::Min3Index(const RealType A, const RealType B, const RealType C) +{ + if (A <= B) + { + return (A <= C) ? 0 : 2; + } + else + { + return (B <= C) ? 1 : 2; + } +} + // compute min and max of a,b,c with max 3 comparisons (sometimes 2) template void TMathUtil::MinMax(RealType A, RealType B, RealType C, RealType& MinOut, RealType& MaxOut) diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polygon2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polygon2.h index b3b1acacedb7..5d819ddd21ce 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polygon2.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polygon2.h @@ -49,6 +49,18 @@ public: { } + /** + * Construct polygon with given indices into a vertex array + */ + TPolygon2(TArrayView> VertexArray, TArrayView VertexIndices) : Timestamp(0) + { + Vertices.SetNum(VertexIndices.Num()); + for (int32 Idx = 0; Idx < VertexIndices.Num(); Idx++) + { + Vertices[Idx] = VertexArray[VertexIndices[Idx]]; + } + } + /** @return the Timestamp for the polygon, which is updated every time the polygon is modified */ int GetTimestamp() const { diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Sampling/VectorSetAnalysis.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Sampling/VectorSetAnalysis.h new file mode 100644 index 000000000000..82f40cd90a26 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Sampling/VectorSetAnalysis.h @@ -0,0 +1,103 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "VectorTypes.h" + + +/** + * TVectorSetAnalysis3 computes various analyses of a set of input Vectors (currently mainly clustering. + */ +template +class TVectorSetAnalysis3 +{ +public: + // + // Input data + // + + TArray> Vectors; + TArray VectorIDs; + bool bNormalized = false; + + // + // Calculated values + // + + /** Set of vectors that represent centers of clusters */ + TArray> ClusterVectors; + /** Mapping from Vector index to ClusterVectors index */ + TArray VectorToClusterMap; + + + /** + * Initialize the internal set of Vectors and IDs using an external integer-Enumerable and associated GetVector(ID) function + * @param NumVectorsHint provide hint as to number of elements in Enumerable, to allow memory to be pre-allocated + * @param bIsNormalizedHint indicate whether vectors are normalized + */ + template + void Initialize(EnumerableIDType EnumerableIDs, TFunctionRef(int32)> GetVectorFunc, int32 NumVectorsHint = 0, bool bIsNormalizedHint = false) + { + Vectors.Reserve(NumVectorsHint); + VectorIDs.Reserve(NumVectorsHint); + for (int32 ID : EnumerableIDs) + { + VectorIDs.Add(ID); + Vectors.Add(GetVectorFunc(ID)); + } + + bNormalized = bIsNormalizedHint; + } + + /** @return number of input vectors */ + int32 NumVectors() const { return Vectors.Num(); } + + /** @return number of clusters found by last clustering algorithm (may be zero if not initialized) */ + int32 NumClusters() const { return ClusterVectors.Num(); } + + + /** + * Run simple greedy clustering algorithm on input ClusterVectors. + * Done in a single pass over vectors, each successive Vector is either grouped with one of the + * existing clusters if it's direction is within AngleToleranceDeg, or creates a new cluster. + */ + inline void GreedyClusterVectors(RealType AngleToleranceDeg) + { + check(bNormalized); // otherwise code below is incorrect + + RealType DotTolerance = TMathUtil::Cos(AngleToleranceDeg * TMathUtil::DegToRad); + + int32 N = NumVectors(), M = 0; + VectorToClusterMap.SetNum(N); + for (int32 k = 0; k < N; ++k) + { + bool bFound = false; + if (M > 0) + { + // try to find an existing cluster + for (int32 j = 0; j < M && !bFound; ++j) + { + if (Vectors[k].Dot(ClusterVectors[j]) > DotTolerance) + { + VectorToClusterMap[k] = j; + bFound = true; + } + } + } + + // cluster not found, spawn a new one + if (!bFound) + { + ClusterVectors.Add(Vectors[k]); + VectorToClusterMap[k] = k; + M++; + } + } + } + + +}; + +typedef TVectorSetAnalysis3 FVectorSetAnalysis3f; +typedef TVectorSetAnalysis3 FVectorSetAnalysis3d; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Solvers/MatrixInterfaces.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Solvers/MatrixInterfaces.h index ce5c8bb4fdb9..7c82af54781f 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Solvers/MatrixInterfaces.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Solvers/MatrixInterfaces.h @@ -37,12 +37,20 @@ namespace UE */ struct FPositionConstraint { - FPositionConstraint(const FVector3d& P, bool b) : Position(P), bPostFix(b) {} + /** ID of constrained UV element */ + int32 ElementID = -1; - FVector3d Position; + /** Index/Identifier of constraint, defined by usage */ + int32 ConstraintIndex = -1; + + /** Constraint position */ + FVector3d Position = FVector3d::Zero(); /** If bPostFix is true, this position constraint should be explicitly enforced after a solve */ - bool bPostFix; + bool bPostFix = false; + + /** Arbitrary weight */ + double Weight = 1.0; }; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/DenseGrid2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/DenseGrid2.h new file mode 100644 index 000000000000..655d25cdf4ef --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/DenseGrid2.h @@ -0,0 +1,189 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DenseGrid2 + +#pragma once + +#include "CoreMinimal.h" +#include "BoxTypes.h" + +#include "HAL/PlatformAtomics.h" + + +/** + * 2D dense grid of floating-point scalar values. + */ +template +class TDenseGrid2 +{ +protected: + /** grid of allocated elements */ + TArray64 Buffer; + + /** dimensions per axis */ + int64 DimensionX, DimensionY; + +public: + /** + * Create empty grid + */ + TDenseGrid2() : DimensionX(0), DimensionY(0) + { + } + + TDenseGrid2(int32 DimX, int32 DimY, ElemType InitialValue) + { + Resize(DimX, DimY); + Assign(InitialValue); + } + + int64 Size() const + { + return DimensionX * DimensionY; + } + + FVector2i GetDimensions() const + { + return FVector2i((int32)DimensionX, (int32)DimensionY); + } + + void Resize(int32 DimX, int32 DimY, bool bAllowShrinking = true) + { + DimensionX = (int64)DimX; + DimensionY = (int64)DimY; + Buffer.SetNumUninitialized( DimensionX*DimensionY, bAllowShrinking); + } + + void AssignAll(ElemType Value) + { + for (int64 i = 0, Num = Size(); i < Num; ++i) + { + Buffer[i] = Value; + } + } + + void SetMin(const FVector2i& IJK, ElemType NewValue) + { + int64 Idx = (int64)IJK.X + (DimensionX * (int64)IJK.Y); + if (NewValue < Buffer[Idx]) + { + Buffer[Idx] = NewValue; + } + } + + void SetMax(const FVector2i& IJK, ElemType NewValue) + { + int64 Idx = (int64)IJK.X + (DimensionX * (int64)IJK.Y); + if (NewValue > Buffer[Idx]) + { + Buffer[Idx] = NewValue; + } + } + + constexpr ElemType& operator[](int64 Idx) + { + return Buffer[Idx]; + } + constexpr const ElemType& operator[](int64 Idx) const + { + return Buffer[Idx]; + } + + constexpr ElemType& operator[](FVector2i Idx) + { + return Buffer[(int64)Idx.X + DimensionX * (int64)Idx.Y]; + } + constexpr const ElemType& operator[](FVector2i Idx) const + { + return Buffer[(int64)Idx.X + DimensionX * (int64)Idx.Y]; + } + constexpr ElemType& At(int32 X, int32 Y) + { + return Buffer[(int64)X + DimensionX * (int64)Y]; + } + constexpr const ElemType& At(int32 X, int32 Y) const + { + return Buffer[(int64)X + DimensionX * (int64)Y]; + } + constexpr ElemType& At(int64 X, int64 Y) + { + return Buffer[X + DimensionX * Y]; + } + constexpr const ElemType& At(int64 X, int64 Y) const + { + return Buffer[X + DimensionX * Y]; + } + + + void GetXPair(int32 X0, int32 Y, ElemType& AOut, ElemType& BOut) const + { + int64 Offset = DimensionX * (int64)Y; + AOut = Buffer[Offset + X0]; + BOut = Buffer[Offset + X0 + 1]; + } + + void Apply(TFunctionRef ApplyFunc) + { + for (int64 Idx = 0, Num = Size(); Idx < Num; Idx++) + { + Buffer[Idx] = F(Buffer[Idx]); + } + } + + FAxisAlignedBox2i Bounds() const + { + return FAxisAlignedBox2i({ 0, 0}, { (int32)DimensionX, (int32)DimensionY}); + } + FAxisAlignedBox2i BoundsInclusive() const + { + return FAxisAlignedBox2i({ 0, 0}, { (int32)(DimensionX - 1), (int32)(DimensionY - 1)}); + } + + FVector2i GetCoords(int64 LinearIndex) const + { + checkSlow(LinearIndex >= 0); + return FVector2i((int32)(LinearIndex % (int64)DimensionX), (int32)(LinearIndex / (int64)DimensionX)); + } + int64 GetIndex(int32 X, int32 Y) const + { + return X + DimensionX * Y; + } + int64 GetIndex(const FVector2i& IJK) const + { + return IJK.X + DimensionX * IJK.Y; + } +}; + + +typedef TDenseGrid2 FDenseGrid2f; +typedef TDenseGrid2 FDenseGrid2d; +typedef TDenseGrid2 FDenseGrid2i; + + +// additional utility functions +namespace DenseGrid +{ + + inline void AtomicIncrement(FDenseGrid2i& Grid, int32 i, int32 j) + { + FPlatformAtomics::InterlockedIncrement(&Grid.At(i, j)); + } + + inline void AtomicDecrement(FDenseGrid2i& Grid, int32 i, int32 j) + { + FPlatformAtomics::InterlockedDecrement(&Grid.At(i, j)); + } + + inline void AtomicIncDec(FDenseGrid2i& Grid, int32 i, int32 j, bool bDecrement = false) + { + if (bDecrement) + { + FPlatformAtomics::InterlockedDecrement(&Grid.At(i, j)); + } + else + { + FPlatformAtomics::InterlockedIncrement(&Grid.At(i, j)); + } + } +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/FastWinding.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/FastWinding.h index 4267322f7d9b..06d16c9bae00 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/FastWinding.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/FastWinding.h @@ -256,7 +256,7 @@ public: */ double FastWindingNumber(const FVector3d& P) const { - checkSlow(IsBuilt(P)); + checkSlow(IsBuilt()); double sum = branch_fast_winding_num(Tree->RootIndex, P); return sum; } diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/GeometrySet3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/GeometrySet3.h index d8d9a642ae3a..52ad3bb84567 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/GeometrySet3.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/GeometrySet3.h @@ -26,6 +26,11 @@ public: /** Add a polycurve with given CurveID and the give Polyline */ void AddCurve(int CurveID, const FPolyline3d& Polyline); + /** Remove a point with given PointID. */ + void RemovePoint(int PointID); + /** Remove a curve with given CurveID. */ + void RemoveCurve(int CurveID); + /** Update the Position of previously-added PointID */ void UpdatePoint(int PointID, const FVector3d& Position); /** Update the Polyline of previously-added CurveID */ diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/SphereTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/SphereTypes.h index e0a6481d7270..1aa0840bbde8 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/SphereTypes.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/SphereTypes.h @@ -61,6 +61,23 @@ public: } + /** + * @return minimum squared distance from Point to Sphere surface for points outside sphere, 0 for points inside + */ + inline T DistanceSquared(const FVector3& Point) const + { + const T PosDistance = TMathUtil::Max(SignedDistance(Point), (T)0); + return PosDistance * PosDistance; + } + + /** + * @return signed distance from Point to Sphere surface. Points inside sphere return negative distance. + */ + inline T SignedDistance(const FVector3& Point) const + { + return Center.Distance(Point) - Radius; + } + // // Sphere utility functions diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/ElementLinearization.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/ElementLinearization.h index bdb883bdc534..8f585d8ea821 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/ElementLinearization.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/ElementLinearization.h @@ -178,6 +178,18 @@ public: YVector[i] = Value.Y; ZVector[i] = Value.Z; } + + FVector3 Get(int32 i) + { + return FVector3(XVector[i], YVector[i], ZVector[i]); + } + + void Set(int32 i, const FVector3& Value) + { + XVector[i] = Value.X; + YVector[i] = Value.Y; + ZVector[i] = Value.Z; + } }; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorTypes.h index a2e47c8d5fbb..e942ab385b79 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorTypes.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorTypes.h @@ -419,6 +419,14 @@ struct FVector3 { return FVector3(TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max()); } + + /** @return unit vector along axis X=0, Y=1, Z=2 */ + constexpr static FVector3 MakeUnit(int32 Axis) + { + FVector3 UnitVec((T)0, (T)0, (T)0); + UnitVec[FMath::Clamp(Axis, 0, 2)] = (T)1; + return UnitVec; + } FVector3& operator=(const FVector3& V2) { @@ -621,11 +629,23 @@ struct FVector3 return TMathUtil::Max3(X,Y,Z); } + /** @return 0/1/2 index of maximum element */ + constexpr int32 MaxElementIndex() const + { + return TMathUtil::Max3Index(X, Y, Z); + } + constexpr T MinElement() const { return TMathUtil::Min3(X,Y,Z); } + /** @return 0/1/2 index of minimum element */ + constexpr int32 MinElementIndex() const + { + return TMathUtil::Min3Index(X, Y, Z); + } + constexpr friend FVector3 Min( const FVector3& V0, const FVector3& V1 ) { return FVector3(TMathUtil::Min(V0.X, V1.X), @@ -645,11 +665,23 @@ struct FVector3 return TMathUtil::Max3(TMathUtil::Abs(X), TMathUtil::Abs(Y), TMathUtil::Abs(Z)); } + /** @return 0/1/2 index of maximum absolute-value element */ + constexpr T MaxAbsElementIndex() const + { + return TMathUtil::Max3Index(TMathUtil::Abs(X), TMathUtil::Abs(Y), TMathUtil::Abs(Z)); + } + constexpr T MinAbsElement() const { return TMathUtil::Min3(TMathUtil::Abs(X), TMathUtil::Abs(Y), TMathUtil::Abs(Z)); } + /** @return 0/1/2 index of minimum absolute-value element */ + constexpr T MinAbsElementIndex() const + { + return TMathUtil::Min3Index(TMathUtil::Abs(X), TMathUtil::Abs(Y), TMathUtil::Abs(Z)); + } + constexpr FVector2 XY() const { return FVector2(X, Y); @@ -759,3 +791,298 @@ typedef FVector2 FVector2f; typedef FVector2 FVector2d; typedef FVector2 FVector2i; + + + + + + + + + +template +struct TVector4 +{ + T X{}, Y{}, Z{}, W{}; + + constexpr TVector4() + : X(0), Y(0), Z(0), W(0) + { + } + + constexpr TVector4(T ValX, T ValY, T ValZ, T ValW) + : X(ValX), Y(ValY), Z(ValZ), W(ValW) + { + } + + constexpr TVector4(const T* Data) + : X(Data[0]), Y(Data[1]), Z(Data[2]), W(Data[3]) + { + } + + constexpr TVector4(const TVector4& Vec) = default; + + template + explicit constexpr TVector4(const TVector4& Vec) + : X((T)Vec.X), Y((T)Vec.Y), Z((T)Vec.Z), W((T)Vec.W) + { + } + + explicit constexpr operator const T*() const + { + return &X; + }; + explicit constexpr operator T*() + { + return &X; + } + + explicit constexpr operator FLinearColor() const + { + return FLinearColor((float)X, (float)Y, (float)Z, (float)W); + } + constexpr TVector4(const FLinearColor& Color) + : X((T)Color.R), Y((T)Color.G), Z((T)Color.B), W((T)Color.A) + { + } + + explicit constexpr operator FColor() const + { + return FColor( + FMathf::Clamp((int)((float)X*255.0f), 0, 255), + FMathf::Clamp((int)((float)Y*255.0f), 0, 255), + FMathf::Clamp((int)((float)Z*255.0f), 0, 255), + FMathf::Clamp((int)((float)W*255.0f), 0, 255)); + } + + + static TVector4 Zero() + { + return TVector4((T)0, (T)0, (T)0, (T)0); + } + static TVector4 One() + { + return FVector3((T)1, (T)1, (T)1, (T)1); + } + + + TVector4& operator=(const TVector4& V2) + { + X = V2.X; + Y = V2.Y; + Z = V2.Z; + W = V2.W; + return *this; + } + + T& operator[](int Idx) + { + return (&X)[Idx]; + } + const T& operator[](int Idx) const + { + return (&X)[Idx]; + } + + T Length() const + { + return TMathUtil::Sqrt(X * X + Y * Y + Z * Z + W * W); + } + T SquaredLength() const + { + return X * X + Y * Y + Z * Z + W * W; + } + + + constexpr TVector4 operator-() const + { + return TVector4(-X, -Y, -Z, -W); + } + + constexpr TVector4 operator+(const TVector4& V2) const + { + return TVector4(X + V2.X, Y + V2.Y, Z + V2.Z, W + V2.W); + } + + constexpr TVector4 operator-(const TVector4& V2) const + { + return TVector4(X - V2.X, Y - V2.Y, Z - V2.Z, W - V2.W); + } + + constexpr TVector4 operator+(const T& Scalar) const + { + return TVector4(X + Scalar, Y + Scalar, Z + Scalar, W + Scalar); + } + + constexpr TVector4 operator-(const T& Scalar) const + { + return TVector4(X - Scalar, Y - Scalar, Z - Scalar, W - Scalar); + } + + constexpr TVector4 operator*(const T& Scalar) const + { + return TVector4(X * Scalar, Y * Scalar, Z * Scalar, W * Scalar); + } + + template + constexpr TVector4 operator*(const RealType2& Scalar) const + { + return TVector4(X * (T)Scalar, Y * (T)Scalar, Z * (T)Scalar, W * (T)Scalar); + } + + constexpr TVector4 operator*(const TVector4& V2) const // component-wise + { + return TVector4(X * V2.X, Y * V2.Y, Z * V2.Z, W * V2.W); + } + + constexpr TVector4 operator/(const T& Scalar) const + { + return TVector4(X / Scalar, Y / Scalar, Z / Scalar, W / Scalar); + } + + constexpr TVector4 operator/(const TVector4& V2) const // component-wise + { + return TVector4(X / V2.X, Y / V2.Y, Z / V2.Z, W / V2.W); + } + + constexpr TVector4& operator+=(const TVector4& V2) + { + X += V2.X; + Y += V2.Y; + Z += V2.Z; + W += V2.W; + return *this; + } + + constexpr TVector4& operator-=(const TVector4& V2) + { + X -= V2.X; + Y -= V2.Y; + Z -= V2.Z; + W -= V2.W; + return *this; + } + + constexpr TVector4& operator*=(const T& Scalar) + { + X *= Scalar; + Y *= Scalar; + Z *= Scalar; + W *= Scalar; + return *this; + } + + constexpr TVector4& operator/=(const T& Scalar) + { + X /= Scalar; + Y /= Scalar; + Z /= Scalar; + W /= Scalar; + return *this; + } + + T Dot(const TVector4& V2) const + { + return X * V2.X + Y * V2.Y + Z * V2.Z + W * V2.W; + } + + + constexpr bool IsNormalized() + { + return TMathUtil::Abs((X * X + Y * Y + Z * Z + W * W) - 1) < TMathUtil::ZeroTolerance; + } + + T Normalize(const T Epsilon = 0) + { + T length = Length(); + if (length > Epsilon) + { + T invLength = ((T)1) / length; + X *= invLength; + Y *= invLength; + Z *= invLength; + W *= invLength; + return length; + } + X = Y = Z = (T)0; + return 0; + } + + constexpr TVector4 Normalized(const T Epsilon = 0) const + { + T length = Length(); + if (length > Epsilon) + { + T invLength = ((T)1) / length; + return TVector4(X * invLength, Y * invLength, Z * invLength, W * invLength); + } + return TVector4::Zero(); + } + + + constexpr FVector3 XYZ() const + { + return FVector3(X, Y, Z); + } + + static TVector4 Lerp(const TVector4& A, const TVector4& B, T Alpha) + { + T OneMinusAlpha = (T)1 - Alpha; + return TVector4(OneMinusAlpha * A.X + Alpha * B.X, + OneMinusAlpha * A.Y + Alpha * B.Y, + OneMinusAlpha * A.Z + Alpha * B.Z, + OneMinusAlpha * A.W + Alpha * B.W ); + } + + static TVector4 Blend3(const TVector4& A, const TVector4& B, const TVector4& C, const T& WeightA, const T& WeightB, const T& WeightC) + { + return TVector4( + WeightA*A.X + WeightB*B.X + WeightC*C.X, + WeightA*A.Y + WeightB*B.Y + WeightC*C.Y, + WeightA*A.Z + WeightB*B.Z + WeightC*C.Z, + WeightA*A.W + WeightB*B.W + WeightC*C.W); + } + + + constexpr bool operator==(const TVector4& Other) const + { + return X == Other.X && Y == Other.Y && Z == Other.Z && W == Other.W; + } + + constexpr bool operator!=(const TVector4& Other) const + { + return X != Other.X || Y != Other.Y || Z != Other.Z || Z == Other.Z; + } +}; + +template +inline TVector4 operator*(RealType Scalar, const TVector4& V) +{ + return TVector4(Scalar * V.X, Scalar * V.Y, Scalar * V.Z, Scalar * V.W); +} + +// allow float*Vector4 and double*Vector4 +template +inline TVector4 operator*(RealType2 Scalar, const TVector4& V) +{ + return TVector4((RealType)Scalar * V.X, (RealType)Scalar * V.Y, (RealType)Scalar * V.Z, (RealType)Scalar * V.W); +} + +template +std::ostream& operator<<(std::ostream& os, const TVector4& Vec) +{ + os << Vec.X << " " << Vec.Y << " " << Vec.Z << " " << Vec.W; + return os; +} + +typedef TVector4 FVector4f; +typedef TVector4 FVector4d; +typedef TVector4 FVector4i; + +template +FORCEINLINE uint32 GetTypeHash(const TVector4& Vector) +{ + // (this is how FIntVector and all the other FVectors do their hash functions) + // Note: this assumes there's no padding that could contain uncompared data. + return FCrc::MemCrc_DEPRECATED(&Vector, sizeof(TVector4)); +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ConvexHull2.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ConvexHull2.cpp new file mode 100644 index 000000000000..246d01b7a27a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ConvexHull2.cpp @@ -0,0 +1,290 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +// Port of "ThirdParty/GTEngine/Mathematics/GteConvexHull2.h" + +#include "ConvexHull2.h" + +#include "ExactPredicates.h" +#include "Algo/Unique.h" + +template +bool TConvexHull2::Solve(int32 NumPoints, TFunctionRef(int32)> GetPointFunc, TFunctionRef FilterFunc) +{ + Hull.Reset(); + + Dimension = 0; + NumUniquePoints = 0; + + // Sort the points. + Hull.Reserve(NumPoints); // Reserve up to NumPoints assuming filter doesn't remove that many points in practice + for (int32 Idx = 0; Idx < NumPoints; Idx++) + { + if (FilterFunc(Idx)) + { + Hull.Add(Idx); + } + } + Hull.Sort([&GetPointFunc](int32 A, int32 B) + { + FVector2 PA = GetPointFunc(A), PB = GetPointFunc(B); + return (PA.X < PB.X) || (PA.X == PB.X && PA.Y < PB.Y); + }); + + Hull.SetNum(Algo::Unique(Hull, [&GetPointFunc](int32 A, int32 B) + { + return GetPointFunc(A) == GetPointFunc(B); + })); + NumUniquePoints = Hull.Num(); + + if (Hull.Num() < 3) + { + Dimension = FMath::Max(0, Hull.Num() - 1); + return false; + } + else + { + FVector2d FirstTwoPts[2]{ (FVector2d)GetPointFunc(Hull[0]), (FVector2d)GetPointFunc(Hull[1]) }; + bool bFoundSecondDim = false; + for (int32 Idx = 2; Idx < Hull.Num(); Idx++) + { + FVector2d Pt = (FVector2d)GetPointFunc(Hull[Idx]); + if (ExactPredicates::Orient2D(FirstTwoPts[0], FirstTwoPts[1], Pt) != 0) + { + bFoundSecondDim = true; + break; + } + } + if (!bFoundSecondDim) + { + Dimension = 1; + return false; + } + } + + // points are not all collinear; proceed with finding hull + Dimension = 2; + + // Use a divide-and-conquer algorithm. The merge step computes the + // convex hull of two convex polygons. + TArray Merged; + Merged.SetNum(NumUniquePoints); + // Note these indices will be changed by the recursive GetHull call + int32 IdxFirst = 0, IdxLast = NumUniquePoints - 1; + GetHull(GetPointFunc, Merged, IdxFirst, IdxLast); + Hull.SetNum(IdxLast - IdxFirst + 1); + + return true; +} + +template +void TConvexHull2::GetHull(TFunctionRef(int32)> GetPointFunc, TArray& Merged, int32& IdxFirst, int32& IdxLast) +{ + int32 NumVertices = IdxLast - IdxFirst + 1; + if (NumVertices > 1) + { + // Compute the middle index of input range. + int32 Mid = (IdxFirst + IdxLast) / 2; + + // Compute the hull of subsets (mid-i0+1 >= i1-mid). + int32 j0 = IdxFirst, j1 = Mid, j2 = Mid + 1, j3 = IdxLast; + GetHull(GetPointFunc, Merged, j0, j1); + GetHull(GetPointFunc, Merged, j2, j3); + + // Merge the convex hulls into a single convex hull. + Merge(GetPointFunc, Merged, j0, j1, j2, j3, IdxFirst, IdxLast); + } + // else: The convex hull is a single point. +} + +template +void TConvexHull2::Merge(TFunctionRef(int32)> GetPointFunc, TArray& Merged, int32 j0, int32 j1, int32 j2, int32 j3, int32& i0, int32& i1) +{ + // Subhull0 is to the left of subhull1 because of the initial sorting of + // the points by x-components. We need to find two mutually visible + // points, one on the left subhull and one on the right subhull. + int32 size0 = j1 - j0 + 1; + int32 size1 = j3 - j2 + 1; + + int32 i; + FVector2d p; + + // Find the right-most point of the left subhull. + FVector2d pmax0 = (FVector2d)GetPointFunc(Hull[j0]); + int32 imax0 = j0; + for (i = j0 + 1; i <= j1; ++i) + { + p = (FVector2d)GetPointFunc(Hull[i]); + if (pmax0.X < p.X || (pmax0.X == p.X && pmax0.Y < p.Y)) // lexicographic pmax0 < p + { + pmax0 = p; + imax0 = i; + } + } + + // Find the left-most point of the right subhull. + FVector2d pmin1 = (FVector2d)GetPointFunc(Hull[j2]); + int32 imin1 = j2; + for (i = j2 + 1; i <= j3; ++i) + { + p = (FVector2d)GetPointFunc(Hull[i]); + if (p.X < pmin1.X || (p.X == pmin1.X && p.Y < pmin1.Y)) // lexicographic p < pmin1 + { + pmin1 = p; + imin1 = i; + } + } + + // Get the lower tangent to hulls (LL = lower-left, LR = lower-right). + int32 iLL = imax0, iLR = imin1; + GetTangent(GetPointFunc, Merged, j0, j1, j2, j3, iLL, iLR); + + // Get the upper tangent to hulls (UL = upper-left, UR = upper-right). + int32 iUL = imax0, iUR = imin1; + GetTangent(GetPointFunc, Merged, j2, j3, j0, j1, iUR, iUL); + + // Construct the counterclockwise-ordered merged-hull vertices. + int32 k; + int32 numMerged = 0; + + i = iUL; + for (k = 0; k < size0; ++k) + { + Merged[numMerged++] = Hull[i]; + if (i == iLL) + { + break; + } + i = (i < j1 ? i + 1 : j0); + } + checkfSlow(k < size0, TEXT("Unexpected condition.")); + + i = iLR; + for (k = 0; k < size1; ++k) + { + Merged[numMerged++] = Hull[i]; + if (i == iUR) + { + break; + } + i = (i < j3 ? i + 1 : j2); + } + checkfSlow(k < size1, TEXT("Unexpected condition.")); + + int32 next = j0; + for (k = 0; k < numMerged; ++k) + { + Hull[next] = Merged[k]; + ++next; + } + + i0 = j0; + i1 = next - 1; +} + +// All possible point orderings for GetTangent (note that duplicate points have been filtered already, so those cases are not included) +enum class EPointOrdering +{ + Positive, + Negative, + CollinearLeft, + CollinearRight, + CollinearContain +}; + + +EPointOrdering PointOnLine(FVector2d P, FVector2d L0, FVector2d L1) +{ + double OnLine = ExactPredicates::Orient2D(P, L0, L1); + if (OnLine > 0) + { + return EPointOrdering::Positive; + } + else if (OnLine < 0) + { + return EPointOrdering::Negative; + } + + // Since points are exactly collinear, and not duplicate, should be ok to just compare on one axis + int UseDim = 0; + // if the points have same value on first axis, use the second + if (P[0] == L0[0]) + { + UseDim = 1; + } + + bool bPv0 = P[UseDim] > L0[UseDim]; + bool bPv1 = P[UseDim] > L1[UseDim]; + if (bPv0 != bPv1) + { + return EPointOrdering::CollinearContain; + } + bool b1v0 = L1[UseDim] > L0[UseDim]; + if (b1v0 == bPv0) + { + return EPointOrdering::CollinearRight; + } + else + { + return EPointOrdering::CollinearLeft; + } +} + +template +void TConvexHull2::GetTangent(TFunctionRef(int32)> GetPointFunc, TArray& Merged, int32 j0, int32 j1, int32 j2, int32 j3, int32& i0, int32& i1) +{ + // In theory the loop terminates in a finite number of steps, but the + // upper bound for the loop variable is used to trap problems caused by + // floating point roundoff errors that might lead to an infinite loop. + + int32 size0 = j1 - j0 + 1; + int32 size1 = j3 - j2 + 1; + int32 const imax = size0 + size1; + int32 i, iLm1, iRp1; + FVector2d L0, L1, R0, R1; + + for (i = 0; i < imax; i++) + { + // Get the endpoints of the potential tangent. + L1 = (FVector2d)GetPointFunc(Hull[i0]); + R0 = (FVector2d)GetPointFunc(Hull[i1]); + + // Walk along the left hull to find the point of tangency. + if (size0 > 1) + { + iLm1 = (i0 > j0 ? i0 - 1 : j1); + L0 = (FVector2d)GetPointFunc(Hull[iLm1]); + EPointOrdering Order = PointOnLine(R0, L0, L1); + if (Order == EPointOrdering::Negative + || Order == EPointOrdering::CollinearRight) + { + i0 = iLm1; + continue; + } + } + + // Walk along right hull to find the point of tangency. + if (size1 > 1) + { + iRp1 = (i1 < j3 ? i1 + 1 : j2); + R1 = (FVector2d)GetPointFunc(Hull[iRp1]); + EPointOrdering Order = PointOnLine(L1, R0, R1); + if (Order == EPointOrdering::Negative + || Order == EPointOrdering::CollinearLeft) + { + i1 = iRp1; + continue; + } + } + + // The tangent segment has been found. + break; + } + + // Detect an "infinite loop" caused by floating point round-off errors; should never happen b/c we use exact predicates + checkfSlow(i < imax, TEXT("Unexpected condition.")); + +} + + +template class TConvexHull2; +template class TConvexHull2; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ConvexHull3.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ConvexHull3.cpp index cd1aadced09e..f95a3b247160 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ConvexHull3.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ConvexHull3.cpp @@ -1,106 +1,281 @@ // Copyright Epic Games, Inc. All Rights Reserved. +// Adaptation/Port of "ThirdParty/GTEngine/Mathematics/GteConvexHull3.h" + #include "ConvexHull3.h" +#include "ExactPredicates.h" -#include "ThirdParty/GTEngine/Mathematics/GteBSNumber.h" -#include "ThirdParty/GTEngine/Mathematics/GteUIntegerFP32.h" -#include "ThirdParty/GTEngine/Mathematics/GteConvexHull3.h" +#include "Async/ParallelFor.h" -template -struct TConvexHull3Internal +/** + * Helper class to find the dimensions spanned by a point cloud + * and (if it spans 3 dimensions) the indices of four 'extreme' points + * forming a (non-degenerate, volume > 0) tetrahedron + * + * The extreme points are chosen to be far apart, and are used as the starting point for + * incremental convex hull construction. + * Construction method is adapted from GteVector3.h's IntrinsicsVector3 + */ +template +struct TExtremePoints3 { - using PreciseNumberType = gte::BSNumber>; // 197 is from ConvexHull3 documentation - using DVector3 = gte::Vector3; + int Dimension = 0; + int Extreme[4]{ 0, 0, 0, 0 }; - bool bUseExact; - TArray DoubleInput; + // Coordinate frame spanned by input points + FVector3 Origin{ 0,0,0 }; + FVector3 Basis[3]{ {0,0,0}, {0,0,0}, {0,0,0} }; - void SetPoint(int32 Index, const FVector3& Point) + TExtremePoints3(int32 NumPoints, TFunctionRef(int32)> GetPointFunc, TFunctionRef FilterFunc, double Epsilon = 0) { - DoubleInput[Index] = DVector3{ {(double)Point.X, (double)Point.Y, (double)Point.Z} }; + FVector3 FirstPoint; + int FirstPtIdx = -1; + for (FirstPtIdx = 0; FirstPtIdx < NumPoints; FirstPtIdx++) + { + if (FilterFunc(FirstPtIdx)) + { + FirstPoint = GetPointFunc(FirstPtIdx); + break; + } + } + if (FirstPtIdx == -1) + { + // no points passed filter + Dimension = 0; + return; + } + + FVector3 Min = GetPointFunc(FirstPtIdx), Max = GetPointFunc(FirstPtIdx); + FIndex3i IndexMin(FirstPtIdx, FirstPtIdx, FirstPtIdx), IndexMax(FirstPtIdx, FirstPtIdx, FirstPtIdx); + for (int Idx = FirstPtIdx + 1; Idx < NumPoints; Idx++) + { + if (!FilterFunc(Idx)) + { + continue; + } + for (int Dim = 0; Dim < 3; Dim++) + { + RealType Val = GetPointFunc(Idx)[Dim]; + if (Val < Min[Dim]) + { + Min[Dim] = Val; + IndexMin[Dim] = Idx; + } + else if (Val > Max[Dim]) + { + Max[Dim] = Val; + IndexMax[Dim] = Idx; + } + } + } + + RealType MaxRange = Max[0] - Min[0]; + int MaxRangeDim = 0; + for (int Dim = 1; Dim < 3; Dim++) + { + RealType Range = Max[Dim] - Min[Dim]; + if (Range > MaxRange) + { + MaxRange = Range; + MaxRangeDim = Dim; + } + } + Extreme[0] = IndexMin[MaxRangeDim]; + Extreme[1] = IndexMax[MaxRangeDim]; + + // all points within box of Epsilon extent; Dimension must be 0 + if (MaxRange <= Epsilon) + { + Dimension = 0; + Extreme[3] = Extreme[2] = Extreme[1] = Extreme[0]; + return; + } + + Origin = GetPointFunc(Extreme[0]); + Basis[0] = GetPointFunc(Extreme[1]) - Origin; + Basis[0].Normalize(); + + // find point furthest from the line formed by the first two extreme points + { + TLine3 Basis0Line(Origin, Basis[0]); + RealType MaxDistSq = 0; + for (int Idx = FirstPtIdx; Idx < NumPoints; Idx++) + { + if (!FilterFunc(Idx)) + { + continue; + } + RealType DistSq = Basis0Line.DistanceSquared(GetPointFunc(Idx)); + if (DistSq > MaxDistSq) + { + MaxDistSq = DistSq; + Extreme[2] = Idx; + } + } + + // Nearly collinear points + if (TMathUtil::Sqrt(MaxDistSq) <= Epsilon * MaxRange) + { + Dimension = 1; + Extreme[3] = Extreme[2] = Extreme[1]; + return; + } + } + + + Basis[1] = GetPointFunc(Extreme[2]) - Origin; + // project Basis[1] to be orthogonal to Basis[0] + Basis[1] -= (Basis[0].Dot(Basis[1])) * Basis[0]; + Basis[1].Normalize(); + Basis[2] = Basis[0].Cross(Basis[1]); + + { + TPlane3 Plane(Basis[2], Origin); + RealType MaxDist = 0, MaxSign = 0; + for (int Idx = FirstPtIdx; Idx < NumPoints; Idx++) + { + if (!FilterFunc(Idx)) + { + continue; + } + RealType DistSigned = Plane.DistanceTo(GetPointFunc(Idx)); + RealType Dist = TMathUtil::Abs(DistSigned); + if (Dist > MaxDist) + { + MaxDist = Dist; + MaxSign = TMathUtil::Sign(DistSigned); + Extreme[3] = Idx; + } + } + + // Nearly coplanar points + if (MaxDist <= Epsilon * MaxRange) + { + Dimension = 2; + Extreme[3] = Extreme[2]; + return; + } + + // make sure the tetrahedron is CW-oriented + if (MaxSign > 0) + { + Swap(Extreme[3], Extreme[2]); + } + } + + Dimension = 3; } - gte::ConvexHull3 DoubleCompute; - gte::ConvexHull3 PreciseCompute; - bool bSolutionOK = false; - - bool ComputeResult() - { - bSolutionOK = false; - if (bUseExact) - { - bSolutionOK = PreciseCompute(DoubleInput.Num(), &DoubleInput[0], 0.0); - } - else - { - bSolutionOK = DoubleCompute(DoubleInput.Num(), &DoubleInput[0], 0.0); - } - return bSolutionOK; - } - - - void EnumerateTriangles( TFunctionRef TriangleFunc ) - { - ensure(bSolutionOK); - std::vector> const& Triangles = (bUseExact) ? - PreciseCompute.GetHullUnordered() : DoubleCompute.GetHullUnordered(); - - for (const gte::TriangleKey& Tri : Triangles) - { - TriangleFunc(FIndex3i(Tri.V[0], Tri.V[1], Tri.V[2])); - } - } }; -template -bool TConvexHull3::Solve(int32 NumPoints, TFunctionRef(int32)> GetPointFunc, bool bUseExactComputation) -{ - Initialize(NumPoints, bUseExactComputation); - check(Internal); - for (int32 k = 0; k < NumPoints; ++k) + +template +bool TConvexHull3::Solve(int32 NumPoints, TFunctionRef(int32)> GetPointFunc, TFunctionRef FilterFunc) +{ + Hull.Reset(); + + TExtremePoints3 InitialTet(NumPoints, GetPointFunc, FilterFunc); + Dimension = InitialTet.Dimension; + if (Dimension < 3) { - FVector3 Point = GetPointFunc(k); - Internal->SetPoint(k, Point); + if (Dimension == 1) + { + Line = TLine3(InitialTet.Origin, InitialTet.Basis[0]); + } + else if (Dimension == 2) + { + Plane = TPlane3(InitialTet.Basis[1], InitialTet.Origin); + } + return false; } - return Internal->ComputeResult(); + // safety check; seems possible the InitialTet chosen points were actually coplanar, because it was constructed w/ inexact math + if (ExactPredicates::Orient3D(GetPointFunc(InitialTet.Extreme[0]), GetPointFunc(InitialTet.Extreme[1]), GetPointFunc(InitialTet.Extreme[2]), GetPointFunc(InitialTet.Extreme[3])) == 0) + { + Plane = TPlane3(InitialTet.Basis[1], InitialTet.Origin); + Dimension = 2; + return false; + } + + // Add triangles from InitialTet + Hull.Add(FIndex3i(InitialTet.Extreme[1], InitialTet.Extreme[2], InitialTet.Extreme[3])); + Hull.Add(FIndex3i(InitialTet.Extreme[0], InitialTet.Extreme[3], InitialTet.Extreme[2])); + Hull.Add(FIndex3i(InitialTet.Extreme[0], InitialTet.Extreme[1], InitialTet.Extreme[3])); + Hull.Add(FIndex3i(InitialTet.Extreme[0], InitialTet.Extreme[2], InitialTet.Extreme[1])); + + // The set of processed points is maintained to eliminate exact duplicates + // (should not be needed for correctness but lets us count unique points and is probably faster) + TSet> Processed; + for (int i = 0; i < 4; ++i) + { + Processed.Add(GetPointFunc(InitialTet.Extreme[i])); + } + // Incrementally update the hull. + for (int i = 0; i < NumPoints; ++i) + { + if (FilterFunc(i) && !Processed.Contains(GetPointFunc(i))) + { + Insert(GetPointFunc, i); + Processed.Add(GetPointFunc(i)); + } + } + NumUniquePoints = Processed.Num(); + + return true; } -template -bool TConvexHull3::IsSolutionAvailable() const + +template +void TConvexHull3::Insert(TFunctionRef(int32)> GetPointFunc, int32 PtIdx) { - return Internal && Internal->bSolutionOK; -} + TArray PtSides; PtSides.SetNum(Hull.Num()); + FVector3d Pt = (FVector3d)GetPointFunc(PtIdx); // double precision point to use double precision plane test + bool bSingleThread = true; // TODO: have yet to find a dataset where the ParallelFor actually helps -- maybe if Hull.Num() gets very large? + ParallelFor(Hull.Num(), [this, &GetPointFunc, &PtSides, Pt](int32 TriIdx) + { + FIndex3i Tri = Hull[TriIdx]; + double Side = FMath::Sign(ExactPredicates::Orient3D((FVector3d)GetPointFunc(Tri.A), (FVector3d)GetPointFunc(Tri.B), (FVector3d)GetPointFunc(Tri.C), Pt)); + PtSides[TriIdx] = Side > 0 ? 1 : (Side < 0 ? -1 : 0); + }, bSingleThread); -template -void TConvexHull3::GetTriangles(TFunctionRef TriangleFunc) -{ - ensure(IsSolutionAvailable()); - Internal->EnumerateTriangles(TriangleFunc); + TSet TerminatorEdges; // set of 'terminator' edges -- edges that we see only once among the removed triangles -- to be connected to the new vertex + for (int TriIdx = 0; TriIdx < Hull.Num(); TriIdx++) + { + int PtSide = PtSides[TriIdx]; + if (PtSide > 0) + { + const FIndex3i& Tri = Hull[TriIdx]; + for (int E0 = 2, E1 = 0; E1 < 3; E0 = E1++) + { + int V0 = Tri[E0], V1 = Tri[E1]; + FIndex2i Edge(V0, V1); + // if we've seen this edge before, it would be backwards -- try to remove it + FIndex2i BackEdge(V1, V0); + if (TerminatorEdges.Remove(BackEdge) == 0) + { + // we hadn't seen it before, so add it as a candidate terminator + TerminatorEdges.Add(Edge); + } + } + Hull.RemoveAtSwap(TriIdx, 1, false); + PtSides.RemoveAtSwap(TriIdx, 1, false); + TriIdx--; + } + } + + for (const FIndex2i& Edge : TerminatorEdges) + { + Hull.Add(FIndex3i(PtIdx, Edge.A, Edge.B)); // add triangle connecting inserted point to terminator edge + } } -template -void TConvexHull3::Initialize(int32 NumPoints, bool bUseExactComputation) -{ - Internal = MakePimpl>(); - - Internal->bUseExact = bUseExactComputation; - Internal->DoubleInput.SetNum(NumPoints); -} - - - - - - - -template class GEOMETRYALGORITHMS_API TConvexHull3; -template class GEOMETRYALGORITHMS_API TConvexHull3; \ No newline at end of file +template class TConvexHull3; +template class TConvexHull3; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ExactPredicates.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ExactPredicates.cpp index 5e53caa4cb40..c4770bc9d074 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ExactPredicates.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ExactPredicates.cpp @@ -15,13 +15,23 @@ namespace ExactPredicates double Orient2DInexact(double *pa, double *pb, double *pc) { - check(ShewchukExactPredicates::IsExactPredicateDataInitialized()); return ShewchukExactPredicates::orient2dfast(pa, pb, pc); } double Orient2D(double *pa, double *pb, double *pc) { - check(ShewchukExactPredicates::IsExactPredicateDataInitialized()); + checkSlow(ShewchukExactPredicates::IsExactPredicateDataInitialized()); return ShewchukExactPredicates::orient2d(pa, pb, pc); } + + double Orient3DInexact(double* PA, double* PB, double* PC, double* PD) + { + return ShewchukExactPredicates::orient3dfast(PA, PB, PC, PD); + } + + double Orient3D(double* PA, double* PB, double* PC, double* PD) + { + checkSlow(ShewchukExactPredicates::IsExactPredicateDataInitialized()); + return ShewchukExactPredicates::orient3d(PA, PB, PC, PD); + } } diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ExactPredicates/ThirdParty/ShewchukPredicatesInterface.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ExactPredicates/ThirdParty/ShewchukPredicatesInterface.h index a26ea44d8cef..389161f01caf 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ExactPredicates/ThirdParty/ShewchukPredicatesInterface.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ExactPredicates/ThirdParty/ShewchukPredicatesInterface.h @@ -19,12 +19,12 @@ namespace ShewchukExactPredicates // TODO also build+expose float version - double orient2dfast(double *pa, double *pb, double *pc); - double orient2d(double *pa, double *pb, double *pc); - double orient3dfast(double *pa, double *pb, double *pc, double *pd); - double orient3d(double *pa, double *pb, double *pc, double *pd); - double incirclefast(double *pa, double *pb, double *pc, double *pd); - double incircle(double *pa, double *pb, double *pc, double *pd); - double inspherefast(double *pa, double *pb, double *pc, double *pd, double *pe); - double insphere(double *pa, double *pb, double *pc, double *pd, double *pe); + double orient2dfast(double* pa, double* pb, double* pc); + double orient2d(double* pa, double* pb, double* pc); + double orient3dfast(double* pa, double* pb, double* pc, double* pd); + double orient3d(double* pa, double* pb, double* pc, double* pd); + double incirclefast(double* pa, double* pb, double* pc, double* pd); + double incircle(double* pa, double* pb, double* pc, double* pd); + double inspherefast(double* pa, double* pb, double* pc, double* pd, double* pe); + double insphere(double* pa, double* pb, double* pc, double* pd, double* pe); } // namespace ExactPredicates diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/GteUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/GteUtil.h index 47def9c6215e..e7b31d2eb5e8 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/GteUtil.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/GteUtil.h @@ -6,10 +6,24 @@ #include "LineTypes.h" #include "CircleTypes.h" +THIRD_PARTY_INCLUDES_START +#include "ThirdParty/GTEngine/Mathematics/GteVector2.h" #include "ThirdParty/GTEngine/Mathematics/GteVector3.h" #include "ThirdParty/GTEngine/Mathematics/GteLine.h" #include "ThirdParty/GTEngine/Mathematics/GteCircle3.h" +THIRD_PARTY_INCLUDES_END +template +gte::Vector2 Convert(const FVector2& Vec) +{ + return gte::Vector2({ Vec.X, Vec.Y}); +} + +template +FVector2 Convert(const gte::Vector2& Vec) +{ + return FVector2(Vec[0], Vec[1]); +} template gte::Vector3 Convert(const FVector3& Vec) diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/MinVolumeBox3.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/MinVolumeBox3.cpp index f1a14117f085..ca28ad63700a 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/MinVolumeBox3.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/MinVolumeBox3.cpp @@ -18,6 +18,8 @@ THIRD_PARTY_INCLUDES_END #pragma warning(pop) #endif +#include "GteUtil.h" + template struct TMinVolumeBox3Internal { @@ -71,6 +73,14 @@ struct TMinVolumeBox3Internal bSolutionOK = true; } + // if resulting box is not finite, something went wrong, just return an empty box + FVector3d Extents = Convert(MinimalBox.extent); + if (!FMathd::IsFinite(Extents.SquaredLength())) + { + bSolutionOK = false; + MinimalBox = gte::OrientedBox3(); + } + Result.Frame = TFrame3( FVector3((RealType)MinimalBox.center[0], (RealType)MinimalBox.center[1], (RealType)MinimalBox.center[2]), FVector3((RealType)MinimalBox.axis[0][0], (RealType)MinimalBox.axis[0][1], (RealType)MinimalBox.axis[0][2]), diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/ConvexHull2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/ConvexHull2.h new file mode 100644 index 000000000000..3282ab8ac7ea --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/ConvexHull2.h @@ -0,0 +1,117 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +// Adapted from GteConvexHull2.h from GTEngine; see Private/ThirdParty/GTEngine/Mathematics/GteConvexHull2.h + +#pragma once + +#include "CoreMinimal.h" + +#include "MathUtil.h" +#include "IndexTypes.h" +#include "PlaneTypes.h" +#include "LineTypes.h" +#include "Polygon2.h" + + +template +class GEOMETRYALGORITHMS_API TConvexHull2 +{ +public: + + /** + * Generate convex hull as long as input is not degenerate + * If input is degenerate, this will return false, and caller can call GetDimension() + * to determine whether the points were collinear, or all the same point + * + * @param NumPoints Number of points to consider + * @param GetPointFunc Function providing array-style access into points + * @param FilterFunc Optional filter to include only a subset of the points in the output hull + * @return true if hull was generated, false if points span < 2 dimensions + */ + bool Solve(int32 NumPoints, TFunctionRef(int32)> GetPointFunc, TFunctionRef FilterFunc = [](int32 Idx) {return true;}); + + /** + * Generate convex hull as long as input is not degenerate + * If input is degenerate, this will return false, and caller can call GetDimension() + * to determine whether the points were collinear, or all the same point + * + * @param Points Array of points to consider + * @param Filter Optional filter to include only a subset of the points in the output hull + * @return true if hull was generated, false if points span < 2 dimensions + */ + bool Solve(TArrayView> Points, TFunctionRef FilterFunc) + { + return Solve(Points.Num(), [&Points](int32 Idx) + { + return Points[Idx]; + }, FilterFunc); + } + + // default FilterFunc version of the above Solve(); workaround for clang bug https://bugs.llvm.org/show_bug.cgi?id=25333 + /** + * Generate convex hull as long as input is not degenerate + * If input is degenerate, this will return false, and caller can call GetDimension() + * to determine whether the points were collinear, or all the same point + * + * @param Points Array of points to consider + * @return true if hull was generated, false if points span < 2 dimensions + */ + bool Solve(TArrayView> Points) + { + return Solve(Points.Num(), [&Points](int32 Idx) + { + return Points[Idx]; + }, [](int32 Idx) {return true;}); + } + + /** @return true if convex hull is available */ + bool IsSolutionAvailable() const + { + return Dimension == 2; + } + + /** + * Empty any previously-computed convex hull data. Frees the hull memory. + * Note: You do not need to call this before calling Solve() with new data. + */ + void Empty() + { + Dimension = 0; + NumUniquePoints = 0; + Hull.Empty(); + } + + /** @return Number of dimensions spanned by the input points. */ + inline int32 GetDimension() const + { + return Dimension; + } + + /** Number of unique points considered by convex hull construction (excludes exact duplicate points and filtered-out points) */ + inline int32 GetNumUniquePoints() const + { + return NumUniquePoints; + } + + /** @return the calculated polygon vertices, as indices into the point set passed to Solve() */ + TArray const& GetPolygonIndices() const + { + ensure(IsSolutionAvailable()); + return Hull; + } + +protected: + + // divide-and-conquer algorithm + void GetHull(TFunctionRef(int32)> GetPointFunc, TArray& Merged, int32& IdxFirst, int32& IdxLast); + void Merge(TFunctionRef(int32)> GetPointFunc, TArray& Merged, int32 j0, int32 j1, int32 j2, int32 j3, int32& i0, int32& i1); + void GetTangent(TFunctionRef(int32)> GetPointFunc, TArray& Merged, int32 j0, int32 j1, int32 j2, int32 j3, int32& i0, int32& i1); + + int32 Dimension = 0; + int32 NumUniquePoints = 0; + TArray Hull; +}; + +typedef TConvexHull2 FConvexHull2f; +typedef TConvexHull2 FConvexHull2d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/ConvexHull3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/ConvexHull3.h index ce9fc5ce57f2..970a950c5110 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/ConvexHull3.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/ConvexHull3.h @@ -5,6 +5,8 @@ #include "CoreMinimal.h" #include "VectorTypes.h" #include "IndexTypes.h" +#include "LineTypes.h" +#include "PlaneTypes.h" #include "Templates/PimplPtr.h" template struct TConvexHull3Internal; @@ -13,30 +15,126 @@ template struct TConvexHull3Internal; * Calculate the Convex Hull of a 3D point set as a Triangle Mesh */ template -class TConvexHull3 +class GEOMETRYALGORITHMS_API TConvexHull3 { public: /** - * Calculate the Convex Hull for the given point set. - * @param bUseExactComputation If true, high-precision Rational number types are used for the calculation, rather than doubles. This is slower but more reliable. - * @return true if convex hull was found + * Generate convex hull as long as input is not degenerate + * If input is degenerate, this will return false, and caller can call GetDimension() + * to determine whether the points were coplanar, collinear, or all the same point + * + * @param Points Array of points to consider + * @param Filter Optional filter to include only a subset of the points in the output hull + * @return true if hull was generated, false if points span < 2 dimensions */ - bool Solve(int32 NumPoints, TFunctionRef(int32)> GetPointFunc, bool bUseExactComputation = true); + bool Solve(int32 NumPoints, TFunctionRef(int32)> GetPointFunc, TFunctionRef FilterFunc = [](int32 Idx) {return true;}); + + /** + * Generate convex hull as long as input is not degenerate + * If input is degenerate, this will return false, and caller can call GetDimension() + * to determine whether the points were collinear, or all the same point + * + * @param NumPoints Number of points to consider + * @param GetPointFunc Function providing array-style access into points + * @param FilterFunc Optional filter to include only a subset of the points in the output hull + * @return true if hull was generated, false if points span < 2 dimensions + */ + bool Solve(TArrayView> Points, TFunctionRef FilterFunc) + { + return Solve(Points.Num(), [&Points](int32 Idx) + { + return Points[Idx]; + }, FilterFunc); + } + + // default FilterFunc version of the above Solve(); workaround for clang bug https://bugs.llvm.org/show_bug.cgi?id=25333 + /** + * Generate convex hull as long as input is not degenerate + * If input is degenerate, this will return false, and caller can call GetDimension() + * to determine whether the points were collinear, or all the same point + * + * @param NumPoints Number of points to consider + * @param GetPointFunc Function providing array-style access into points + * @return true if hull was generated, false if points span < 2 dimensions + */ + bool Solve(TArrayView> Points) + { + return Solve(Points.Num(), [&Points](int32 Idx) + { + return Points[Idx]; + }, [](int32 Idx) {return true;}); + } /** @return true if convex hull is available */ - bool IsSolutionAvailable() const; + bool IsSolutionAvailable() const + { + return Dimension == 3; + } /** * Call TriangleFunc for each triangle of the Convex Hull. The triangles index into the point set passed to Solve() */ - void GetTriangles(TFunctionRef TriangleFunc); + void GetTriangles(TFunctionRef TriangleFunc) + { + for (FIndex3i Triangle : Hull) + { + TriangleFunc(Triangle); + } + } + /** @return convex hull triangles */ + TArray const& GetTriangles() const + { + return Hull; + } + + /** + * Empty any previously-computed convex hull data. Frees the hull memory. + * Note: You do not need to call this before calling Generate() with new data. + */ + void Empty() + { + Dimension = 0; + NumUniquePoints = 0; + Hull.Empty(); + } + + /** + * Number of dimensions spanned by the input points. + */ + inline int GetDimension() const + { + return Dimension; + } + /** @return If GetDimension() returns 1, this returns the line the data was on. Otherwise result is not meaningful. */ + inline TLine3 const& GetLine() const + { + return Line; + } + /** @return If GetDimension() returns 2, this returns the plane the data was on. Otherwise result is not meaningful. */ + inline TPlane3 const& GetPlane() const + { + return Plane; + } + + /** @return Number of unique points considered by convex hull construction (excludes exact duplicate points and filtered-out points) */ + int GetNumUniquePoints() const + { + return NumUniquePoints; + } protected: - void Initialize(int32 NumPoints, bool bUseExactComputation); + // Incrementally insert ith point + void Insert(TFunctionRef(int32)> GetPointFunc, int Idx); - TPimplPtr> Internal; + int32 Dimension; + TLine3 Line; + TPlane3 Plane; + + int NumUniquePoints = 0; + TArray Hull; + }; typedef TConvexHull3 FConvexHull3f; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/ExactPredicates.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/ExactPredicates.h index c03eec193791..de3a977bd6df 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/ExactPredicates.h +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/ExactPredicates.h @@ -19,6 +19,9 @@ namespace ExactPredicates double GEOMETRYALGORITHMS_API Orient2DInexact(double *PA, double *PB, double *PC); double GEOMETRYALGORITHMS_API Orient2D(double *PA, double *PB, double *PC); + double GEOMETRYALGORITHMS_API Orient3DInexact(double* PA, double* PB, double* PC, double* PD); + double GEOMETRYALGORITHMS_API Orient3D(double* PA, double* PB, double* PC, double* PD); + /** * @return value indicating which side of line AB point C is on, or 0 if ABC are collinear */ @@ -31,5 +34,18 @@ namespace ExactPredicates return Orient2D(PA, PB, PC); } + /** + * @return value indicating which side of triangle ABC point D is on, or 0 if ABCD are coplanar + */ + template + double Orient3D(const VectorType& A, const VectorType& B, const VectorType& C, const VectorType& D) + { + double PA[3]{ A.X, A.Y, A.Z }; + double PB[3]{ B.X, B.Y, B.Z }; + double PC[3]{ C.X, C.Y, C.Z }; + double PD[3]{ D.X, D.Y, D.Z }; + return Orient3D(PA, PB, PC, PD); + } + // TODO: all remaining predicates }; // namespace ExactPredicates diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/DynamicMeshToMeshDescription.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/DynamicMeshToMeshDescription.cpp index 238c936b3348..99073a053ee2 100644 --- a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/DynamicMeshToMeshDescription.cpp +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/DynamicMeshToMeshDescription.cpp @@ -168,7 +168,8 @@ void FDynamicMeshToMeshDescription::Convert(const FDynamicMesh3* MeshIn, FMeshDe { if (MeshIn->HasAttributes()) { - Convert_SharedInstances(MeshIn, MeshOut); + //Convert_SharedInstances(MeshIn, MeshOut); + Convert_NoSharedInstances(MeshIn, MeshOut); } else { @@ -453,37 +454,53 @@ void FDynamicMeshToMeshDescription::Convert_NoSharedInstances(const FDynamicMesh } } + int32 NumUVLayers = MeshIn->HasAttributes() ? MeshIn->Attributes()->NumUVLayers() : 0; + Builder.SetNumUVLayers(NumUVLayers); + + // cache the UV layers + TArray UVLayers; + for (int32 k = 0; k < NumUVLayers; ++k) + { + UVLayers.Add( MeshIn->Attributes()->GetUVLayer(k) ); + } + + TArray UVTris; + UVTris.SetNum(NumUVLayers); - FVertexID TriVertices[3]; - FVector2D TriUVs[3]; - FVector TriNormals[3]; for (int TriID : MeshIn->TriangleIndicesItr()) { FIndex3i Triangle = MeshIn->GetTriangle(TriID); - FVector2D* UseUVs = nullptr; - if (UVOverlay != nullptr) + FVertexID TriVertices[3] = { MapV[Triangle[0]] , MapV[Triangle[1]] , MapV[Triangle[2]] }; + FVertexInstanceID TriVertInstances[3]; + + // look up normal and UV triangles + FIndex3i NormalTri = (NormalOverlay) ? NormalOverlay->GetTriangle(TriID) : FIndex3i::Invalid(); + for (int32 k = 0; k < NumUVLayers; ++k) { - FIndex3i UVTriangle = UVOverlay->GetTriangle(TriID); - TriUVs[0] = (FVector2D)UVOverlay->GetElement(UVTriangle[0]); - TriUVs[1] = (FVector2D)UVOverlay->GetElement(UVTriangle[1]); - TriUVs[2] = (FVector2D)UVOverlay->GetElement(UVTriangle[2]); - UseUVs = TriUVs; + UVTris[k] = UVLayers[k]->GetTriangle(TriID); } - FVector* UseNormals = nullptr; - if (NormalOverlay != nullptr) + // create new vtx instances and set attributes for each triangle + for (int32 j = 0; j < 3; ++j) { - FIndex3i NormalTriangle = NormalOverlay->GetTriangle(TriID); - TriNormals[0] = (FVector)NormalOverlay->GetElement(NormalTriangle[0]); - TriNormals[1] = (FVector)NormalOverlay->GetElement(NormalTriangle[1]); - TriNormals[2] = (FVector)NormalOverlay->GetElement(NormalTriangle[2]); - UseNormals = TriNormals; - } + FVertexInstanceID NewInstanceID = Builder.AppendInstance(TriVertices[j]); + TriVertInstances[j] = NewInstanceID; - TriVertices[0] = MapV[Triangle[0]]; - TriVertices[1] = MapV[Triangle[1]]; - TriVertices[2] = MapV[Triangle[2]]; + FVector TriVertNormal = FVector::UpVector; + if (NormalOverlay && NormalOverlay->IsElement(NormalTri[j])) + { + TriVertNormal = (FVector)NormalOverlay->GetElement(NormalTri[j]); + } + Builder.SetInstanceNormal(NewInstanceID, TriVertNormal); + + for (int32 k = 0; k < NumUVLayers; ++k) + { + Builder.SetInstanceUV(NewInstanceID, + UVLayers[k]->IsElement(UVTris[k][j]) ? (FVector2D)UVLayers[k]->GetElement(UVTris[k][j]) : FVector2D::ZeroVector, + k); + } + } // transfer material index to MeshDescription polygon group (by convention) FPolygonGroupID UsePolygonGroupID = ZeroPolygonGroupID; @@ -494,14 +511,11 @@ void FDynamicMeshToMeshDescription::Convert_NoSharedInstances(const FDynamicMesh UsePolygonGroupID = FPolygonGroupID(MaterialID); } - FPolygonID NewPolygonID = Builder.AppendTriangle(TriVertices, UsePolygonGroupID, UseUVs, UseNormals); + FPolygonID NewPolygonID = Builder.AppendTriangle(TriVertInstances[0], TriVertInstances[1], TriVertInstances[2], UsePolygonGroupID); if (bCopyGroupToPolyGroup) { Builder.SetPolyGroupID(NewPolygonID, MeshIn->GetTriangleGroup(TriID)); } } - - // set to hard edges - //PolyMeshIn->SetAllEdgesHardness(true); } diff --git a/Engine/Plugins/Experimental/HairStrands/Shaders/Private/NiagaraDirectSolver.ush b/Engine/Plugins/Experimental/HairStrands/Shaders/Private/NiagaraDirectSolver.ush new file mode 100644 index 000000000000..8fa77ecab84d --- /dev/null +++ b/Engine/Plugins/Experimental/HairStrands/Shaders/Private/NiagaraDirectSolver.ush @@ -0,0 +1,1031 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +/*============================================================================= + NiagaraStrandsPhysics.ush +=============================================================================*/ + +#if GPU_SIMULATION + +/* ----------------------------------------------------------------- + * Shared memory for datas that will be accessed within the constraints + * ----------------------------------------------------------------- + */ + +groupshared float3 SharedNodePosition[THREADGROUP_SIZE]; +groupshared float4 SharedNodeOrientation[THREADGROUP_SIZE]; + +groupshared float SharedInverseMass[THREADGROUP_SIZE]; +groupshared float SharedInverseInertia[THREADGROUP_SIZE]; + +groupshared float3 SharedPreviousPosition[THREADGROUP_SIZE]; +groupshared float4 SharedPreviousOrientation[THREADGROUP_SIZE]; + +//groupshared float3x3 SharedMatrixValues[THREADGROUP_SIZE*3]; +groupshared float4 SharedTridiagValues[THREADGROUP_SIZE]; +groupshared float4 SharedTridiagResult[THREADGROUP_SIZE]; + +/* ----------------------------------------------------------------- + * Shared memory for datas that will be accessed within the constraints + * ----------------------------------------------------------------- + */ + #define Test_QUATERNION_IDENTITY float4(0,0,0,1) +#define Test_SMALL_NUMBER 1e-8 + +float4 Test_NormalizeQuat(in float4 Quat) +{ + float SquaredNorm = dot(Quat,Quat); + return (SquaredNorm >= Test_SMALL_NUMBER) ? Quat / sqrt(SquaredNorm) : Test_QUATERNION_IDENTITY; +} + +float4 Test_InverseQuat(in float4 Quat) +{ + return float4(-Quat.x,-Quat.y,-Quat.z,Quat.w); +} + +float4 Test_MultiplyQuat(in float4 QuatA, in float4 QuatB) +{ + return float4( + QuatB.xyz * QuatA.w + QuatA.xyz * QuatB.w + cross(QuatA.xyz, QuatB.xyz), + QuatA.w * QuatB.w - dot(QuatA.xyz, QuatB.xyz)); +} + +float3 Test_RotateVectorByQuat(in float3 Vector, in float4 Quat) +{ + float3 T = 2.0 * cross(Quat.xyz,Vector); + return Vector + Quat.w * T + cross(Quat.xyz,T); +} + +float4 Test_FindQuatBetweenHelper(in float3 A, in float3 B, in float NormAB) +{ + float W = NormAB + dot(A,B); + float4 Quat = (W>1e-6*NormAB) ? float4(A.y*B.z-A.z*B.y,A.z*B.x-A.x*B.z,A.x*B.y-A.y*B.x,W) : + (abs(A.x) > abs(A.y)) ? float4(-A.z,0.0,A.x,0.0) : float4(0.0,-A.z,A.y,0.0); + + return Test_NormalizeQuat(Quat); +} + +float4 Test_FindQuatBetweenInternal(in float3 An, in float3 Bn) +{ + float3 Cn = normalize(An+Bn); + return float4(cross(An,Cn),dot(An,Cn)); +} + +float4 Test_FindQuatBetweenNormals(in float3 NormalA, in float3 NormalB) +{ + float NormAB = 1.0; + return Test_FindQuatBetweenHelper(NormalA,NormalB,NormAB); +} + +void Test_ComputeMaterialFrame(in int StrandsSize) +{ + const int LocalIndex = (GGroupThreadId.x % StrandsSize); + if( LocalIndex == 0 ) + { + float4 NodeQuaternion = SharedNodeOrientation[GGroupThreadId.x]; + float3 TangentPrev = normalize(SharedPreviousPosition[GGroupThreadId.x+1] - SharedPreviousPosition[GGroupThreadId.x]); + float3 TangentNext = TangentPrev; + + for (int EdgeIndex = 1, EdgeEnd = StrandsSize-1; EdgeIndex < EdgeEnd; ++EdgeIndex) + { + TangentPrev = TangentNext; + TangentNext = normalize(SharedPreviousPosition[GGroupThreadId.x+EdgeIndex+1] - SharedPreviousPosition[GGroupThreadId.x+EdgeIndex]); + + NodeQuaternion = Test_NormalizeQuat( Test_MultiplyQuat( Test_FindQuatBetweenNormals(TangentPrev,TangentNext), NodeQuaternion ) ); + SharedNodeOrientation[GGroupThreadId.x+EdgeIndex] = NodeQuaternion; + GroupMemoryBarrier(); + } + GroupMemoryBarrier(); + } + GroupMemoryBarrier(); +} + + +void InverseMatrix(in float3x3 Input, out float3x3 Output) +{ + float det = Input[0][0] * (Input[1][1] * Input[2][2] - Input[2][1] * Input[1][2]) - + Input[0][1] * (Input[1][0] * Input[2][2] - Input[1][2] * Input[2][0]) + + Input[0][2] * (Input[1][0] * Input[2][1] - Input[1][1] * Input[2][0]); + + float invdet = 1 / det; + + Output[0][0] = (Input[1][1] * Input[2][2] - Input[2][1] * Input[1][2]) * invdet; + Output[0][1] = (Input[0][2] * Input[2][1] - Input[0][1] * Input[2][2]) * invdet; + Output[0][2] = (Input[0][1] * Input[1][2] - Input[0][2] * Input[1][1]) * invdet; + Output[1][0] = (Input[1][2] * Input[2][0] - Input[1][0] * Input[2][2]) * invdet; + Output[1][1] = (Input[0][0] * Input[2][2] - Input[0][2] * Input[2][0]) * invdet; + Output[1][2] = (Input[1][0] * Input[0][2] - Input[0][0] * Input[1][2]) * invdet; + Output[2][0] = (Input[1][0] * Input[2][1] - Input[2][0] * Input[1][1]) * invdet; + Output[2][1] = (Input[2][0] * Input[0][1] - Input[0][0] * Input[2][1]) * invdet; + Output[2][2] = (Input[0][0] * Input[1][1] - Input[1][0] * Input[0][1]) * invdet; +} + +void SolveTridiagSerial2() +{ + if(GGroupThreadId.x == 0) + { + { + SharedTridiagValues[0].z = SharedTridiagValues[0].z / SharedTridiagValues[0].y; + GroupMemoryBarrier(); + SharedTridiagValues[0].w = SharedTridiagValues[0].w / SharedTridiagValues[0].y; + GroupMemoryBarrier(); + + for(int i = 1; i < THREADGROUP_SIZE; ++i) + { + if(i < (THREADGROUP_SIZE-1)) + { + SharedTridiagValues[i].z = SharedTridiagValues[i].z / ( SharedTridiagValues[i].y - SharedTridiagValues[i-1].z * SharedTridiagValues[i-1].x); + GroupMemoryBarrier(); + } + + SharedTridiagValues[i].w = (SharedTridiagValues[i].w -SharedTridiagValues[i-1].w * SharedTridiagValues[i].x) / ( SharedTridiagValues[i].y - SharedTridiagValues[i-1].z * SharedTridiagValues[i-1].x); + GroupMemoryBarrier(); + } + } + { + for(int i = THREADGROUP_SIZE-2; i >= 0; --i) + { + SharedTridiagValues[i].w = SharedTridiagValues[i].w - SharedTridiagValues[i].z * SharedTridiagValues[i+1].w; + GroupMemoryBarrier(); + } + } + } + GroupMemoryBarrier(); +} + +void SolveTridiagSerial() +{ + if(GGroupThreadId.x == 0) + { + { + for(int i = 1; i < THREADGROUP_SIZE; ++i) + { + const float W = SharedTridiagValues[i].x / SharedTridiagValues[i-1].y; + SharedTridiagValues[i].y -= W * SharedTridiagValues[i-1].z; + GroupMemoryBarrier(); + + SharedTridiagValues[i].w -= W * SharedTridiagValues[i-1].w; + GroupMemoryBarrier(); + } + } + SharedTridiagValues[THREADGROUP_SIZE-1].w = SharedTridiagValues[THREADGROUP_SIZE-1].w / SharedTridiagValues[THREADGROUP_SIZE-1].y; + GroupMemoryBarrier(); + { + for(int i = THREADGROUP_SIZE-2; i >= 0; --i) + { + SharedTridiagValues[i].w = (SharedTridiagValues[i].w - SharedTridiagValues[i].z * SharedTridiagValues[i+1].w) / SharedTridiagValues[i].y; + GroupMemoryBarrier(); + } + } + } + GroupMemoryBarrier(); +} + + +void SolveConjugateResidual() +{ + float X = 0.0; + + SharedTridiagResult[GGroupThreadId.x] = float4(0.0,0.0,0.0,0.0); + GroupMemoryBarrier(); + + const int PrevIdx = (GGroupThreadId.x == 0) ? GGroupThreadId.x : GGroupThreadId.x-1; + const int NextIdx = (GGroupThreadId.x == THREADGROUP_SIZE-1) ? GGroupThreadId.x : GGroupThreadId.x+1; + + SharedTridiagResult[GGroupThreadId.x].y = SharedTridiagValues[GGroupThreadId.x].w; + GroupMemoryBarrier(); + + SharedTridiagResult[GGroupThreadId.x].z = SharedTridiagResult[GGroupThreadId.x].y / SharedTridiagValues[GGroupThreadId.x].y; + SharedTridiagResult[GGroupThreadId.x].w = SharedTridiagResult[GGroupThreadId.x].z; + GroupMemoryBarrier(); + { + + for(int ii = 0; ii < 10; ++ii) + { + SharedTridiagResult[GGroupThreadId.x].x = SharedTridiagValues[GGroupThreadId.x].x * SharedTridiagResult[PrevIdx].w+ + SharedTridiagValues[GGroupThreadId.x].y * SharedTridiagResult[GGroupThreadId.x].w+ + SharedTridiagValues[GGroupThreadId.x].z * SharedTridiagResult[NextIdx].w; + GroupMemoryBarrier(); + + SharedTridiagResult[GGroupThreadId.x].y = SharedTridiagValues[GGroupThreadId.x].x * SharedTridiagResult[PrevIdx].z+ + SharedTridiagValues[GGroupThreadId.x].y * SharedTridiagResult[GGroupThreadId.x].z+ + SharedTridiagValues[GGroupThreadId.x].z * SharedTridiagResult[NextIdx].z; + GroupMemoryBarrier(); + + float rkArk = 0.0; + float ApMAp = 0.0; + + [unroll] + for(int kk = 0; kk < THREADGROUP_SIZE; ++kk) + { + rkArk += SharedTridiagResult[kk].z * SharedTridiagResult[kk].y; + ApMAp += SharedTridiagResult[kk].x * SharedTridiagResult[kk].x / SharedTridiagValues[kk].y; + } + + float Alpha = (ApMAp != 0.0) ? rkArk / ApMAp : 0.0; + X += Alpha * SharedTridiagResult[GGroupThreadId.x].w; + + SharedTridiagResult[GGroupThreadId.x].z -= Alpha * SharedTridiagResult[GGroupThreadId.x].x / SharedTridiagValues[GGroupThreadId.x].y; + GroupMemoryBarrier(); + + SharedTridiagResult[GGroupThreadId.x].y = SharedTridiagValues[GGroupThreadId.x].x * SharedTridiagResult[PrevIdx].z+ + SharedTridiagValues[GGroupThreadId.x].y * SharedTridiagResult[GGroupThreadId.x].z+ + SharedTridiagValues[GGroupThreadId.x].z * SharedTridiagResult[NextIdx].z; + GroupMemoryBarrier(); + + float rkArkn = 0.0; + + [unroll] + for(int ll = 0; ll < THREADGROUP_SIZE; ++ll) + { + rkArkn += SharedTridiagResult[ll].z * SharedTridiagResult[ll].y; + } + float Beta = (rkArk != 0.0) ? rkArkn / rkArk : 0.0; + SharedTridiagResult[GGroupThreadId.x].w = SharedTridiagResult[GGroupThreadId.x].z + Beta * SharedTridiagResult[GGroupThreadId.x].w; + //SharedTridiagResult[GGroupThreadId.x].x = SharedTridiagResult[GGroupThreadId.x].y + Beta * SharedTridiagResult[GGroupThreadId.x].x; + GroupMemoryBarrier(); + } + } + + SharedTridiagValues[GGroupThreadId.x].w = X; +} + + +void SolveConjugateGradient() +{ + float X = 0.0; + + SharedTridiagResult[GGroupThreadId.x] = float4(0.0,0.0,0.0,0.0); + GroupMemoryBarrier(); + + const int PrevIdx = (GGroupThreadId.x == 0) ? GGroupThreadId.x : GGroupThreadId.x-1; + const int NextIdx = (GGroupThreadId.x == THREADGROUP_SIZE-1) ? GGroupThreadId.x : GGroupThreadId.x+1; + + SharedTridiagResult[GGroupThreadId.x].y = SharedTridiagValues[GGroupThreadId.x].w; + GroupMemoryBarrier(); + + SharedTridiagResult[GGroupThreadId.x].z = SharedTridiagResult[GGroupThreadId.x].y / SharedTridiagValues[GGroupThreadId.x].y; + SharedTridiagResult[GGroupThreadId.x].w = SharedTridiagResult[GGroupThreadId.x].z; + GroupMemoryBarrier(); + { + + for(int ii = 0; ii < 10; ++ii) + { + SharedTridiagResult[GGroupThreadId.x].x = SharedTridiagValues[GGroupThreadId.x].x * SharedTridiagResult[PrevIdx].w+ + SharedTridiagValues[GGroupThreadId.x].y * SharedTridiagResult[GGroupThreadId.x].w+ + SharedTridiagValues[GGroupThreadId.x].z * SharedTridiagResult[NextIdx].w; + GroupMemoryBarrier(); + + float rkzko = 0.0; + float pkApk = 0.0; + + [unroll] + for(int kk = 0; kk < THREADGROUP_SIZE; ++kk) + { + rkzko += SharedTridiagResult[kk].y * SharedTridiagResult[kk].z; + pkApk += SharedTridiagResult[kk].x * SharedTridiagResult[kk].w; + } + + float Alpha = (pkApk != 0.0) ? rkzko / pkApk : 0.0; + X += Alpha * SharedTridiagResult[GGroupThreadId.x].w; + + SharedTridiagResult[GGroupThreadId.x].y -= Alpha * SharedTridiagResult[GGroupThreadId.x].x; + GroupMemoryBarrier(); + SharedTridiagResult[GGroupThreadId.x].z = SharedTridiagResult[GGroupThreadId.x].y / SharedTridiagValues[GGroupThreadId.x].y; + GroupMemoryBarrier(); + + float rkzkn = 0.0; + + [unroll] + for(int ll = 0; ll < THREADGROUP_SIZE; ++ll) + { + rkzkn += SharedTridiagResult[ll].y * SharedTridiagResult[ll].z; + } + float Beta = (rkzko != 0.0) ? rkzkn / rkzko : 0.0; + SharedTridiagResult[GGroupThreadId.x].w = SharedTridiagResult[GGroupThreadId.x].z + Beta * SharedTridiagResult[GGroupThreadId.x].w; + GroupMemoryBarrier(); + } + } + + SharedTridiagValues[GGroupThreadId.x].w = X; +} + +void SolveTridiagSystem() +{ + float lTemp = 0.0, uTemp = 0.0, hTemp = 0.0; + + [unroll] + for(int span = 1; span < THREADGROUP_SIZE; span *= 2) + { + if( (GGroupThreadId.x - span) >= 0) + { + lTemp = (SharedTridiagValues[GGroupThreadId.x - span].y != 0.0) ? -SharedTridiagValues[GGroupThreadId.x].x / SharedTridiagValues[GGroupThreadId.x - span].y : 0.0; + } + else + { + lTemp = 0.0; + } + if( (GGroupThreadId.x + span) < THREADGROUP_SIZE) + { + uTemp = (SharedTridiagValues[GGroupThreadId.x + span].y != 0.0) ? -SharedTridiagValues[GGroupThreadId.x].z / SharedTridiagValues[GGroupThreadId.x + span].y : 0.0; + } + else + { + uTemp = 0.0; + } + GroupMemoryBarrier(); + if( (GGroupThreadId.x - span) >= 0) + { + SharedTridiagValues[GGroupThreadId.x].y += lTemp * SharedTridiagValues[GGroupThreadId.x - span].z; + hTemp += lTemp * SharedTridiagValues[GGroupThreadId.x - span].w; + lTemp *= SharedTridiagValues[GGroupThreadId.x - span].x; + } + if( (GGroupThreadId.x + span) < THREADGROUP_SIZE) + { + SharedTridiagValues[GGroupThreadId.x].y += uTemp * SharedTridiagValues[GGroupThreadId.x + span].x; + hTemp += uTemp * SharedTridiagValues[GGroupThreadId.x + span].w; + uTemp *= SharedTridiagValues[GGroupThreadId.x + span].z; + } + GroupMemoryBarrier(); + SharedTridiagValues[GGroupThreadId.x].x = hTemp; + SharedTridiagValues[GGroupThreadId.x].z = uTemp; + SharedTridiagValues[GGroupThreadId.x].w = hTemp; + GroupMemoryBarrier(); + } + SharedTridiagValues[GGroupThreadId.x].w /= SharedTridiagValues[GGroupThreadId.x].y; + GroupMemoryBarrier(); +} + +float SolveTridiag() +{ + const int NumIteration = log2(THREADGROUP_SIZE/2); + + uint DataStride = 1; + + float4 DataNew = {0,0,0,0}; + + [unroll] + for(int i = 0; i < NumIteration; ++i) + { + int RightIdx = GGroupThreadId.x + DataStride; + RightIdx = RightIdx & (THREADGROUP_SIZE-1); + + int LeftIdx = GGroupThreadId.x - DataStride; + LeftIdx = LeftIdx & (THREADGROUP_SIZE-1); + + const float DataLeft = SharedTridiagValues[GGroupThreadId.x].x / SharedTridiagValues[LeftIdx].y; + const float DataRight = SharedTridiagValues[GGroupThreadId.x].z / SharedTridiagValues[RightIdx].y; + + DataNew.y = SharedTridiagValues[GGroupThreadId.x].y - SharedTridiagValues[LeftIdx].z * DataLeft - + SharedTridiagValues[RightIdx].x * DataRight; + DataNew.w = SharedTridiagValues[GGroupThreadId.x].w - SharedTridiagValues[LeftIdx].w * DataLeft - + SharedTridiagValues[RightIdx].w * DataRight; + DataNew.x = -SharedTridiagValues[LeftIdx].x * DataLeft; + DataNew.z = -SharedTridiagValues[RightIdx].z * DataRight; + + GroupMemoryBarrier(); + + SharedTridiagValues[GGroupThreadId.x] = DataNew; + DataStride *= 2; + + GroupMemoryBarrier(); + } + + const int LeftIdx = GGroupThreadId.x & (DataStride-1); + const int RightIdx = LeftIdx + DataStride; + + const float DataValue = SharedTridiagValues[RightIdx].y * SharedTridiagValues[LeftIdx].y - + SharedTridiagValues[LeftIdx].z * SharedTridiagValues[RightIdx].x; + + return (GGroupThreadId.x < DataStride) ? + (SharedTridiagValues[RightIdx].y * SharedTridiagValues[LeftIdx].w - + SharedTridiagValues[LeftIdx].z * SharedTridiagValues[RightIdx].w) / DataValue : + (SharedTridiagValues[RightIdx].w * SharedTridiagValues[LeftIdx].y - + SharedTridiagValues[LeftIdx].w * SharedTridiagValues[RightIdx].x) / DataValue; +} + +/* ----------------------------------------------------------------- + * Get the strand parent position + * ----------------------------------------------------------------- + */ + +void GetParentPosition(const bool IsMobile, out float3 ParentPosition) +{ + ParentPosition = SharedNodePosition[IsMobile ? GGroupThreadId.x-1 : GGroupThreadId.x]; +} + +/* ----------------------------------------------------------------- + * Fill the shared datas from the particles + * ----------------------------------------------------------------- + */ + +void ExtractPositionSharedDatas(const bool IsMobile, const float3 InParticlePosition, const float3 InParticleVelocity, const float InParticleMass) +{ + SharedNodePosition[GGroupThreadId.x] = InParticlePosition; + //SharedLocalVelocity[GGroupThreadId.x] = InParticleVelocity; + SharedInverseMass[GGroupThreadId.x] = (InParticleMass != 0.0 && IsMobile) ? 1.0/InParticleMass : 0.0; + + GroupMemoryBarrier(); +} + +void ExtractOrientationSharedDatas(const bool IsMobile, const float4 InParticleQuaternion, const float3 InParticleOmega, const float InParticleInertia) +{ + SharedNodeOrientation[GGroupThreadId.x] = InParticleQuaternion; + //SharedLocalOmega[GGroupThreadId.x] = InParticleOmega; + SharedInverseInertia[GGroupThreadId.x] = (InParticleInertia != 0.0 && IsMobile) ? 1.0/InParticleInertia : 0.0; + + GroupMemoryBarrier(); +} + +/* ----------------------------------------------------------------- + * Report the shared datas onto the particles + * ----------------------------------------------------------------- + */ + +void ReportPositionSharedDatas(const float InDeltaTime, inout float3 OutParticleVelocity, out float3 OutParticlePosition) +{ + const float3 CurrentPosition = SharedNodePosition[GGroupThreadId.x]; + //OutParticleVelocity += (CurrentPosition-OutParticlePosition) / InDeltaTime; + OutParticlePosition = CurrentPosition; +} + +void ReportOrientationSharedDatas(const float InDeltaTime, inout float3 OutParticleOmega, out float4 OutParticleQuaternion) +{ + const float4 CurrentQuaternion = SharedNodeOrientation[GGroupThreadId.x]; + //const float4 DeltaQuat = float4( + // -OutParticleQuaternion.xyz * CurrentQuaternion.w + CurrentQuaternion.xyz * OutParticleQuaternion.w + cross(CurrentQuaternion.xyz, -OutParticleQuaternion.xyz), + // CurrentQuaternion.w * OutParticleQuaternion.w - dot(CurrentQuaternion.xyz, -OutParticleQuaternion.xyz)); + + OutParticleQuaternion = CurrentQuaternion; + + //const float AxisLength = length( DeltaQuat.xyz ); + //const float QuatAngle = 2.0 * atan2(AxisLength,DeltaQuat.w ); + //OutParticleOmega += DeltaQuat.xyz * QuatAngle / (AxisLength*InDeltaTime); +} + +/* ----------------------------------------------------------------- + * Advect the particles shared datas with forces + * ----------------------------------------------------------------- + */ + +void AdvectPositionSharedDatas(const bool IsMobile, const float3 InParticleAcceleration, const float InDeltaTime, inout float3 OutParticleVelocity, inout float3 OutParticlePosition) +{ + const float3 ParticleAcceleration = IsMobile ? InParticleAcceleration : float3(0,0,0); + OutParticleVelocity += ParticleAcceleration * InDeltaTime; + OutParticlePosition += OutParticleVelocity * InDeltaTime; +} + +void AdvectOrientationSharedDatas(const float3 InParticleOmega, const float InDeltaTime, inout float4 OutParticleQuaternion) +{ + OutParticleQuaternion = OutParticleQuaternion + 0.5 * InDeltaTime * float4( + InParticleOmega.xyz * OutParticleQuaternion.w + cross(InParticleOmega.xyz, OutParticleQuaternion.xyz), + - dot(InParticleOmega.xyz, OutParticleQuaternion.xyz) ); + + OutParticleQuaternion = normalize( OutParticleQuaternion ); +} + +/* ----------------------------------------------------------------- + * Hooke spring material + * ----------------------------------------------------------------- + */ + +void StoreHookeSpringConfig(const bool IsMobile, const float InMaterialCompliance, out float OutRestLength) +{ + if (!IsMobile){ + const float3 EdgeDirection = SharedNodePosition[GGroupThreadId.x] - SharedNodePosition[GGroupThreadId.x-1]; + OutRestLength = length(EdgeDirection); + } + else + { + OutRestLength = 0.0; + } +} + +void ComputeHookeSpringWeight(const float InMaterialCompliance, out float OutMaterialWeight) +{ + const float SchurDiagonal = ( SharedInverseMass[GGroupThreadId.x] + SharedInverseMass[GGroupThreadId.x-1] + InMaterialCompliance ); + OutMaterialWeight = ( SchurDiagonal != 0.0 ) ? 1.0 / SchurDiagonal : 0.0; +} + +void EvalHookeSpringCompliance(const float InDeltaTime, const float InYoungModulus, const float InRodThickness, const float InRestLength, out float OutMaterialCompliance) +{ + // Compliance = 1.0 / (k * dt * dt) + // with k = Y * A / L + // A is the cross section area (Pi * R * R), L is the rest length and Y is the young modulus + OutMaterialCompliance = InRestLength*4.0/(InYoungModulus*PI*InRodThickness*InRodThickness*InDeltaTime*InDeltaTime); +} + +void ResetHookeSpringMultiplier(out float OutMaterialMultiplier) +{ + OutMaterialMultiplier = 0; +} + +void UpdateHookeSpringMultiplier(const float InMaterialCompliance, const float InMaterialWeight, const float InRestLength, inout float OutMaterialMultiplier) +{ + float3 EdgeDirection = SharedNodePosition[GGroupThreadId.x] - SharedNodePosition[GGroupThreadId.x-1]; + const float EdgeLength = length(EdgeDirection); + EdgeDirection /= EdgeLength; + + // XPBD lagrange multiplier update : dL = -(C+compliance*L) / (dC * invM * dCt + alpha) + const float DeltaLambda = -((EdgeLength-InRestLength) + OutMaterialMultiplier * InMaterialCompliance) * InMaterialWeight; + + // L += dL + OutMaterialMultiplier += DeltaLambda; + + // XPBD position update : dX = dL * dCt * invM + const float3 PositionDelta = EdgeDirection * DeltaLambda; + + // dX += dX + SharedNodePosition[GGroupThreadId.x] += PositionDelta * SharedInverseMass[GGroupThreadId.x]; + SharedNodePosition[GGroupThreadId.x-1] -= PositionDelta * SharedInverseMass[GGroupThreadId.x-1]; +} + +void SolveHookeSpringMaterial(const float InMaterialCompliance, const float InMaterialWeight, const float InRestLength, const bool InIsRed, inout float OutMaterialMultiplier) +{ + // Process all the red rods + if (InIsRed) + { + UpdateHookeSpringMultiplier(InMaterialCompliance,InMaterialWeight,InRestLength,OutMaterialMultiplier); + } + // Process all the black rods + GroupMemoryBarrier(); + if (!InIsRed) + { + UpdateHookeSpringMultiplier(InMaterialCompliance,InMaterialWeight,InRestLength,OutMaterialMultiplier); + } + GroupMemoryBarrier(); +} + +/* ----------------------------------------------------------------- + * Stretch/shear rod material + * ----------------------------------------------------------------- + */ + + void ComputeStretchShearWeight(const float InMaterialCompliance, const float InRestLength, out float OutMaterialWeight) +{ + //const float SchurDiagonal = ( SharedInverseMass[GGroupThreadId.x] + SharedInverseMass[GGroupThreadId.x-1] ) / (InRestLength*InRestLength) + + // SharedInverseInertia[GGroupThreadId.x-1]*4.0 + InMaterialCompliance; + const float SchurDiagonal = ( SharedInverseMass[GGroupThreadId.x] + SharedInverseMass[GGroupThreadId.x-1] ) / (InRestLength*InRestLength) + + + InMaterialCompliance; + OutMaterialWeight = ( SchurDiagonal != 0.0 ) ? 1.0 / SchurDiagonal : 0.0; +} + +void EvalStretchShearCompliance(const float InDeltaTime, const float InYoungModulus, const float InRodThickness, const float InRestLength, out float OutMaterialCompliance) +{ + // Compliance = 1.0 / (k * dt * dt) + // with k = L * L * (Y * A / L) (the L*L term before is coming from the fact that our constraint is dL/L and not dL) + // A is the cross section area (Pi * R * R), L is the rest length and Y is the young modulus + OutMaterialCompliance = 4.0/(InYoungModulus*PI*InRodThickness*InRestLength*InRodThickness*InDeltaTime*InDeltaTime); +} + +void ResetStretchShearMultiplier(out float3 OutMaterialMultiplier) +{ + OutMaterialMultiplier = float3(0,0,0); +} + +void UpdateStretchShearMultiplier(const float InMaterialCompliance, const float InMaterialWeight, const float InRestLength, inout float3 OutMaterialMultiplier) +{ + float3 EdgeDirection; + float4 q0 = SharedNodeOrientation[GGroupThreadId.x-1]; + EdgeDirection[0] = 2.0 * (q0.x * q0.z + q0.w * q0.y); + EdgeDirection[1] = 2.0 * (q0.y * q0.z - q0.w * q0.x); + EdgeDirection[2] = q0.w * q0.w - q0.x * q0.x - q0.y * q0.y + q0.z * q0.z; + + // XPBD lagrange multiplier update : dL = -(C+compliance*L) / (dC * invM * dCt + alpha) + const float3 DeltaLambda = -((SharedNodePosition[GGroupThreadId.x] - SharedNodePosition[GGroupThreadId.x-1]) / InRestLength - EdgeDirection + + OutMaterialMultiplier * InMaterialCompliance) * InMaterialWeight; + + // L += dL + OutMaterialMultiplier += DeltaLambda; + + // XPBD position update : dX = dL * dCt * invM + const float3 PositionDelta = DeltaLambda/InRestLength; + + // dX += dX + SharedNodePosition[GGroupThreadId.x] += PositionDelta * SharedInverseMass[GGroupThreadId.x]; + SharedNodePosition[GGroupThreadId.x-1] -= PositionDelta * SharedInverseMass[GGroupThreadId.x-1]; + + //const float4 qebar = float4(-q0.y, q0.x, -q0.w, q0.z); + //const float4 QuaternionDelta = -2.0 * float4( DeltaLambda.xyz * qebar.w + cross(DeltaLambda.xyz, qebar.xyz), + // - dot(DeltaLambda.xyz, qebar.xyz) ); + + //q0 += QuaternionDelta * SharedInverseInertia[GGroupThreadId.x-1]; + //SharedNodeOrientation[GGroupThreadId.x-1] = normalize(q0); +} + +void SolveStretchShearMaterial(const float InMaterialCompliance, const float InMaterialWeight, const float InRestLength, const bool InIsRed, inout float3 OutMaterialMultiplier) +{ + // Process all the red rods + if (InIsRed) + { + UpdateStretchShearMultiplier(InMaterialCompliance,InMaterialWeight,InRestLength,OutMaterialMultiplier); + } + // Process all the black rods + GroupMemoryBarrier(); + if (!InIsRed) + { + UpdateStretchShearMultiplier(InMaterialCompliance,InMaterialWeight,InRestLength,OutMaterialMultiplier); + } + GroupMemoryBarrier(); +} + +/* ----------------------------------------------------------------- + * Angular Spring material + * ----------------------------------------------------------------- + */ + + void ComputeAngularSpringWeight(const float InMaterialCompliance, const float InRestLength, out float OutMaterialWeight) +{ + const float SchurDiagonal = SharedInverseInertia[GGroupThreadId.x-1]*4.0 + InMaterialCompliance; + OutMaterialWeight = ( SchurDiagonal != 0.0 ) ? 1.0 / SchurDiagonal : 0.0; +} + +void EvalAngularSpringCompliance(const float InDeltaTime, const float InYoungModulus, const float InRodThickness, const float InRestLength, out float OutMaterialCompliance) +{ + // Compliance = 1.0 / (k * dt * dt) + // with k = L * L * (Y * A / L) (the L*L term before is coming from the fact that our constraint is dL/L and not dL) + // A is the cross section area (Pi * R * R), L is the rest length and Y is the young modulus + OutMaterialCompliance = 4.0/(InYoungModulus*PI*InRodThickness*InRestLength*InRodThickness*InDeltaTime*InDeltaTime); +} + +void ResetAngularSpringMultiplier(out float3 OutMaterialMultiplier) +{ + OutMaterialMultiplier = float3(0,0,0); +} + +void UpdateAngularSpringMultiplier(const float InMaterialCompliance, const float InMaterialWeight, const float InRestLength, inout float3 OutMaterialMultiplier) +{ + float3 EdgeDirection; + float4 q0 = SharedNodeOrientation[GGroupThreadId.x-1]; + EdgeDirection[0] = 2.0 * (q0.x * q0.z + q0.w * q0.y); + EdgeDirection[1] = 2.0 * (q0.y * q0.z - q0.w * q0.x); + EdgeDirection[2] = q0.w * q0.w - q0.x * q0.x - q0.y * q0.y + q0.z * q0.z; + + // XPBD lagrange multiplier update : dL = -(C+compliance*L) / (dC * invM * dCt + alpha) + const float3 DeltaLambda = -(normalize(SharedNodePosition[GGroupThreadId.x] - SharedNodePosition[GGroupThreadId.x-1]) - EdgeDirection + + OutMaterialMultiplier * InMaterialCompliance) * InMaterialWeight; + + // L += dL + OutMaterialMultiplier += DeltaLambda; + + const float4 qebar = float4(-q0.y, q0.x, -q0.w, q0.z); + const float4 QuaternionDelta = -2.0 * float4( DeltaLambda.xyz * qebar.w + cross(DeltaLambda.xyz, qebar.xyz), + - dot(DeltaLambda.xyz, qebar.xyz) ); + + q0 += QuaternionDelta * SharedInverseInertia[GGroupThreadId.x-1]; + SharedNodeOrientation[GGroupThreadId.x-1] = normalize(q0); +} + +void SolveAngularSpringMaterial(const float InMaterialCompliance, const float InMaterialWeight, const float InRestLength, const bool InIsRed, inout float3 OutMaterialMultiplier) +{ + // Process all the red rods + if (InIsRed) + { + UpdateAngularSpringMultiplier(InMaterialCompliance,InMaterialWeight,InRestLength,OutMaterialMultiplier); + } + // Process all the black rods + GroupMemoryBarrier(); + if (!InIsRed) + { + UpdateAngularSpringMultiplier(InMaterialCompliance,InMaterialWeight,InRestLength,OutMaterialMultiplier); + } + GroupMemoryBarrier(); +} + +/* ----------------------------------------------------------------- + * Bend spring rod material + * ----------------------------------------------------------------- + */ + +void UpdateBendSpringMultiplier(const float InMaterialCompliance, const float InMaterialWeight, const float InRestLength, const float3 InRestDirection, inout float3 OutMaterialMultiplier, const bool IsFirst) +{ + float4 ParentFrame = (IsFirst) ? SharedNodeOrientation[GGroupThreadId.x-1] : SharedNodeOrientation[GGroupThreadId.x-2]; + float3 CrossProduct = 2.0 * cross(ParentFrame.xyz,InRestDirection); + float3 EdgeDirection = InRestDirection + ParentFrame.w * CrossProduct + cross(ParentFrame.xyz,CrossProduct); + + // XPBD lagrange multiplier update : dL = -(C+compliance*L) / (dC * invM * dCt + alpha) + const float3 DeltaLambda = -((SharedNodePosition[GGroupThreadId.x] - SharedNodePosition[GGroupThreadId.x-1]) / InRestLength - EdgeDirection / InRestLength + + OutMaterialMultiplier * InMaterialCompliance) * InMaterialWeight; + + // L += dL + OutMaterialMultiplier += DeltaLambda; + + // XPBD position update : dX = dL * dCt * invM + const float3 PositionDelta = DeltaLambda/InRestLength; + + // dX += dX + SharedNodePosition[GGroupThreadId.x] += PositionDelta * SharedInverseMass[GGroupThreadId.x]; + SharedNodePosition[GGroupThreadId.x-1] -= PositionDelta * SharedInverseMass[GGroupThreadId.x-1]; +} + +void SolveBendSpringMaterial(const float InMaterialCompliance, const float InMaterialWeight, const float InRestLength, const float3 InRestDirection, const bool InIsRed, inout float3 OutMaterialMultiplier, const int InStrandSize) +{ + const bool IsFirst = (GGroupThreadId.x % InStrandSize) == 1; + // Process all the red rods + if (InIsRed) + { + UpdateBendSpringMultiplier(InMaterialCompliance,InMaterialWeight,InRestLength,InRestDirection,OutMaterialMultiplier,IsFirst); + } + // Process all the black rods + GroupMemoryBarrier(); + if (!InIsRed) + { + UpdateBendSpringMultiplier(InMaterialCompliance,InMaterialWeight,InRestLength,InRestDirection,OutMaterialMultiplier,IsFirst); + } + GroupMemoryBarrier(); +} + + +/* ----------------------------------------------------------------- + * Parallel transport + * ----------------------------------------------------------------- + */ + +float4 MultiplyQuat_tmp(in float4 QuatA, in float4 QuatB) +{ + return float4( + QuatB.xyz * QuatA.w + QuatA.xyz * QuatB.w + cross(QuatA.xyz, QuatB.xyz), + QuatA.w * QuatB.w - dot(QuatA.xyz, QuatB.xyz)); +} + +float4 FindQuatBetweenHelper_tmp(in float3 A, in float3 B, in float NormAB) +{ + float W = NormAB + dot(A,B); + float4 Quat = (W>1e-6*NormAB) ? float4(A.y*B.z-A.z*B.y,A.z*B.x-A.x*B.z,A.x*B.y-A.y*B.x,W) : + (abs(A.x) > abs(A.y)) ? float4(-A.z,0.0,A.x,0.0) : float4(0.0,-A.z,A.y,0.0); + return normalize(Quat); +} + + void ComputeBishopFrame(const int StrandSize) +{ + const bool IsMobile = (GGroupThreadId.x % StrandSize) != 0; + if (!IsMobile) + { + float4 NodeQuaternion = float4(0,0,0,1);//SharedNodeOrientation[GGroupThreadId.x]; + float3 TangentNext = float3(0,0,1); + float3 TangentPrev = TangentNext; + + float3 PositionNext = SharedNodePosition[GGroupThreadId.x]; + float3 PositionPrev = PositionNext; + + for (int EdgeIndex = 0, EdgeEnd = StrandSize-1; EdgeIndex < EdgeEnd; ++EdgeIndex) + { + PositionPrev = PositionNext; + PositionNext = SharedNodePosition[GGroupThreadId.x+EdgeIndex+1]; + + TangentPrev = TangentNext; + TangentNext = normalize(PositionNext - PositionPrev); + NodeQuaternion = normalize( MultiplyQuat_tmp( FindQuatBetweenHelper_tmp(TangentPrev,TangentNext,1.0), NodeQuaternion ) ); + SharedNodeOrientation[GGroupThreadId.x+EdgeIndex] = NodeQuaternion; + } + } + GroupMemoryBarrier(); +} +/* ----------------------------------------------------------------- + * Bend/twist rod material + * ----------------------------------------------------------------- + */ + + void ComputeBendTwistWeight(const float InMaterialCompliance, const float InRestLength, out float OutMaterialWeight) +{ + const float SchurDiagonal = ( SharedInverseInertia[GGroupThreadId.x] + SharedInverseInertia[GGroupThreadId.x-1] ) * 4.0 / (InRestLength*InRestLength) + InMaterialCompliance; + OutMaterialWeight = ( SchurDiagonal != 0.0 ) ? 1.0 / SchurDiagonal : 0.0; +} + +void EvalBendTwistCompliance(const float InDeltaTime, const float InYoungModulus, const float InRodThickness, const float InRestLength, out float OutMaterialCompliance) +{ + // Compliance = 1.0 / (k * dt * dt) + // with k = (Y * I * L) + // A is the polar moment of inertia (Pi * R * R * R * R / 4), L is the rest length and Y is the young modulus + OutMaterialCompliance = 64.0/(InYoungModulus*PI*InRestLength*InRodThickness*InRodThickness*InRodThickness*InRodThickness*InDeltaTime*InDeltaTime); +} + +void ResetBendTwistMultiplier(out float3 OutMaterialMultiplier) +{ + OutMaterialMultiplier = float3(0,0,0); +} + +void UpdateBendTwistMultiplier(const float InMaterialCompliance, const float InMaterialWeight, const float InRestLength, const float4 InRestDarboux, inout float3 OutMaterialMultiplier) +{ + float4 q0 = SharedNodeOrientation[GGroupThreadId.x-1]; + float4 q1 = SharedNodeOrientation[GGroupThreadId.x]; + const float4 CurrentDarboux = float4( + q1.xyz * q0.w - q0.xyz * q1.w + cross(-q0.xyz, q1.xyz), + q0.w * q1.w - dot(-q0.xyz, q1.xyz)); + const float InverseRestLength = 2.0 / InRestLength; + + const float4 OmegaPlus = (CurrentDarboux + InRestDarboux) * InverseRestLength; + const float4 OmegaMinus = (CurrentDarboux - InRestDarboux) * InverseRestLength; + + float4 DeltaOmega = (dot(OmegaPlus,OmegaPlus) > dot(OmegaMinus,OmegaMinus) ) ? OmegaMinus : OmegaPlus; + DeltaOmega.xyz += OutMaterialMultiplier * InMaterialCompliance; + DeltaOmega.w = 0; + DeltaOmega *= InMaterialWeight; + + OutMaterialMultiplier -= DeltaOmega.xyz; + + SharedNodeOrientation[GGroupThreadId.x-1] += SharedInverseInertia[GGroupThreadId.x-1] * float4( + DeltaOmega.xyz * q1.w + cross(q1.xyz, DeltaOmega.xyz), - dot(q1.xyz, DeltaOmega.xyz)) * InverseRestLength; + SharedNodeOrientation[GGroupThreadId.x] -= SharedInverseInertia[GGroupThreadId.x] * float4( + DeltaOmega.xyz * q0.w + cross(q0.xyz, DeltaOmega.xyz), - dot(q0.xyz, DeltaOmega.xyz)) * InverseRestLength; + + SharedNodeOrientation[GGroupThreadId.x-1] = normalize(SharedNodeOrientation[GGroupThreadId.x-1]); + SharedNodeOrientation[GGroupThreadId.x] = normalize(SharedNodeOrientation[GGroupThreadId.x]); +} + +/*void UpdateBendTwistMultiplier(const float InMaterialCompliance, const float InMaterialWeight, const float InRestLength, const float4 InRestDarboux, inout float3 OutMaterialMultiplier) +{ + float4 q0 = SharedNodeOrientation[GGroupThreadId.x-1]; + float4 q1 = SharedNodeOrientation[GGroupThreadId.x]; + const float4 CurrentDarboux = float4( + q1.xyz * q0.w - q0.xyz * q1.w + cross(-q0.xyz, q1.xyz), + q0.w * q1.w - dot(-q0.xyz, q1.xyz)); + const float InverseRestLength = 2.0 / InRestLength; + + float3 DeltaOmega = (CurrentDarboux.xyz / CurrentDarboux.w - InRestDarboux.xyz / InRestDarboux.w) * InverseRestLength; + DeltaOmega.xyz += OutMaterialMultiplier * InMaterialCompliance; + DeltaOmega *= InMaterialWeight; + + OutMaterialMultiplier -= DeltaOmega.xyz; + + DeltaOmega = DeltaOmega - CurrentDarboux.xyz * dot(CurrentDarboux.xyz,DeltaOmega); + + const float qtu = dot(q0,q1); + const float dto = dot(CurrentDarboux.xyz, DeltaOmega.xyz); + + const float4 q1tmp = qtu * q1; + const float4 q0tmp = qtu * q0; + + const float afac = dot(CurrentDarboux.xyz,DeltaOmega); + + SharedNodeOrientation[GGroupThreadId.x-1] += SharedInverseInertia[GGroupThreadId.x-1] * ( float4( + DeltaOmega.xyz * q1tmp.w + cross(q1tmp.xyz, DeltaOmega.xyz), - dot(q1tmp.xyz, DeltaOmega.xyz)) + dto * q1) * InverseRestLength; + SharedNodeOrientation[GGroupThreadId.x] -= SharedInverseInertia[GGroupThreadId.x] * ( float4( + DeltaOmega.xyz * q0tmp.w + cross(q0tmp.xyz, DeltaOmega.xyz), - dot(q0tmp.xyz, DeltaOmega.xyz)) - dto * q0) * InverseRestLength; + + SharedNodeOrientation[GGroupThreadId.x-1] = normalize(SharedNodeOrientation[GGroupThreadId.x-1]); + SharedNodeOrientation[GGroupThreadId.x] = normalize(SharedNodeOrientation[GGroupThreadId.x]); +}*/ + +void SolveBendTwistMaterial(const float InMaterialCompliance, const float InMaterialWeight, const float InRestLength, const float4 InRestDarboux, const bool InIsRed, inout float3 OutMaterialMultiplier) +{ + // Process all the red rods + if (InIsRed) + { + UpdateBendTwistMultiplier(InMaterialCompliance,InMaterialWeight,InRestLength,InRestDarboux,OutMaterialMultiplier); + } + // Process all the black rods + GroupMemoryBarrier(); + if (!InIsRed) + { + UpdateBendTwistMultiplier(InMaterialCompliance,InMaterialWeight,InRestLength,InRestDarboux,OutMaterialMultiplier); + } + GroupMemoryBarrier(); +} + +/* ----------------------------------------------------------------- + * Follow the leader constraint + * ----------------------------------------------------------------- + */ + + void SolveFollowTheLeaderConstraint(const int InBlockSize, const float InRestLength, const float InDampening, const float InDeltaTime, const float InBending) +{ + const bool IsMobile = (GGroupThreadId.x % InBlockSize) != 0; + if (!IsMobile) + { + const float BendingLimit = 2*InBending*InRestLength; + for (uint i = GGroupThreadId.x+1, end = GGroupThreadId.x+InBlockSize; i < end; ++i) + { + if (i > GGroupThreadId.x+1) + { + float3 EdgeDirection = SharedNodePosition[i] - SharedNodePosition[i-2]; + const float EdgeLength = length(EdgeDirection); + EdgeDirection /= EdgeLength; + if (EdgeLength<=BendingLimit) + { + // Comutes dL + const float DeltaLambda = -(EdgeLength-BendingLimit); + const float3 DeltaPosition = EdgeDirection * DeltaLambda; + + // dX += dX + SharedNodePosition[i] += DeltaPosition; + + const float3 DeltaVelocity = DeltaPosition / InDeltaTime; + + // dV += dV + //SharedLocalVelocity[i] += SharedInverseMass[i] * DeltaVelocity / ( SharedInverseMass[i] + InDampening * SharedInverseMass[i-2]); + //SharedLocalVelocity[i-2] -= InDampening * SharedInverseMass[i-2] * DeltaVelocity / ( SharedInverseMass[i] + InDampening * SharedInverseMass[i-2]); + } + GroupMemoryBarrier(); + } + + + float3 EdgeDirection = SharedNodePosition[i] - SharedNodePosition[i-1]; + const float EdgeLength = length(EdgeDirection); + EdgeDirection /= EdgeLength; + + // Comutes dL + const float DeltaLambda = -(EdgeLength-InRestLength); + const float3 DeltaPosition = EdgeDirection * DeltaLambda; + + // dX += dX + SharedNodePosition[i] += DeltaPosition; + + //SharedNodePosition[i] += SharedInverseMass[i] * DeltaPosition / ( SharedInverseMass[i] + InDampening * SharedInverseMass[i-1]); + //SharedNodePosition[i-1] -= InDampening * SharedInverseMass[i-1] * DeltaPosition / ( SharedInverseMass[i] + InDampening * SharedInverseMass[i-1]); + + const float3 DeltaVelocity = DeltaPosition / InDeltaTime; + + // dV += dV + //SharedLocalVelocity[i] += SharedInverseMass[i] * DeltaVelocity / ( SharedInverseMass[i] + InDampening * SharedInverseMass[i-1]); + //SharedLocalVelocity[i-1] -= InDampening * SharedInverseMass[i-1] * DeltaVelocity / ( SharedInverseMass[i] + InDampening * SharedInverseMass[i-1]); + + GroupMemoryBarrier(); + } + } +} + +/* ----------------------------------------------------------------- + * Static volume collisions against sphere, box... + * ----------------------------------------------------------------- + */ + + float3 ProcessCollisionData( const float3 InDeltaPosition, const float3 InDeltaVelocity, const float3 InCollisionNormal, const float InThicknessSum, + const float InStaticFriction, const float InKineticFriction, const bool IsProjection ) + { + const float DeltaVNormal = dot(InDeltaVelocity,InCollisionNormal); + const float DeltaPNormal = dot(InDeltaPosition,InCollisionNormal)-InThicknessSum; + + if (DeltaPNormal < 0.0) + { + const float DeltaFNormal = IsProjection ? DeltaPNormal : DeltaVNormal; + const float NormDeltaFN = abs(DeltaFNormal); + + const float3 CollisionTangent = InDeltaVelocity - DeltaVNormal * InCollisionNormal; + const float TangentLength = length(CollisionTangent); + + float3 CollisionTangentA = (TangentLength > 0.0) ? CollisionTangent/TangentLength : (abs(InCollisionNormal.x-1.0f) > 0.0) ? float3(1,0,0) : + (abs(InCollisionNormal.y-1.0f) > 0.0) ? float3(0,1,0) : + float3(0,0,1); + float3 CollisionTangentB = cross(InCollisionNormal,CollisionTangentA); + CollisionTangentA = cross(CollisionTangentB,InCollisionNormal); + + const float TangentLengthA = length(CollisionTangentA); + const float TangentLengthB = length(CollisionTangentB); + + CollisionTangentA = (TangentLengthA > 0.0) ? CollisionTangentA/TangentLengthA : float3(0,0,0); + CollisionTangentB = (TangentLengthB > 0.0) ? CollisionTangentB/TangentLengthB : float3(0,0,0); + + const float DeltaVTangentA = dot( InDeltaVelocity, CollisionTangentA); + const float DeltaVTangentB = dot( InDeltaVelocity, CollisionTangentB); + + const float NormDeltaVTA = abs(DeltaVTangentA); + const float NormDeltaVTB = abs(DeltaVTangentB); + + const float AlphaTangentA = ( NormDeltaVTA < InStaticFriction * NormDeltaFN ) ? 1.0 : (InKineticFriction > 0.0) ? min(InKineticFriction*NormDeltaFN/NormDeltaVTA, 1.0) : 0.0; + const float AlphaTangentB = ( NormDeltaVTB < InStaticFriction * NormDeltaFN ) ? 1.0 : (InKineticFriction > 0.0) ? min(InKineticFriction*NormDeltaFN/NormDeltaVTB, 1.0) : 0.0; + + return DeltaFNormal * InCollisionNormal + AlphaTangentA * CollisionTangentA * DeltaVTangentA + AlphaTangentB * CollisionTangentB * DeltaVTangentB; + } + return float3(0,0,0); + } + +/* ----------------------------------------------------------------- + * Static volume collisions against sphere, box... + * ----------------------------------------------------------------- + */ + +void DetectSphereVolumeCollision(const float3 InParticlePosition, const float3 InSphereCenter, const float InSphereRadius, + out float3 OutCollisionPosition, out float3 OutCollisionVelocity, inout float3 OutCollisionNormal) +{ + OutCollisionNormal = InParticlePosition - InSphereCenter; + const float ParticleDistance = length(OutCollisionNormal); + const float CollisionDepth = ParticleDistance-InSphereRadius; + + OutCollisionNormal = (ParticleDistance != 0.0) ? OutCollisionNormal/ParticleDistance : float3(0,0,0); + OutCollisionPosition = InParticlePosition - OutCollisionNormal * CollisionDepth; + + // Compute the sdf velocity based on the transform deriv + OutCollisionVelocity = float3(0,0,0); +} + +/* ----------------------------------------------------------------- + * Static collision constraint + * ----------------------------------------------------------------- + */ + +void SolveStaticCollisionConstraint(const float InParticleRadius, const bool InIsMobile, inout float3 OutParticlePosition, inout float3 OutParticleVelocity, const float3 InCollisionPosition, const float3 InCollisionVelocity, const float3 InCollisionNormal, + const float InStaticFriction, const float InKineticFriction, const float InDeltaTime, const bool IsProjection ) +{ + if(InIsMobile) + { + const float3 DeltaPosition = OutParticlePosition - InCollisionPosition; + const float3 DeltaVelocity = (OutParticleVelocity - InCollisionVelocity) * InDeltaTime; + const float ThicknessSum = InParticleRadius; + + const float3 PositionDisplacement = ProcessCollisionData(DeltaPosition,DeltaVelocity,InCollisionNormal,ThicknessSum,InStaticFriction,InKineticFriction,IsProjection); + OutParticlePosition -= PositionDisplacement; + OutParticleVelocity -= PositionDisplacement / InDeltaTime; + } +} + +#endif //GPU_SIMULATION \ No newline at end of file diff --git a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfaceFieldSystem.cpp b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfaceFieldSystem.cpp deleted file mode 100644 index 6290cca7f5bb..000000000000 --- a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfaceFieldSystem.cpp +++ /dev/null @@ -1,914 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "NiagaraDataInterfaceFieldSystem.h" -#include "NiagaraShader.h" -#include "NiagaraComponent.h" -#include "NiagaraRenderer.h" -#include "NiagaraSystemInstance.h" -#include "NiagaraEmitterInstanceBatcher.h" -#include "ShaderParameterUtils.h" -#include "Field/FieldSystemActor.h" -#include "Field/FieldSystem.h" -#include "Field/FieldSystemNodes.h" - -#define LOCTEXT_NAMESPACE "NiagaraDataInterfaceFieldSystem" -DEFINE_LOG_CATEGORY_STATIC(LogFieldSystem, Log, All); - -//------------------------------------------------------------------------------------------------------------ - -static const FName SampleLinearVelocityName(TEXT("SampleLinearVelocity")); -static const FName SampleAngularVelocityName(TEXT("SampleAngularVelocity")); -static const FName SampleLinearForceName(TEXT("SampleLinearForce")); -static const FName SampleAngularTorqueName(TEXT("SampleAngularTorque")); - -//------------------------------------------------------------------------------------------------------------ - -const FString UNiagaraDataInterfaceFieldSystem::FieldCommandsNodesBufferName(TEXT("FieldCommandsNodesBuffer_")); -const FString UNiagaraDataInterfaceFieldSystem::FieldNodesParamsBufferName(TEXT("FieldNodesParamsBuffer_")); -const FString UNiagaraDataInterfaceFieldSystem::FieldNodesOffsetsBufferName(TEXT("FieldNodesOffsetsBuffer_")); - -//------------------------------------------------------------------------------------------------------------ - -struct FNDIFieldSystemParametersName -{ - FNDIFieldSystemParametersName(const FString& Suffix) - { - FieldCommandsNodesBufferName = UNiagaraDataInterfaceFieldSystem::FieldCommandsNodesBufferName + Suffix; - FieldNodesParamsBufferName = UNiagaraDataInterfaceFieldSystem::FieldNodesParamsBufferName + Suffix; - FieldNodesOffsetsBufferName = UNiagaraDataInterfaceFieldSystem::FieldNodesOffsetsBufferName + Suffix; - } - - FString FieldCommandsNodesBufferName; - FString FieldNodesParamsBufferName; - FString FieldNodesOffsetsBufferName; -}; - -//------------------------------------------------------------------------------------------------------------ - -template -void CreateInternalBuffer(const uint32 ElementCount, const BufferType* InputData, FRWBuffer& OutputBuffer) -{ - if (ElementCount > 0) - { - const uint32 BufferCount = ElementCount * ElementSize; - const uint32 BufferBytes = sizeof(BufferType) * BufferCount; - - if (InitBuffer) - { - OutputBuffer.Initialize(sizeof(BufferType), BufferCount, PixelFormat, BUF_Static); - } - void* OutputData = RHILockVertexBuffer(OutputBuffer.Buffer, 0, BufferBytes, RLM_WriteOnly); - - FMemory::Memcpy(OutputData, InputData, BufferBytes); - RHIUnlockVertexBuffer(OutputBuffer.Buffer); - } -} - -void BuildNodeParams(FFieldNodeBase* FieldNode, FNDIFieldSystemArrays* OutAssetArrays) -{ - if (FieldNode && OutAssetArrays) - { - if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FUniformInteger) - { - FUniformInteger* LocalNode = StaticCast(FieldNode); - - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FUniformInteger); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FRadialIntMask) - { - FRadialIntMask* LocalNode = StaticCast(FieldNode); - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add( FFieldNodeBase::ESerializationType::FieldNode_FRadialIntMask); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Radius); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.X); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Y); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Z); - OutAssetArrays->FieldNodesParams.Add(LocalNode->InteriorValue); - OutAssetArrays->FieldNodesParams.Add(LocalNode->ExteriorValue); - OutAssetArrays->FieldNodesParams.Add(LocalNode->SetMaskCondition); - - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FUniformScalar) - { - FUniformScalar* LocalNode = StaticCast(FieldNode); - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FUniformScalar); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FRadialFalloff) - { - FRadialFalloff* LocalNode = StaticCast(FieldNode); - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FRadialFalloff); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); - OutAssetArrays->FieldNodesParams.Add(LocalNode->MinRange); - OutAssetArrays->FieldNodesParams.Add(LocalNode->MaxRange); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Default); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Radius); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.X); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Y); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Z); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Falloff); - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FPlaneFalloff) - { - FPlaneFalloff* LocalNode = StaticCast(FieldNode); - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FPlaneFalloff); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); - OutAssetArrays->FieldNodesParams.Add(LocalNode->MinRange); - OutAssetArrays->FieldNodesParams.Add(LocalNode->MaxRange); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Default); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Distance); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.X); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Y); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Z); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Normal.X); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Normal.Y); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Normal.Z); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Falloff); - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FBoxFalloff) - { - FBoxFalloff* LocalNode = StaticCast(FieldNode); - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FBoxFalloff); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); - OutAssetArrays->FieldNodesParams.Add(LocalNode->MinRange); - OutAssetArrays->FieldNodesParams.Add(LocalNode->MaxRange); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Default); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().X); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().Y); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().Z); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().W); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetTranslation().X); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetTranslation().Y); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetTranslation().Z); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetScale3D().X); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetScale3D().Y); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetScale3D().Z); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Falloff); - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FNoiseField) - { - FNoiseField* LocalNode = StaticCast(FieldNode); - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FNoiseField); - OutAssetArrays->FieldNodesParams.Add(LocalNode->MinRange); - OutAssetArrays->FieldNodesParams.Add(LocalNode->MaxRange); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().X); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().Y); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().Z); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetRotation().W); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetTranslation().X); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetTranslation().Y); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetTranslation().Z); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetScale3D().X); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetScale3D().Y); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Transform.GetScale3D().Z); - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FUniformVector) - { - FUniformVector* LocalNode = StaticCast(FieldNode); - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FUniformVector); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Direction.X); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Direction.Y); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Direction.Z); - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FRadialVector) - { - FRadialVector* LocalNode = StaticCast(FieldNode); - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FRadialVector); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.X); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Y); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Position.Z); - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FRandomVector) - { - FRandomVector* LocalNode = StaticCast(FieldNode); - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FRandomVector); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FSumScalar) - { - FSumScalar* LocalNode = StaticCast(FieldNode); - - BuildNodeParams(LocalNode->ScalarRight.Get(), OutAssetArrays); - BuildNodeParams(LocalNode->ScalarLeft.Get(), OutAssetArrays); - - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FSumScalar); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); - OutAssetArrays->FieldNodesParams.Add(LocalNode->ScalarRight != nullptr); - OutAssetArrays->FieldNodesParams.Add(LocalNode->ScalarLeft != nullptr); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Operation); - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FSumVector) - { - FSumVector* LocalNode = StaticCast(FieldNode); - - BuildNodeParams(LocalNode->Scalar.Get(), OutAssetArrays); - BuildNodeParams(LocalNode->VectorRight.Get(), OutAssetArrays); - BuildNodeParams(LocalNode->VectorLeft.Get(), OutAssetArrays); - - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FSumVector); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Magnitude); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Scalar.Get() != nullptr); - OutAssetArrays->FieldNodesParams.Add(LocalNode->VectorRight.Get() != nullptr); - OutAssetArrays->FieldNodesParams.Add(LocalNode->VectorLeft.Get() != nullptr); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Operation); - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FConversionField) - { - if (FieldNode->Type() == FFieldNodeBase::EFieldType::EField_Int32) - { - FConversionField* LocalNode = StaticCast*>(FieldNode); - - BuildNodeParams(LocalNode->InputField.Get(), OutAssetArrays); - - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FConversionField); - OutAssetArrays->FieldNodesParams.Add(LocalNode->InputField.Get() != nullptr); - } - else if (FieldNode->Type() == FFieldNodeBase::EFieldType::EField_Float) - { - FConversionField* LocalNode = StaticCast*>(FieldNode); - - BuildNodeParams(LocalNode->InputField.Get(), OutAssetArrays); - - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FConversionField); - OutAssetArrays->FieldNodesParams.Add(LocalNode->InputField.Get() != nullptr); - } - } - else if (FieldNode->SerializationType() == FFieldNodeBase::ESerializationType::FieldNode_FCullingField) - { - if (FieldNode->Type() == FFieldNodeBase::EFieldType::EField_Int32) - { - FCullingField* LocalNode = StaticCast*>(FieldNode); - - BuildNodeParams(LocalNode->Culling.Get(), OutAssetArrays); - BuildNodeParams(LocalNode->Input.Get(), OutAssetArrays); - - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FCullingField); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Culling.Get() != nullptr); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Input.Get() != nullptr); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Operation); - } - else if (FieldNode->Type() == FFieldNodeBase::EFieldType::EField_Float) - { - FCullingField* LocalNode = StaticCast*>(FieldNode); - - BuildNodeParams(LocalNode->Culling.Get(), OutAssetArrays); - BuildNodeParams(LocalNode->Input.Get(), OutAssetArrays); - - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FCullingField); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Culling.Get() != nullptr); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Input.Get() != nullptr); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Operation); - } - else if (FieldNode->Type() == FFieldNodeBase::EFieldType::EField_FVector) - { - FCullingField* LocalNode = StaticCast*>(FieldNode); - - BuildNodeParams(LocalNode->Culling.Get(), OutAssetArrays); - BuildNodeParams(LocalNode->Input.Get(), OutAssetArrays); - - OutAssetArrays->FieldNodesOffsets.Add(OutAssetArrays->FieldNodesParams.Num()); - OutAssetArrays->FieldNodesParams.Add(FieldNode->Type()); - OutAssetArrays->FieldNodesParams.Add(FFieldNodeBase::ESerializationType::FieldNode_FCullingField); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Culling.Get() != nullptr); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Input.Get() != nullptr); - OutAssetArrays->FieldNodesParams.Add(LocalNode->Operation); - } - } - } -} - -void CreateInternalArrays(const TArray>& FieldSystems, const TArray>& FieldComponents, - FNDIFieldSystemArrays* OutAssetArrays) -{ - if (OutAssetArrays != nullptr) - { - OutAssetArrays->FieldNodesOffsets.Empty(); - for (uint32 FieldIndex = 0; FieldIndex < FNDIFieldSystemArrays::NumCommands+1; ++FieldIndex) - { - OutAssetArrays->FieldCommandsNodes[FieldIndex] = 0; - } - for (int32 SystemIndex = 0; SystemIndex < FieldSystems.Num(); ++SystemIndex) - { - TWeakObjectPtr FieldSystem = FieldSystems[SystemIndex]; - if (FieldSystem.IsValid() && FieldSystem.Get() != nullptr) - { - TArray< FFieldSystemCommand >& FieldCommands = FieldSystem->Commands; - for (int32 CommandIndex = 0; CommandIndex < FieldCommands.Num(); ++CommandIndex) - { - const EFieldPhysicsType CommandType = GetFieldPhysicsType(FieldCommands[CommandIndex].TargetAttribute); - OutAssetArrays->FieldCommandsNodes[CommandType+1] = OutAssetArrays->FieldNodesOffsets.Num(); - - TUniquePtr& RootNode = FieldCommands[CommandIndex].RootNode; - BuildNodeParams(RootNode.Get(), OutAssetArrays); - - OutAssetArrays->FieldCommandsNodes[CommandType+1] = OutAssetArrays->FieldNodesOffsets.Num() - - OutAssetArrays->FieldCommandsNodes[CommandType+1]; - - //UE_LOG(LogFieldSystem, Warning, TEXT("Params offset = %d %d %d %d %d"), CommandIndex, CommandType, - // OutAssetArrays->FieldCommandsNodes[CommandType+1], OutAssetArrays->FieldNodesOffsets.Num(), OutAssetArrays->FieldNodesParams.Num()); - } - } - } - for (uint32 FieldIndex = 1; FieldIndex < FNDIFieldSystemArrays::NumCommands + 1; ++FieldIndex) - { - OutAssetArrays->FieldCommandsNodes[FieldIndex] += OutAssetArrays->FieldCommandsNodes[FieldIndex-1]; - } - /*for (auto& OffsetsValue : OutAssetArrays->FieldNodesOffsets) - { - UE_LOG(LogFieldSystem, Warning, TEXT("Offsets Value = %d"), OffsetsValue); - } - for (auto& ParamsValue : OutAssetArrays->FieldNodesParams) - { - UE_LOG(LogFieldSystem, Warning, TEXT("Params Value = %f"), ParamsValue); - } - for (auto& CommandsNodes : OutAssetArrays->FieldCommandsNodes) - { - UE_LOG(LogFieldSystem, Warning, TEXT("Commands Nodes = %d"), CommandsNodes); - }*/ - } -} - -void UpdateInternalArrays(const TArray>& FieldSystems, const TArray>& FieldComponents, - FNDIFieldSystemArrays* OutAssetArrays) -{ -} - -//------------------------------------------------------------------------------------------------------------ - - -bool FNDIFieldSystemBuffer::IsValid() const -{ - return (0 < FieldSystems.Num() && FieldSystems[0].IsValid() && - FieldSystems[0].Get() != nullptr) && (AssetArrays.IsValid() && AssetArrays.Get() != nullptr) && FieldSystems.Num() == FieldSystems.Num(); -} - -void FNDIFieldSystemBuffer::Initialize(const TArray>& InFieldSystems, const TArray>& InFieldComponents) -{ - FieldSystems = InFieldSystems; - FieldComponents = InFieldComponents; - - AssetArrays = MakeUnique(); - - if (IsValid()) - { - CreateInternalArrays(FieldSystems, FieldComponents, AssetArrays.Get()); - } -} - -void FNDIFieldSystemBuffer::Update() -{ - if (IsValid()) - { - UpdateInternalArrays(FieldSystems, FieldComponents, AssetArrays.Get()); - - FNDIFieldSystemBuffer* ThisBuffer = this; - ENQUEUE_RENDER_COMMAND(UpdateFieldSystem)( - [ThisBuffer](FRHICommandListImmediate& RHICmdList) mutable - { - CreateInternalBuffer(ThisBuffer->AssetArrays->FieldNodesParams.Num(), ThisBuffer->AssetArrays->FieldNodesParams.GetData(), ThisBuffer->FieldNodesParamsBuffer); - CreateInternalBuffer(ThisBuffer->AssetArrays->FieldCommandsNodes.Num(), ThisBuffer->AssetArrays->FieldCommandsNodes.GetData(), ThisBuffer->FieldCommandsNodesBuffer); - CreateInternalBuffer(ThisBuffer->AssetArrays->FieldNodesOffsets.Num(), ThisBuffer->AssetArrays->FieldNodesOffsets.GetData(), ThisBuffer->FieldNodesOffsetsBuffer); - } - ); - } -} - -void FNDIFieldSystemBuffer::InitRHI() -{ - if (IsValid()) - { - CreateInternalBuffer(AssetArrays->FieldNodesParams.Num(), AssetArrays->FieldNodesParams.GetData(), FieldNodesParamsBuffer); - CreateInternalBuffer(AssetArrays->FieldCommandsNodes.Num(), AssetArrays->FieldCommandsNodes.GetData(), FieldCommandsNodesBuffer); - CreateInternalBuffer(AssetArrays->FieldNodesOffsets.Num(), AssetArrays->FieldNodesOffsets.GetData(), FieldNodesOffsetsBuffer); - } -} - -void FNDIFieldSystemBuffer::ReleaseRHI() -{ - FieldNodesParamsBuffer.Release(); - FieldCommandsNodesBuffer.Release(); - FieldNodesOffsetsBuffer.Release(); -} - -//------------------------------------------------------------------------------------------------------------ - -void FNDIFieldSystemData::Release() -{ - if (FieldSystemBuffer) - { - BeginReleaseResource(FieldSystemBuffer); - ENQUEUE_RENDER_COMMAND(DeleteResource)( - [ParamPointerToRelease = FieldSystemBuffer](FRHICommandListImmediate& RHICmdList) - { - delete ParamPointerToRelease; - }); - FieldSystemBuffer = nullptr; - } -} - -bool FNDIFieldSystemData::Init(UNiagaraDataInterfaceFieldSystem* Interface, FNiagaraSystemInstance* SystemInstance) -{ - FieldSystemBuffer = nullptr; - - if (Interface != nullptr && SystemInstance != nullptr) - { - Interface->ExtractSourceComponent(SystemInstance); - - FieldSystemBuffer = new FNDIFieldSystemBuffer(); - FieldSystemBuffer->Initialize(Interface->FieldSystems, Interface->SourceComponents); - - BeginInitResource(FieldSystemBuffer); - } - - return true; -} - -//------------------------------------------------------------------------------------------------------------ - -struct FNDIFieldSystemParametersCS : public FNiagaraDataInterfaceParametersCS -{ - DECLARE_TYPE_LAYOUT(FNDIFieldSystemParametersCS, NonVirtual); -public: - void Bind(const FNiagaraDataInterfaceGPUParamInfo& ParameterInfo, const class FShaderParameterMap& ParameterMap) - { - FNDIFieldSystemParametersName ParamNames(*ParameterInfo.DataInterfaceHLSLSymbol); - - FieldCommandsNodesBuffer.Bind(ParameterMap, *ParamNames.FieldCommandsNodesBufferName); - FieldNodesParamsBuffer.Bind(ParameterMap, *ParamNames.FieldNodesParamsBufferName); - FieldNodesOffsetsBuffer.Bind(ParameterMap, *ParamNames.FieldNodesOffsetsBufferName); - - if (!FieldNodesParamsBuffer.IsBound()) - { - UE_LOG(LogFieldSystem, Warning, TEXT("Binding failed for FNDIFieldSystemParametersCS %s. Was it optimized out?"), *ParamNames.FieldNodesParamsBufferName) - } - if (!FieldCommandsNodesBuffer.IsBound()) - { - UE_LOG(LogFieldSystem, Warning, TEXT("Binding failed for FNDIFieldSystemParametersCS %s. Was it optimized out?"), *ParamNames.FieldCommandsNodesBufferName) - } - if (!FieldNodesOffsetsBuffer.IsBound()) - { - UE_LOG(LogFieldSystem, Warning, TEXT("Binding failed for FNDIFieldSystemParametersCS %s. Was it optimized out?"), *ParamNames.FieldNodesOffsetsBufferName) - } - } - - void Set(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) const - { - check(IsInRenderingThread()); - - FRHIComputeShader* ComputeShaderRHI = RHICmdList.GetBoundComputeShader(); - - FNDIFieldSystemProxy* InterfaceProxy = - static_cast(Context.DataInterface); - FNDIFieldSystemData* ProxyData = - InterfaceProxy->SystemInstancesToProxyData.Find(Context.SystemInstance); - - if (ProxyData != nullptr && ProxyData->FieldSystemBuffer && ProxyData->FieldSystemBuffer->IsInitialized()) - { - FNDIFieldSystemBuffer* AssetBuffer = ProxyData->FieldSystemBuffer; - SetSRVParameter(RHICmdList, ComputeShaderRHI, FieldNodesParamsBuffer, AssetBuffer->FieldNodesParamsBuffer.SRV); - SetSRVParameter(RHICmdList, ComputeShaderRHI, FieldCommandsNodesBuffer, AssetBuffer->FieldCommandsNodesBuffer.SRV); - SetSRVParameter(RHICmdList, ComputeShaderRHI, FieldNodesOffsetsBuffer, AssetBuffer->FieldNodesOffsetsBuffer.SRV); - } - else - { - SetSRVParameter(RHICmdList, ComputeShaderRHI, FieldNodesParamsBuffer, FNiagaraRenderer::GetDummyFloatBuffer()); - SetSRVParameter(RHICmdList, ComputeShaderRHI, FieldCommandsNodesBuffer, FNiagaraRenderer::GetDummyIntBuffer()); - SetSRVParameter(RHICmdList, ComputeShaderRHI, FieldNodesOffsetsBuffer, FNiagaraRenderer::GetDummyIntBuffer()); - } - } - - void Unset(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) const - { - } - -private: - - LAYOUT_FIELD(FShaderResourceParameter, FieldNodesParamsBuffer); - LAYOUT_FIELD(FShaderResourceParameter, FieldCommandsNodesBuffer); - LAYOUT_FIELD(FShaderResourceParameter, FieldNodesOffsetsBuffer); -}; - -IMPLEMENT_TYPE_LAYOUT(FNDIFieldSystemParametersCS); - -IMPLEMENT_NIAGARA_DI_PARAMETER(UNiagaraDataInterfaceFieldSystem, FNDIFieldSystemParametersCS); - - -//------------------------------------------------------------------------------------------------------------ - -void FNDIFieldSystemProxy::ConsumePerInstanceDataFromGameThread(void* PerInstanceData, const FNiagaraSystemInstanceID& Instance) -{ - FNDIFieldSystemData* SourceData = static_cast(PerInstanceData); - FNDIFieldSystemData* TargetData = &(SystemInstancesToProxyData.FindOrAdd(Instance)); - - ensure(TargetData); - if (TargetData) - { - TargetData->FieldSystemBuffer = SourceData->FieldSystemBuffer; - } - else - { - UE_LOG(LogFieldSystem, Log, TEXT("ConsumePerInstanceDataFromGameThread() ... could not find %d"), Instance); - } -} - -void FNDIFieldSystemProxy::InitializePerInstanceData(const FNiagaraSystemInstanceID& SystemInstance) -{ - check(IsInRenderingThread()); - - FNDIFieldSystemData* TargetData = SystemInstancesToProxyData.Find(SystemInstance); - TargetData = &SystemInstancesToProxyData.Add(SystemInstance); -} - -void FNDIFieldSystemProxy::DestroyPerInstanceData(NiagaraEmitterInstanceBatcher* Batcher, const FNiagaraSystemInstanceID& SystemInstance) -{ - check(IsInRenderingThread()); - SystemInstancesToProxyData.Remove(SystemInstance); -} - -void FNDIFieldSystemProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) -{ -} - -void FNDIFieldSystemProxy::PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) -{ -} - -void FNDIFieldSystemProxy::ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) -{ -} - -//------------------------------------------------------------------------------------------------------------ - -UNiagaraDataInterfaceFieldSystem::UNiagaraDataInterfaceFieldSystem(FObjectInitializer const& ObjectInitializer) - : Super(ObjectInitializer) - , DefaultSource(nullptr) - , BlueprintSource(nullptr) - , SourceActor(nullptr) - , SourceComponents() - , FieldSystems() -{ - Proxy.Reset(new FNDIFieldSystemProxy()); -} - -void UNiagaraDataInterfaceFieldSystem::ExtractSourceComponent(FNiagaraSystemInstance* SystemInstance) -{ - TWeakObjectPtr SourceComponent; - if (SourceActor) - { - AFieldSystemActor* FieldSystemActor = Cast(SourceActor); - if (FieldSystemActor != nullptr) - { - SourceComponent = FieldSystemActor->GetFieldSystemComponent(); - } - else - { - SourceComponent = SourceActor->FindComponentByClass(); - } - } - else - { - if (UNiagaraComponent* SimComp = SystemInstance->GetComponent()) - { - if (UFieldSystemComponent* ParentComp = Cast(SimComp->GetAttachParent())) - { - SourceComponent = ParentComp; - } - else if (UFieldSystemComponent* OuterComp = SimComp->GetTypedOuter()) - { - SourceComponent = OuterComp; - } - else - { - TArray SceneComponents; - SimComp->GetParentComponents(SceneComponents); - - for (USceneComponent* ActorComp : SceneComponents) - { - UFieldSystemComponent* SourceComp = Cast(ActorComp); - if (SourceComp && SourceComp->FieldSystem) - { - SourceComponent = SourceComp; - break; - } - } - } - } - } - if (BlueprintSource) - { - AFieldSystemActor* FieldSystemActor = Cast(BlueprintSource->GeneratedClass.GetDefaultObject()); - if (FieldSystemActor != nullptr) - { - SourceComponent = FieldSystemActor->FieldSystemComponent; - } - } - - SourceComponents.Empty(); - FieldSystems.Empty(); - if (SourceComponent != nullptr) - { - SourceComponents.Add(SourceComponent); - FieldSystems.Add(SourceComponent->FieldSystem); - } - else if (DefaultSource != nullptr) - { - SourceComponents.Add(nullptr); - FieldSystems.Add(DefaultSource); - } -} - -bool UNiagaraDataInterfaceFieldSystem::InitPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) -{ - FNDIFieldSystemData* InstanceData = new (PerInstanceData) FNDIFieldSystemData(); - - check(InstanceData); - - return InstanceData->Init(this, SystemInstance); -} - -void UNiagaraDataInterfaceFieldSystem::DestroyPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) -{ - FNDIFieldSystemData* InstanceData = static_cast(PerInstanceData); - - InstanceData->Release(); - InstanceData->~FNDIFieldSystemData(); - - FNDIFieldSystemProxy* ThisProxy = GetProxyAs(); - ENQUEUE_RENDER_COMMAND(FNiagaraDIDestroyInstanceData) ( - [ThisProxy, InstanceID = SystemInstance->GetId(), Batcher = SystemInstance->GetBatcher()](FRHICommandListImmediate& CmdList) - { - ThisProxy->SystemInstancesToProxyData.Remove(InstanceID); - } - ); -} - -bool UNiagaraDataInterfaceFieldSystem::PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float InDeltaSeconds) -{ - FNDIFieldSystemData* InstanceData = static_cast(PerInstanceData); - if (InstanceData->FieldSystemBuffer && SystemInstance) - { - InstanceData->FieldSystemBuffer->Update(); - } - return false; -} - -bool UNiagaraDataInterfaceFieldSystem::CopyToInternal(UNiagaraDataInterface* Destination) const -{ - if (!Super::CopyToInternal(Destination)) - { - return false; - } - - UNiagaraDataInterfaceFieldSystem* OtherTyped = CastChecked(Destination); - OtherTyped->FieldSystems = FieldSystems; - OtherTyped->SourceActor = SourceActor; - OtherTyped->SourceComponents = SourceComponents; - OtherTyped->DefaultSource = DefaultSource; - OtherTyped->BlueprintSource = BlueprintSource; - - return true; -} - -bool UNiagaraDataInterfaceFieldSystem::Equals(const UNiagaraDataInterface* Other) const -{ - if (!Super::Equals(Other)) - { - return false; - } - const UNiagaraDataInterfaceFieldSystem* OtherTyped = CastChecked(Other); - - return (OtherTyped->FieldSystems == FieldSystems) && (OtherTyped->SourceActor == SourceActor) && - (OtherTyped->SourceComponents == SourceComponents) && (OtherTyped->DefaultSource == DefaultSource) - && ( OtherTyped->BlueprintSource == BlueprintSource); -} - -void UNiagaraDataInterfaceFieldSystem::PostInitProperties() -{ - Super::PostInitProperties(); - - if (HasAnyFlags(RF_ClassDefaultObject)) - { - FNiagaraTypeRegistry::Register(FNiagaraTypeDefinition(GetClass()), true, false, false); - } -} - -void UNiagaraDataInterfaceFieldSystem::GetFunctions(TArray& OutFunctions) -{ - { - FNiagaraFunctionSignature Sig; - Sig.Name = SampleLinearVelocityName; - Sig.bMemberFunction = true; - Sig.bRequiresContext = false; - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Min Bound"))); - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Max Bound"))); - Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Linear Velocity"))); - - OutFunctions.Add(Sig); - } - { - FNiagaraFunctionSignature Sig; - Sig.Name = SampleAngularVelocityName; - Sig.bMemberFunction = true; - Sig.bRequiresContext = false; - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Min Bound"))); - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Max Bound"))); - Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Angular Velocity"))); - - OutFunctions.Add(Sig); - } - { - FNiagaraFunctionSignature Sig; - Sig.Name = SampleLinearForceName; - Sig.bMemberFunction = true; - Sig.bRequiresContext = false; - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Min Bound"))); - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Max Bound"))); - Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Linear Force"))); - - OutFunctions.Add(Sig); - } - { - FNiagaraFunctionSignature Sig; - Sig.Name = SampleAngularTorqueName; - Sig.bMemberFunction = true; - Sig.bRequiresContext = false; - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Field System"))); - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Sample Position"))); - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Min Bound"))); - Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Max Bound"))); - Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Angular Torque"))); - - OutFunctions.Add(Sig); - } -} - -DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleLinearVelocity); -DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleAngularVelocity); -DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleLinearForce); -DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleAngularTorque); - -void UNiagaraDataInterfaceFieldSystem::GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction& OutFunc) -{ - if (BindingInfo.Name == SampleLinearVelocityName) - { - check(BindingInfo.GetNumInputs() == 10 && BindingInfo.GetNumOutputs() == 3); - NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleLinearVelocity)::Bind(this, OutFunc); - } - else if (BindingInfo.Name == SampleAngularVelocityName) - { - check(BindingInfo.GetNumInputs() == 10 && BindingInfo.GetNumOutputs() == 3); - NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleAngularVelocity)::Bind(this, OutFunc); - } - else if (BindingInfo.Name == SampleLinearForceName) - { - check(BindingInfo.GetNumInputs() == 10 && BindingInfo.GetNumOutputs() == 3); - NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleLinearForce)::Bind(this, OutFunc); - } - else if (BindingInfo.Name == SampleAngularTorqueName) - { - check(BindingInfo.GetNumInputs() == 10 && BindingInfo.GetNumOutputs() == 3); - NDI_FUNC_BINDER(UNiagaraDataInterfaceFieldSystem, SampleAngularTorque)::Bind(this, OutFunc); - } -} - -void UNiagaraDataInterfaceFieldSystem::SampleLinearVelocity(FVectorVMContext& Context) -{ -} - -void UNiagaraDataInterfaceFieldSystem::SampleAngularVelocity(FVectorVMContext& Context) -{ -} - -void UNiagaraDataInterfaceFieldSystem::SampleLinearForce(FVectorVMContext& Context) -{ -} - -void UNiagaraDataInterfaceFieldSystem::SampleAngularTorque(FVectorVMContext& Context) -{ -} - -bool UNiagaraDataInterfaceFieldSystem::GetFunctionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, const FNiagaraDataInterfaceGeneratedFunction& FunctionInfo, int FunctionInstanceIndex, FString& OutHLSL) -{ - FNDIFieldSystemParametersName ParamNames(ParamInfo.DataInterfaceHLSLSymbol); - - TMap ArgsSample = { - {TEXT("InstanceFunctionName"), FunctionInfo.InstanceName}, - {TEXT("FieldSystemContextName"), TEXT("DIFieldSystem_MAKE_CONTEXT(") + ParamInfo.DataInterfaceHLSLSymbol + TEXT(")")}, - }; - - if (FunctionInfo.DefinitionName == SampleLinearVelocityName) - { - static const TCHAR* FormatSample = TEXT(R"( - void {InstanceFunctionName}(in float3 SamplePosition, in float3 MinBound, in float3 MaxBound, out float3 OutLinearVelocity) - { - {FieldSystemContextName} - OutLinearVelocity = DIFieldSystem_SampleFieldVector(DIContext,SamplePosition,MinBound,MaxBound,LINEAR_VELOCITY); - } - )"); - OutHLSL += FString::Format(FormatSample, ArgsSample); - return true; - } - else if (FunctionInfo.DefinitionName == SampleLinearForceName) - { - static const TCHAR* FormatSample = TEXT(R"( - void {InstanceFunctionName}(in float3 SamplePosition, in float3 MinBound, in float3 MaxBound, out float3 OutLinearForce) - { - {FieldSystemContextName} - OutLinearForce = DIFieldSystem_SampleFieldVector(DIContext,SamplePosition,MinBound,MaxBound,LINEAR_FORCE); - } - )"); - OutHLSL += FString::Format(FormatSample, ArgsSample); - return true; - } - else if (FunctionInfo.DefinitionName == SampleAngularVelocityName) - { - static const TCHAR* FormatSample = TEXT(R"( - void {InstanceFunctionName}(in float3 SamplePosition, in float3 MinBound, in float3 MaxBound, out float3 OutAngularVelocity) - { - {FieldSystemContextName} - OutAngularVelocity = DIFieldSystem_SampleFieldVector(DIContext,SamplePosition,MinBound,MaxBound,ANGULAR_VELOCITY); - } - )"); - OutHLSL += FString::Format(FormatSample, ArgsSample); - return true; - } - else if (FunctionInfo.DefinitionName == SampleAngularTorqueName) - { - static const TCHAR* FormatSample = TEXT(R"( - void {InstanceFunctionName}(in float3 SamplePosition, in float3 MinBound, in float3 MaxBound, out float3 OutAngularTorque) - { - {FieldSystemContextName} - OutAngularTorque = DIFieldSystem_SampleFieldVector(DIContext,SamplePosition,MinBound,MaxBound,ANGULAR_TORQUE); - } - )"); - OutHLSL += FString::Format(FormatSample, ArgsSample); - return true; - } - - OutHLSL += TEXT("\n"); - return false; -} - -void UNiagaraDataInterfaceFieldSystem::GetCommonHLSL(FString& OutHLSL) -{ - OutHLSL += TEXT("#include \"/Plugin/Experimental/HairStrands/Private/NiagaraDataInterfaceFieldSystem.ush\"\n"); -} - -void UNiagaraDataInterfaceFieldSystem::GetParameterDefinitionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) -{ - OutHLSL += TEXT("DIFieldSystem_DECLARE_CONSTANTS(") + ParamInfo.DataInterfaceHLSLSymbol + TEXT(")\n"); -} - -void UNiagaraDataInterfaceFieldSystem::ProvidePerInstanceDataForRenderThread(void* DataForRenderThread, void* PerInstanceData, const FNiagaraSystemInstanceID& SystemInstance) -{ - FNDIFieldSystemData* GameThreadData = static_cast(PerInstanceData); - FNDIFieldSystemData* RenderThreadData = static_cast(DataForRenderThread); - - if (GameThreadData != nullptr && RenderThreadData != nullptr) - { - RenderThreadData->FieldSystemBuffer = GameThreadData->FieldSystemBuffer; - } - check(Proxy); -} - -#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfaceHairStrands.cpp b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfaceHairStrands.cpp index 51ade7760cc3..87c430d3f3b4 100644 --- a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfaceHairStrands.cpp +++ b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfaceHairStrands.cpp @@ -384,7 +384,7 @@ void FNDIHairStrandsData::Update(UNiagaraDataInterfaceHairStrands* Interface, FN if (Interface != nullptr) { WorldTransform = Interface->IsComponentValid() ? Interface->SourceComponent->GetComponentToWorld() : - SystemInstance ? SystemInstance->GetComponent()->GetComponentToWorld() : FTransform::Identity; + SystemInstance ? SystemInstance->GetWorldTransform() : FTransform::Identity; GlobalInterpolation = (Interface->IsComponentValid() && Interface->SourceComponent->BindingAsset && Interface->SourceComponent->GroomAsset) ? Interface->SourceComponent->GroomAsset->EnableGlobalInterpolation : false; @@ -579,7 +579,7 @@ struct FNDIHairStrandsParametersCS : public FNiagaraDataInterfaceParametersCS FNDIHairStrandsProxy* InterfaceProxy = static_cast(Context.DataInterface); FNDIHairStrandsData* ProxyData = - InterfaceProxy->SystemInstancesToProxyData.Find(Context.SystemInstance); + InterfaceProxy->SystemInstancesToProxyData.Find(Context.SystemInstanceID); const bool IsHairValid = ProxyData != nullptr && ProxyData->HairStrandsBuffer != nullptr && ProxyData->HairStrandsBuffer->IsInitialized(); const bool IsRootValid = IsHairValid && ProxyData->HairStrandsBuffer->SourceDeformedRootResources != nullptr;//&& ProxyData->HairStrandsBuffer->SourceRootResources->IsInitialized(); @@ -838,31 +838,43 @@ void UNiagaraDataInterfaceHairStrands::ExtractSourceComponent(FNiagaraSystemInst SourceComponent = SourceActor->FindComponentByClass(); } } - else if(SystemInstance) + else if (SystemInstance) { - if (UNiagaraComponent* SimComp = SystemInstance->GetComponent()) + if (USceneComponent* AttachComponent = SystemInstance->GetAttachComponent()) { - if (UGroomComponent* ParentComp = Cast(SimComp->GetAttachParent())) + // First, look to our attachment hierarchy for the source component + for (USceneComponent* Curr = AttachComponent; Curr; Curr = Curr->GetAttachParent()) { - SourceComponent = ParentComp; - } - else if (UGroomComponent* OuterComp = SimComp->GetTypedOuter()) - { - SourceComponent = OuterComp; - } - else if (AActor* Owner = SimComp->GetAttachmentRootActor()) - { - for (UActorComponent* ActorComp : Owner->GetComponents()) + UGroomComponent* SourceComp = Cast(Curr); + if (SourceComp && SourceComp->GroomAsset) { - UGroomComponent* SourceComp = Cast(ActorComp); - if (SourceComp && SourceComp->GroomAsset) + SourceComponent = SourceComp; + break; + } + } + + if (!SourceComponent.IsValid()) + { + // Next, check out outer chain to look for the component + if (UGroomComponent* OuterComp = AttachComponent->GetTypedOuter()) + { + SourceComponent = OuterComp; + } + else if (AActor* Owner = AttachComponent->GetAttachmentRootActor()) + { + // Lastly, look through all our root actor's components for a sibling component + for (UActorComponent* ActorComp : Owner->GetComponents()) { - SourceComponent = SourceComp; - break; + UGroomComponent* SourceComp = Cast(ActorComp); + if (SourceComp && SourceComp->GroomAsset) + { + SourceComponent = SourceComp; + break; + } } } } - } + } } } @@ -889,10 +901,13 @@ void UNiagaraDataInterfaceHairStrands::ExtractDatasAndResources( { for (int32 NiagaraIndex = 0, NiagaraCount = SourceComponent->NiagaraComponents.Num(); NiagaraIndex < NiagaraCount; ++NiagaraIndex) { - if (SourceComponent->NiagaraComponents[NiagaraIndex] == SystemInstance->GetComponent()) + if (UNiagaraComponent* NiagaraComponent = SourceComponent->NiagaraComponents[NiagaraIndex]) { - OutGroupIndex = NiagaraIndex; - break; + if (NiagaraComponent->GetSystemInstance() == SystemInstance) + { + OutGroupIndex = NiagaraIndex; + break; + } } } if (OutGroupIndex >= 0 && OutGroupIndex < SourceComponent->NiagaraComponents.Num()) @@ -3848,12 +3863,12 @@ void UNiagaraDataInterfaceHairStrands::ProvidePerInstanceDataForRenderThread(voi } } -void FNDIHairStrandsProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNDIHairStrandsProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) { if (Context.SimulationStageIndex == 0) { FNDIHairStrandsData* ProxyData = - SystemInstancesToProxyData.Find(Context.SystemInstance); + SystemInstancesToProxyData.Find(Context.SystemInstanceID); if (ProxyData != nullptr && ProxyData->HairStrandsBuffer != nullptr) { @@ -3868,11 +3883,4 @@ void FNDIHairStrandsProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraD } } -void FNDIHairStrandsProxy::PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) -{} - -void FNDIHairStrandsProxy::ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) -{} - - #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfacePhysicsAsset.cpp b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfacePhysicsAsset.cpp index bbea006d29eb..ca971738a3f2 100644 --- a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfacePhysicsAsset.cpp +++ b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfacePhysicsAsset.cpp @@ -395,8 +395,7 @@ bool FNDIPhysicsAssetData::Init(UNiagaraDataInterfacePhysicsAsset* Interface, FN Interface->ExtractSourceComponent(SystemInstance); PhysicsAssetBuffer = new FNDIPhysicsAssetBuffer(); - PhysicsAssetBuffer->Initialize(Interface->PhysicsAssets, Interface->SourceComponents, - SystemInstance->GetComponent()->GetComponentTransform()); + PhysicsAssetBuffer->Initialize(Interface->PhysicsAssets, Interface->SourceComponents, SystemInstance->GetWorldTransform()); BeginInitResource(PhysicsAssetBuffer); @@ -485,7 +484,7 @@ public: FNDIPhysicsAssetProxy* InterfaceProxy = static_cast(Context.DataInterface); FNDIPhysicsAssetData* ProxyData = - InterfaceProxy->SystemInstancesToProxyData.Find(Context.SystemInstance); + InterfaceProxy->SystemInstancesToProxyData.Find(Context.SystemInstanceID); if (ProxyData != nullptr && ProxyData->PhysicsAssetBuffer && ProxyData->PhysicsAssetBuffer->IsInitialized()) { @@ -580,18 +579,6 @@ void FNDIPhysicsAssetProxy::DestroyPerInstanceData(NiagaraEmitterInstanceBatcher SystemInstancesToProxyData.Remove(SystemInstance); } -void FNDIPhysicsAssetProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) -{ -} - -void FNDIPhysicsAssetProxy::PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) -{ -} - -void FNDIPhysicsAssetProxy::ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) -{ -} - //------------------------------------------------------------------------------------------------------------ UNiagaraDataInterfacePhysicsAsset::UNiagaraDataInterfacePhysicsAsset(FObjectInitializer const& ObjectInitializer) @@ -606,6 +593,7 @@ UNiagaraDataInterfacePhysicsAsset::UNiagaraDataInterfacePhysicsAsset(FObjectInit void UNiagaraDataInterfacePhysicsAsset::ExtractSourceComponent(FNiagaraSystemInstance* SystemInstance) { + // Track down the source component TWeakObjectPtr SourceComponent; if (SourceActor) { @@ -619,51 +607,41 @@ void UNiagaraDataInterfacePhysicsAsset::ExtractSourceComponent(FNiagaraSystemIns SourceComponent = SourceActor->FindComponentByClass(); } } - else + else if (USceneComponent* AttachComponent = SystemInstance->GetAttachComponent()) { - if (UNiagaraComponent* SimComp = SystemInstance->GetComponent()) + // Try to find the component by walking the attachment hierarchy + for (USceneComponent* Curr = AttachComponent; Curr; Curr = Curr->GetAttachParent()) { - if (USkeletalMeshComponent* ParentComp = Cast(SimComp->GetAttachParent())) + USkeletalMeshComponent* SkelMeshComp = Cast(Curr); + if (SkelMeshComp && SkelMeshComp->SkeletalMesh) { - SourceComponent = ParentComp; - } - else if (USkeletalMeshComponent* OuterComp = SimComp->GetTypedOuter()) - { - SourceComponent = OuterComp; - } - else - { - TArray SceneComponents; - SimComp->GetParentComponents(SceneComponents); - - for (USceneComponent* ActorComp : SceneComponents) - { - USkeletalMeshComponent* SourceComp = Cast(ActorComp); - if (SourceComp && SourceComp->SkeletalMesh) - { - SourceComponent = SourceComp; - break; - } - } - } - } - } - UPhysicsAsset* GroomPhysicsAsset = DefaultSource; - if (UNiagaraComponent* SimComp = SystemInstance->GetComponent()) - { - TArray SceneComponents; - SimComp->GetParentComponents(SceneComponents); - - for (USceneComponent* ActorComp : SceneComponents) - { - UGroomComponent* SourceComp = Cast(ActorComp); - if (SourceComp && SourceComp->PhysicsAsset) - { - GroomPhysicsAsset = SourceComp->PhysicsAsset; + SourceComponent = SkelMeshComp; break; } } + + if (!SourceComponent.IsValid()) + { + // Fall back on the attach component's outer chain if we aren't attached to the skeletal mesh + if (USkeletalMeshComponent* OuterComp = AttachComponent->GetTypedOuter()) + { + SourceComponent = OuterComp; + } + } } + + // Try to find the groom physics asset by walking the attachment hierarchy + UPhysicsAsset* GroomPhysicsAsset = DefaultSource; + for (USceneComponent* Curr = SystemInstance->GetAttachComponent(); Curr; Curr = Curr->GetAttachParent()) + { + UGroomComponent* GroomComponent = Cast(Curr); + if (GroomComponent && GroomComponent->PhysicsAsset) + { + GroomPhysicsAsset = GroomComponent->PhysicsAsset; + break; + } + } + const bool IsAssetMatching = (SourceComponent != nullptr) && (GroomPhysicsAsset != nullptr) && (GroomPhysicsAsset->GetPreviewMesh() == SourceComponent->SkeletalMesh); @@ -742,7 +720,7 @@ bool UNiagaraDataInterfacePhysicsAsset::PerInstanceTick(void* PerInstanceData, F FNDIPhysicsAssetData* InstanceData = static_cast(PerInstanceData); if (InstanceData->PhysicsAssetBuffer && SystemInstance) { - InstanceData->PhysicsAssetBuffer->Update(SystemInstance->GetComponent()->GetComponentTransform()); + InstanceData->PhysicsAssetBuffer->Update(SystemInstance->GetWorldTransform()); } return false; } diff --git a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfacePressureGrid.cpp b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfacePressureGrid.cpp index 591b8d776d63..80a85e7ac666 100644 --- a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfacePressureGrid.cpp +++ b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfacePressureGrid.cpp @@ -496,10 +496,10 @@ inline void ClearBuffer(FRHICommandList& RHICmdList, FNDIVelocityGridBuffer* Cur //------------------------------------------------------------------------------------------------------------ -void FNDIPressureGridProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNDIPressureGridProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) { FNDIVelocityGridData* ProxyData = - FNDIVelocityGridProxy::SystemInstancesToProxyData.Find(Context.SystemInstance); + FNDIVelocityGridProxy::SystemInstancesToProxyData.Find(Context.SystemInstanceID); if (ProxyData != nullptr) { diff --git a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfaceVelocityGrid.cpp b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfaceVelocityGrid.cpp index 1a39bfe433a0..117f6150cea6 100644 --- a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfaceVelocityGrid.cpp +++ b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Private/NiagaraDataInterfaceVelocityGrid.cpp @@ -174,7 +174,7 @@ void FNDIVelocityGridParametersCS::Set(FRHICommandList& RHICmdList, const FNiaga FNDIVelocityGridProxy* InterfaceProxy = static_cast(Context.DataInterface); FNDIVelocityGridData* ProxyData = - InterfaceProxy->SystemInstancesToProxyData.Find(Context.SystemInstance); + InterfaceProxy->SystemInstancesToProxyData.Find(Context.SystemInstanceID); if (ProxyData != nullptr && ProxyData->CurrentGridBuffer != nullptr && ProxyData->DestinationGridBuffer != nullptr && ProxyData->CurrentGridBuffer->IsInitialized() && ProxyData->DestinationGridBuffer->IsInitialized()) @@ -264,7 +264,7 @@ bool UNiagaraDataInterfaceVelocityGrid::PerInstanceTick(void* PerInstanceData, F bool RequireReset = false; if (InstanceData) { - InstanceData->WorldTransform = SystemInstance->GetComponent()->GetComponentToWorld().ToMatrixWithScale(); + InstanceData->WorldTransform = SystemInstance->GetWorldTransform().ToMatrixWithScale(); if (InstanceData->NeedResize) { @@ -623,10 +623,10 @@ void FNDIVelocityGridProxy::ConsumePerInstanceDataFromGameThread(void* PerInstan } } -void FNDIVelocityGridProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNDIVelocityGridProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) { FNDIVelocityGridData* ProxyData = - SystemInstancesToProxyData.Find(Context.SystemInstance); + SystemInstancesToProxyData.Find(Context.SystemInstanceID); if (ProxyData != nullptr ) { @@ -637,10 +637,10 @@ void FNDIVelocityGridProxy::PreStage(FRHICommandList& RHICmdList, const FNiagara } } -void FNDIVelocityGridProxy::PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNDIVelocityGridProxy::PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) { FNDIVelocityGridData* ProxyData = - SystemInstancesToProxyData.Find(Context.SystemInstance); + SystemInstancesToProxyData.Find(Context.SystemInstanceID); if (ProxyData != nullptr) { @@ -651,9 +651,9 @@ void FNDIVelocityGridProxy::PostStage(FRHICommandList& RHICmdList, const FNiagar } } -void FNDIVelocityGridProxy::ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNDIVelocityGridProxy::ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) { - FNDIVelocityGridData* ProxyData = SystemInstancesToProxyData.Find(Context.SystemInstance); + FNDIVelocityGridData* ProxyData = SystemInstancesToProxyData.Find(Context.SystemInstanceID); if (ProxyData != nullptr && ProxyData->DestinationGridBuffer != nullptr && ProxyData->CurrentGridBuffer != nullptr) { diff --git a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfaceHairStrands.h b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfaceHairStrands.h index cd0e46e00eeb..20a44bc9993e 100644 --- a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfaceHairStrands.h +++ b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfaceHairStrands.h @@ -658,13 +658,7 @@ struct FNDIHairStrandsProxy : public FNiagaraDataInterfaceProxy void DestroyPerInstanceData(NiagaraEmitterInstanceBatcher* Batcher, const FNiagaraSystemInstanceID& SystemInstance); /** Launch all pre stage functions */ - virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - - /** Launch all post stage functions */ - virtual void PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - - /** Reset the buffers */ - virtual void ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; + virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) override; /** List of proxy data for each system instances*/ TMap SystemInstancesToProxyData; diff --git a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfacePhysicsAsset.h b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfacePhysicsAsset.h index f9568617c538..bd122ec9cea0 100644 --- a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfacePhysicsAsset.h +++ b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfacePhysicsAsset.h @@ -235,15 +235,6 @@ struct FNDIPhysicsAssetProxy : public FNiagaraDataInterfaceProxy /** Destroy the proxy data if necessary */ void DestroyPerInstanceData(NiagaraEmitterInstanceBatcher* Batcher, const FNiagaraSystemInstanceID& SystemInstance); - /** Launch all pre stage functions */ - virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - - /** Launch all post stage functions */ - virtual void PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - - /** Reset the buffers */ - virtual void ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - /** List of proxy data for each system instances*/ TMap SystemInstancesToProxyData; }; diff --git a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfacePressureGrid.h b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfacePressureGrid.h index 98b8370366fb..e81c9133ddb7 100644 --- a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfacePressureGrid.h +++ b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfacePressureGrid.h @@ -62,6 +62,6 @@ public: struct FNDIPressureGridProxy : public FNDIVelocityGridProxy { /** Launch all pre stage functions */ - virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; + virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) override; }; diff --git a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfaceVelocityGrid.h b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfaceVelocityGrid.h index f53c9b88fd00..211e029a0ebb 100644 --- a/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfaceVelocityGrid.h +++ b/Engine/Plugins/Experimental/HairStrands/Source/HairStrandsNiagara/Public/NiagaraDataInterfaceVelocityGrid.h @@ -185,13 +185,13 @@ struct FNDIVelocityGridProxy : public FNiagaraDataInterfaceProxy virtual void ConsumePerInstanceDataFromGameThread(void* PerInstanceData, const FNiagaraSystemInstanceID& Instance) override; /** Launch all pre stage functions */ - virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; + virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) override; /** Launch all post stage functions */ - virtual void PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; + virtual void PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) override; /** Reset the buffers */ - virtual void ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; + virtual void ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) override; /** List of proxy data for each system instances*/ TMap SystemInstancesToProxyData; diff --git a/Engine/Plugins/Experimental/ImagePlate/Source/ImagePlate/Private/ImagePlateComponent.cpp b/Engine/Plugins/Experimental/ImagePlate/Source/ImagePlate/Private/ImagePlateComponent.cpp index 5df0d23fd20c..c6239f549d26 100644 --- a/Engine/Plugins/Experimental/ImagePlate/Source/ImagePlate/Private/ImagePlateComponent.cpp +++ b/Engine/Plugins/Experimental/ImagePlate/Source/ImagePlate/Private/ImagePlateComponent.cpp @@ -82,7 +82,7 @@ namespace Material = InComponent->GetPlate().DynamicMaterial ? InComponent->GetPlate().DynamicMaterial : InComponent->GetPlate().Material; if (Material) { - MaterialRelevance |= Material->GetRelevance(GetScene().GetFeatureLevel()); + MaterialRelevance |= Material->GetRelevance_Concurrent(GetScene().GetFeatureLevel()); } FColor NewPropertyColor; diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/LiveStreamAnimation.uplugin b/Engine/Plugins/Experimental/LiveStreamAnimation/LiveStreamAnimation.uplugin deleted file mode 100644 index 94c477322ec1..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/LiveStreamAnimation.uplugin +++ /dev/null @@ -1,38 +0,0 @@ -{ - "FileVersion": 3, - - "FriendlyName": "Live Stream Animation Plugin", - "Version": 1, - "VersionName": "1.0", - "Description": "Used to live animation data from a single client to multiple other clients", - "Category": "Animation", - "CreatedBy": "Epic Games, Inc.", - "CreatedByURL": "http://epicgames.com", - "EnabledByDefault": false, - - "Modules": [ - { - "Name": "LiveStreamAnimation", - "Type": "Runtime", - "LoadingPhase": "Default" - }, - { - "Name": "LSAEditor", - "Type": "Editor", - "LoadingPhase": "Default", - "BlacklistPlatforms": [ - "Mac" - ] - } - ], - "Plugins": [ - { - "Name": "ForwardingChannels", - "Enabled": true - }, - { - "Name": "LiveLink", - "Enabled": true - } - ] -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/LSAEditor.Build.cs b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/LSAEditor.Build.cs deleted file mode 100644 index 5e83bca0d00b..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/LSAEditor.Build.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -namespace UnrealBuildTool.Rules -{ - public class LSAEditor : ModuleRules - { - public LSAEditor(ReadOnlyTargetRules Target) : base(Target) - { - PrivateDependencyModuleNames.AddRange( - new string[] { - "LiveStreamAnimation", - "Core", - "CoreUObject", - "EditorFramework", - "UnrealEd", - "AssetTools", - "LiveLinkInterface", - "Engine", - "PropertyEditor", - "SlateCore", - "Slate", - "InputCore", - "EditorStyle", - } - ); - } - } -} diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAEditorModule.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAEditorModule.cpp deleted file mode 100644 index cb1ced13d750..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAEditorModule.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "LSAEditorModule.h" -#include "LiveLink/LSALiveLinkFrameTranslatorAssetActions.h" -#include "LSAHandleDetailCustomization.h" -#include "IAssetTools.h" - -IMPLEMENT_MODULE(FLSAEditorModule, LSAEditor) - -static const FName AssetToolsModuleName(TEXT("AssetTools")); -static const FName PropertyEditorModuleName(TEXT("PropertyEditor")); - -void FLSAEditorModule::StartupModule() -{ - TSharedRef LocalFrameTranslatorActions = MakeShared(); - - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked(AssetToolsModuleName).Get(); - AssetTools.RegisterAssetTypeActions(LocalFrameTranslatorActions); - FrameTranslatorActions = LocalFrameTranslatorActions; - - FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked(PropertyEditorModuleName); - PropertyModule.RegisterCustomPropertyTypeLayout(FLiveStreamAnimationHandleWrapper::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(FLSAHandleDetailCustomization::MakeInstance)); - -} - -void FLSAEditorModule::ShutdownModule() -{ - if (FModuleManager::Get().IsModuleLoaded(AssetToolsModuleName)) - { - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked(AssetToolsModuleName).Get(); - AssetTools.UnregisterAssetTypeActions(FrameTranslatorActions.ToSharedRef()); - } - - if (FModuleManager::Get().IsModuleLoaded(PropertyEditorModuleName)) - { - FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked(PropertyEditorModuleName); - PropertyModule.UnregisterCustomPropertyTypeLayout(FLiveStreamAnimationHandleWrapper::StaticStruct()->GetFName()); - } - - FrameTranslatorActions.Reset(); -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAEditorModule.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAEditorModule.h deleted file mode 100644 index 85f6de5c6b9f..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAEditorModule.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "Modules/ModuleManager.h" -#include "Modules/ModuleInterface.h" -#include "IAssetTypeActions.h" - -class FLSAEditorModule : public IModuleInterface -{ -public: - - FLSAEditorModule() = default; - virtual ~FLSAEditorModule() = default; - - // IModuleInterface - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - static inline bool IsAvailable() - { - return FModuleManager::Get().IsModuleLoaded(GetModuleName()); - } - -protected: - - static FName GetModuleName() - { - static FName ModuleName = FName(TEXT("LSAEditor")); - return ModuleName; - } - -private: - - TSharedPtr FrameTranslatorActions; -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAHandleDetailCustomization.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAHandleDetailCustomization.cpp deleted file mode 100644 index aa5ac01b3f6b..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LSAHandleDetailCustomization.cpp +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "LSAHandleDetailCustomization.h" -#include "LiveStreamAnimationHandle.h" -#include "LiveStreamAnimationSettings.h" - -#include "DetailLayoutBuilder.h" -#include "DetailWidgetRow.h" - -#include "SlateFwd.h" -#include "Widgets/DeclarativeSyntaxSupport.h" -#include "Widgets/SWidget.h" -#include "Widgets/SCompoundWidget.h" -#include "Widgets/Input/SComboButton.h" -#include "Widgets/Input/SSearchBox.h" -#include "Widgets/Layout/SSeparator.h" -#include "Widgets/Views/SListView.h" - -DECLARE_DELEGATE_OneParam(FOnHandleSelectionChanged, FName); -DECLARE_DELEGATE_RetVal_OneParam(FName, FGetSelectedHandle, bool&); - -//~ This is based largely on SBoneSelectionWidget and SBoneTreeView from BoneSelectionWidget.h -class SLSAHandleSelectionWidget : public SCompoundWidget -{ -public: - - using ThisClass = SLSAHandleSelectionWidget; - - SLATE_BEGIN_ARGS(ThisClass) - : _OnHandleSelectionChanged() - , _OnGetSelectedHandle() - {} - - - /** set selected handle */ - SLATE_EVENT(FOnHandleSelectionChanged, OnHandleSelectionChanged); - - /** get selected handle **/ - SLATE_EVENT(FGetSelectedHandle, OnGetSelectedHandle); - - SLATE_END_ARGS(); - - void Construct(const FArguments& InArgs) - { - OnHandleSelectionChanged = InArgs._OnHandleSelectionChanged; - OnGetSelectedHandle = InArgs._OnGetSelectedHandle; - - ChildSlot - [ - SAssignNew(HandlePickerButton, SComboButton) - .OnGetMenuContent(FOnGetContent::CreateSP(this, &ThisClass::CreateHandleSelectionMenu)) - .ContentPadding(FMargin(4.0f, 2.0f, 4.0f, 2.0f)) - .ButtonContent() - [ - SNew(STextBlock) - .Text(this, &ThisClass::GetSelectedHandleNameText) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(this, &ThisClass::GetFinalToolTip) - ] - ]; - } - -private: - - using SHandleListView = SListView>; - - TOptional GetSelectedHandleName(bool& bMultiple) const - { - if (OnGetSelectedHandle.IsBound()) - { - return OnGetSelectedHandle.Execute(bMultiple); - } - - return TOptional(); - } - - FText GetSelectedHandleNameText() const - { - bool bMultiple = false; - TOptional SelectedHandleName = GetSelectedHandleName(bMultiple); - return SelectedHandleName ? FText::FromName(SelectedHandleName.GetValue()) : FText::GetEmpty(); - } - - FText GetFinalToolTip() const - { - return FText::Format( - NSLOCTEXT("LiveStreamAnimation", "HandleSelector_Tooltip", "Handle:{0}\n\nClick to choose a different handle"), - GetSelectedHandleNameText()); - } - - TSharedRef CreateHandleSelectionMenu() - { - TSharedRef HandleListView = SNew(SHandleListView) - .ListItemsSource(&HandleNameSourceList) - .OnGenerateRow(this, &ThisClass::MakeHandleListViewRowWidget) - .OnSelectionChanged(this, &ThisClass::OnHandleListViewSelectionChanged) - .SelectionMode(ESelectionMode::Single); - - bool bMultiple = false; - TOptional SelectedBone = GetSelectedHandleName(bMultiple); - if (bMultiple) - { - SelectedBone.Reset(); - } - - RebuildHandleListViewEntries(SelectedBone, HandleListView); - - TSharedRef HandleListViewMenu = SNew(SBox) - .Content() - [ - SNew(SBorder) - .Padding(6) - .BorderImage(FEditorStyle::GetBrush("NoBorder")) - .Content() - [ - SNew(SBox) - .WidthOverride(300) - .HeightOverride(512) - .Content() - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .AutoHeight() - [ - SNew(STextBlock) - .Font(FEditorStyle::GetFontStyle("BoldFont")) - .Text(NSLOCTEXT("LiveStreamAnimation", "HandleSelector_Title", "Select...")) - ] - + SVerticalBox::Slot() - .AutoHeight() - [ - SNew(SSeparator) - .SeparatorImage(FEditorStyle::GetBrush("Menu.Separator")) - .Orientation(Orient_Horizontal) - ] - + SVerticalBox::Slot() - .AutoHeight() - [ - SAssignNew(FilterTextWidget, SSearchBox) - .SelectAllTextWhenFocused(true) - .OnTextChanged(this, &ThisClass::OnHandleListViewFilterTextChanged, HandleListView) - .HintText(NSLOCTEXT("LiveStreamAnimation", "HandleSelector_Search", "Search...")) - ] - + SVerticalBox::Slot() - [ - HandleListView - ] - ] - ] - ]; - - return HandleListViewMenu; - } - - void OnHandleListViewFilterTextChanged(const FText& InFilterText, TSharedRef HandleListView) - { - FilterText = InFilterText; - RebuildHandleListViewEntries(TOptional(), HandleListView); - } - - void RebuildHandleListViewEntries(TOptional SelectedHandle, TSharedRef HandleListView) - { - HandleNameSourceList.Empty(); - - const TArrayView HandleNames = ULiveStreamAnimationSettings::GetHandleNames(); - for (const FName HandleName : HandleNames) - { - if (!FilterText.IsEmpty() && !HandleName.ToString().Contains(FilterText.ToString())) - { - continue; - } - - TSharedRef HandleListEntry = MakeShared(HandleName); - HandleNameSourceList.Add(HandleListEntry); - - if (SelectedHandle && SelectedHandle.GetValue() == HandleName) - { - HandleListView->SetItemSelection(HandleListEntry, true); - HandleListView->RequestScrollIntoView(HandleListEntry); - } - } - - HandleListView->RequestListRefresh(); - } - - TSharedRef MakeHandleListViewRowWidget(TSharedPtr InHandle, const TSharedRef& OwnerTable) - { - return SNew(STableRow>, OwnerTable) - .Content() - [ - SNew(STextBlock) - .HighlightText(FilterText) - .Text(FText::FromName(*InHandle)) - ]; - } - - void OnHandleListViewSelectionChanged(TSharedPtr HandleName, ESelectInfo::Type SelectInfo) - { - if (HandleName.IsValid() && SelectInfo != ESelectInfo::Direct) - { - OnHandleSelectionChanged.ExecuteIfBound(*HandleName); - } - - HandlePickerButton->SetIsOpen(false); - } - - TSharedPtr HandlePickerButton; - TSharedPtr FilterTextWidget; - - TArray> HandleNameSourceList; - FText FilterText; - - FOnHandleSelectionChanged OnHandleSelectionChanged; - FGetSelectedHandle OnGetSelectedHandle; -}; - -void FLSAHandleDetailCustomization::CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) -{ - auto FindStructMemberProperty = [PropertyHandle](FName PropertyName) - { - uint32 NumChildren = 0; - PropertyHandle->GetNumChildren(NumChildren); - for (uint32 ChildIdx = 0; ChildIdx < NumChildren; ++ChildIdx) - { - TSharedPtr ChildHandle = PropertyHandle->GetChildHandle(ChildIdx); - if (ChildHandle->GetProperty()->GetFName() == PropertyName) - { - return ChildHandle; - } - } - - return TSharedPtr(); - }; - - HandleProperty = FindStructMemberProperty(GET_MEMBER_NAME_CHECKED(FLiveStreamAnimationHandleWrapper, Handle)); - check(HandleProperty); - - if (HandleProperty->IsValidHandle()) - { - HeaderRow - .NameContent() - [ - PropertyHandle->CreatePropertyNameWidget() - ] - .ValueContent() - [ - SNew(SLSAHandleSelectionWidget) - .ToolTipText(PropertyHandle->GetToolTipText()) - .OnGetSelectedHandle(this, &ThisClass::GetSelectedHandle) - .OnHandleSelectionChanged(this, &ThisClass::OnHandleSelectionChanged) - ]; - } - else - { - ensureAlways(false); - UE_LOG(LogTemp, Warning, TEXT("")) - } -} - -void FLSAHandleDetailCustomization::CustomizeChildren(TSharedRef PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) -{ - -} - -FName FLSAHandleDetailCustomization::GetSelectedHandle(bool& bMultipleValues) const -{ - FString OutText; - - FPropertyAccess::Result Result = HandleProperty->GetValueAsFormattedString(OutText); - bMultipleValues = (Result == FPropertyAccess::MultipleValues); - - return FName(*OutText); -} - -void FLSAHandleDetailCustomization::OnHandleSelectionChanged(FName NewHandle) -{ - HandleProperty->SetValue(NewHandle); -} - -TSharedRef FLSAHandleDetailCustomization::MakeInstance() -{ - return MakeShared(); -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LiveLink/LSALiveLinkFrameTranslatorAssetActions.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LiveLink/LSALiveLinkFrameTranslatorAssetActions.h deleted file mode 100644 index 0ad1ae796bed..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LiveLink/LSALiveLinkFrameTranslatorAssetActions.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Toolkits/IToolkitHost.h" -#include "AssetTypeActions_Base.h" -#include "LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.h" - -class FLSALiveLinkFrameTranslatorAssetActions : public FAssetTypeActions_Base -{ -public: - //~ Begin IAssetTypeActions Implementation - virtual FText GetName() const override { return NSLOCTEXT("LiveStreamAnimation", "LiveLinkFrameTranslatorAssetActions_Name", "Live Stream Animation Live Link Frame Translator"); } - virtual FColor GetTypeColor() const override { return FColor(212, 97, 85); } - virtual UClass* GetSupportedClass() const override { return ULiveStreamAnimationLiveLinkFrameTranslator::StaticClass(); } - virtual bool CanFilter() override { return true; } - virtual bool IsImportedAsset() const override { return false; } - virtual TSharedPtr GetThumbnailOverlay(const FAssetData& AssetData) const override { return nullptr; } - virtual uint32 GetCategories() override { return EAssetTypeCategories::Animation; } - //~ End IAssetTypeActions Implementation -}; diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LiveLink/LSALiveLinkFrameTranslatorFactory.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LiveLink/LSALiveLinkFrameTranslatorFactory.cpp deleted file mode 100644 index ae6d13bae14f..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LiveLink/LSALiveLinkFrameTranslatorFactory.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserverd. - -#include "LiveLink/LSALiveLinkFrameTranslatorFactory.h" -#include "LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.h" -#include "AssetTypeCategories.h" - -ULSALiveLinkFrameTranslatorFactory::ULSALiveLinkFrameTranslatorFactory() -{ - bCreateNew = true; - bEditAfterNew = true; - SupportedClass = ULiveStreamAnimationLiveLinkFrameTranslator::StaticClass(); -} - -//~ Begin UFactory Interface -uint32 ULSALiveLinkFrameTranslatorFactory::GetMenuCategories() const -{ - return EAssetTypeCategories::Animation; -} - -UObject* ULSALiveLinkFrameTranslatorFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - ULiveStreamAnimationLiveLinkFrameTranslator* Translator = NewObject(InParent, InClass, InName, Flags); - return Translator; -} - -bool ULSALiveLinkFrameTranslatorFactory::ShouldShowInNewMenu() const -{ - return true; -} -//~ End UFactory Interface \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LiveLink/LSALiveLinkFrameTranslatorFactory.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LiveLink/LSALiveLinkFrameTranslatorFactory.h deleted file mode 100644 index 6ab62ef5a4ac..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LSAEditor/Private/LiveLink/LSALiveLinkFrameTranslatorFactory.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserverd. - -#pragma once - -#include "CoreMinimal.h" -#include "Factories/Factory.h" -#include "LSALiveLinkFrameTranslatorFactory.generated.h" - -UCLASS() -class LSAEDITOR_API ULSALiveLinkFrameTranslatorFactory : public UFactory -{ - GENERATED_BODY() - -public: - - ULSALiveLinkFrameTranslatorFactory(); - - //~ Begin UFactory Interface - virtual uint32 GetMenuCategories() const override; - virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; - virtual bool ShouldShowInNewMenu() const override; - //~ End UFactory Interface -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/LiveStreamAnimation.Build.cs b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/LiveStreamAnimation.Build.cs deleted file mode 100644 index 1565d1f49891..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/LiveStreamAnimation.Build.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; - -public class LiveStreamAnimation : ModuleRules -{ - public LiveStreamAnimation(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - - PrivateDependencyModuleNames.AddRange( - new string[] { - "ForwardingChannels", - "LiveLinkInterface", - "LiveLink" - } - ); - - PublicDependencyModuleNames.AddRange( - new string[] { - "Core", - "CoreUObject", - "Engine", - "DeveloperSettings" - } - ); - } -} diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/ControlPacket.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/ControlPacket.cpp deleted file mode 100644 index 64360fe4c5db..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/ControlPacket.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "ControlPacket.h" -#include "LiveStreamAnimationLog.h" -#include "LiveStreamAnimationHandle.h" - -namespace LiveStreamAnimation -{ - - FControlPacket::~FControlPacket() - { - } - - void FControlPacket::WriteToStream(FArchive& InWriter, const FControlPacket& InPacket) - { - uint8 PacketTypeValue = static_cast(InPacket.PacketType); - InWriter << PacketTypeValue; - - if (InWriter.IsError()) - { - return; - } - - switch (InPacket.PacketType) - { - case EControlPacketType::Initial: - break; - - default: - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FControlPacket::WriteToStream: Invalid packet type %d"), static_cast(InPacket.PacketType)); - InWriter.SetError(); - break; - } - } - - TUniquePtr FControlPacket::ReadFromStream(FArchive& InReader) - { - FLiveStreamAnimationHandle Handle; - uint8 PacketTypeValue = 0; - InReader << PacketTypeValue; - - if (InReader.IsError()) - { - return nullptr; - } - - const EControlPacketType PacketType = static_cast(PacketTypeValue); - - switch (PacketType) - { - case EControlPacketType::Initial: - return MakeUnique(); - - default: - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FControlPacket::ReadFromStream: Invalid packet type %d"), static_cast(PacketType)); - InReader.SetError(); - return nullptr; - } - } -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/ControlPacket.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/ControlPacket.h deleted file mode 100644 index 7a9582f95727..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/ControlPacket.h +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserverd. - -#pragma once - -#include "Templates/UniquePtr.h" -#include "LiveStreamAnimationPacket.h" - -namespace LiveStreamAnimation -{ - enum class EControlPacketType : uint8 - { - Initial - }; - - /** - * Generic packet that is used as a base for all Live Stream Animation Control - * @see EControlPacketType for the types of packets. - */ - class FControlPacket - { - public: - - virtual ~FControlPacket() = 0; - - static constexpr ELiveStreamAnimationPacketType GetAnimationPacketType() - { - return ELiveStreamAnimationPacketType::Control; - } - - EControlPacketType GetPacketType() const - { - return PacketType; - } - - /** - * Writes a Control Packet to the given archive. - * - * @param InWriter The archive to write into. - * @param InPacket The packet to write. - */ - static void WriteToStream(class FArchive& InWriter, const FControlPacket& InPacket); - - /** - * Reads a Control Packet from the given archive. - * The type read can be determined by using GetPacketType() on the resulting packet. - * If we fail to read the packet, nullptr will be returned. - * - * @param InReader The archive to read from. - * - * @return The read packet, or null if serialization failed. - */ - static TUniquePtr ReadFromStream(class FArchive& InReader); - - protected: - - FControlPacket(const EControlPacketType InPacketType) - : PacketType(InPacketType) - { - } - - private: - - const EControlPacketType PacketType; - }; - - class FControlInitialPacket : public FControlPacket - { - public: - - FControlInitialPacket() - : FControlPacket(EControlPacketType::Initial) - { - } - - virtual ~FControlInitialPacket() - { - } - }; -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkPacket.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkPacket.cpp deleted file mode 100644 index 5fa4c3a3b305..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkPacket.cpp +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "LiveLinkPacket.h" -#include "LiveStreamAnimationLog.h" - -#include "UObject/CoreNet.h" -#include "Math/NumericLimits.h" -#include "Serialization/Archive.h" - -namespace LiveStreamAnimation -{ - struct FWriteToStreamParams - { - class FArchive& Writer; - const FLiveLinkPacket& InPacket; - }; - - struct FReadFromStreamParams - { - class FArchive& Reader; - const FLiveStreamAnimationHandle SubjectHandle; - }; - - //~ Begin FLiveLinkPacket + Generic Serialization - FLiveLinkPacket::~FLiveLinkPacket() - { - } - - void FLiveLinkPacket::WriteToStream(FArchive& InWriter, const FLiveLinkPacket& InPacket) - { - uint8 PacketTypeValue = static_cast(InPacket.PacketType); - InWriter << PacketTypeValue; - InWriter << const_cast(InPacket.SubjectHandle); - - if (InWriter.IsError()) - { - return; - } - - FWriteToStreamParams Params{InWriter, InPacket}; - - switch (InPacket.PacketType) - { - case ELiveLinkPacketType::AddOrUpdateSubject: - FLiveLinkAddOrUpdateSubjectPacket::WriteToStream(Params); - break; - - case ELiveLinkPacketType::RemoveSubject: - FLiveLinkRemoveSubjectPacket::WriteToStream(Params); - break; - - case ELiveLinkPacketType::AnimationFrame: - FLiveLinkAnimationFramePacket::WriteToStream(Params); - break; - - default: - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkPacket::WriteToStream: Invalid packet type %d"), static_cast(InPacket.PacketType)); - InWriter.SetError(); - break; - } - } - - TUniquePtr FLiveLinkPacket::ReadFromStream(FArchive& InReader) - { - FLiveStreamAnimationHandle Handle; - uint8 PacketTypeValue = 0; - InReader << PacketTypeValue; - InReader << Handle; - - if (InReader.IsError()) - { - return nullptr; - } - - const ELiveLinkPacketType PacketType = static_cast(PacketTypeValue); - - FReadFromStreamParams Params{InReader, Handle}; - - switch (PacketType) - { - case ELiveLinkPacketType::AddOrUpdateSubject: - return FLiveLinkAddOrUpdateSubjectPacket::ReadFromStream(Params); - - case ELiveLinkPacketType::RemoveSubject: - return FLiveLinkRemoveSubjectPacket::ReadFromStream(Params); - - case ELiveLinkPacketType::AnimationFrame: - return FLiveLinkAnimationFramePacket::ReadFromStream(Params); - - default: - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkPacket::ReadFromStream: Invalid packet type %d"), static_cast(PacketType)); - InReader.SetError(); - return nullptr; - } - } - //~ End FLiveLinkPacket + Generic Serialization. - - //~ Begin FLiveLinkAddOrUpdateSubjectPacket Serialization. - static void SerializeStaticData(FArchive& InAr, FLiveStreamAnimationLiveLinkStaticData& Data) - { - static constexpr uint32 MaxSize = static_cast(TNumericLimits::Max()); - - uint32 UnsignedArraySize = static_cast(Data.BoneNames.Num()); - InAr.SerializeIntPacked(UnsignedArraySize); - - const int32 ArraySize = static_cast(UnsignedArraySize); - if (UnsignedArraySize > MaxSize || ArraySize <= 0) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("SerializeStaticData: Invalid array size %d"), ArraySize); - InAr.SetError(); - return; - } - - if (InAr.IsLoading()) - { - Data.BoneNames.SetNumUninitialized(ArraySize); - Data.BoneParents.SetNumUninitialized(ArraySize); - } - - for (int32 i = 0; i < ArraySize; ++i) - { - InAr.SerializeIntPacked(reinterpret_cast(Data.BoneParents[i])); - } - - for (int32 i = 0; i < ArraySize; ++i) - { - InAr << Data.BoneNames[i]; - } - } - - static bool ValidateStaticData(const FLiveLinkSkeletonStaticData& InStaticData) - { - if (InStaticData.BoneParents.Num() != InStaticData.BoneNames.Num()) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("LiveStreamAnimation::ValidateStaticData: Invalid number of bones and parents. Bones=%d, Parents=%d"), - InStaticData.BoneNames.Num(), InStaticData.BoneParents.Num()); - - return false; - } - - return true; - } - - TUniquePtr FLiveLinkAddOrUpdateSubjectPacket::CreatePacket( - const FLiveStreamAnimationHandle InSubjectHandle, - FLiveStreamAnimationLiveLinkStaticData&& InStaticData) - { - if (!InSubjectHandle.IsValid()) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkAddOrUpdateSubjectPacket::CreatePacket: Invalid subject handle.")); - return nullptr; - } - - if (!ValidateStaticData(InStaticData)) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkAddOrUpdateSubjectPacket::CreatePacket: Invalid static data.")); - return nullptr; - } - - return TUniquePtr(new FLiveLinkAddOrUpdateSubjectPacket( - InSubjectHandle, - MoveTemp(InStaticData) - )); - } - - void FLiveLinkAddOrUpdateSubjectPacket::WriteToStream(FWriteToStreamParams& Params) - { - const FLiveLinkAddOrUpdateSubjectPacket& Packet = static_cast(Params.InPacket); - SerializeStaticData(Params.Writer, const_cast(Packet.StaticData)); - } - - TUniquePtr FLiveLinkAddOrUpdateSubjectPacket::ReadFromStream(FReadFromStreamParams& Params) - { - FName SubjectName; - FLiveStreamAnimationLiveLinkStaticData StaticData; - SerializeStaticData(Params.Reader, StaticData); - - if (!Params.Reader.IsError()) - { - return FLiveLinkAddOrUpdateSubjectPacket::CreatePacket(Params.SubjectHandle, MoveTemp(StaticData)); - } - - return nullptr; - } - //~ End FLiveLinkAddOrUpdateSubjectPacket Serialization. - - //~ Begin FLiveLinkRemoveSubjectPacketSerialization. - TUniquePtr FLiveLinkRemoveSubjectPacket::CreatePacket(const FLiveStreamAnimationHandle InSubjectHandle) - { - if (!InSubjectHandle.IsValid()) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkRemoveSubjectPacket::CreatePacket: Invalid subject handle.")); - return nullptr; - } - - return TUniquePtr(new FLiveLinkRemoveSubjectPacket(InSubjectHandle)); - } - - void FLiveLinkRemoveSubjectPacket::WriteToStream(FWriteToStreamParams& Params) - { - // Nothing extra to write, other than standard packet data. - } - - TUniquePtr FLiveLinkRemoveSubjectPacket::ReadFromStream(FReadFromStreamParams& Params) - { - // Nothing extra to read, other than standard packet data. - return FLiveLinkRemoveSubjectPacket::CreatePacket(Params.SubjectHandle); - } - //~ End FLiveLinkRemoveSubjectPacketSerialization - - //~ Begin FLiveLinkAnimationFramePacket Serialization. - static void SerializeFrameData(FArchive& InAr, FLiveStreamAnimationLiveLinkFrameData& Data) - { - const bool bIsLoading = InAr.IsLoading(); - - uint8 PackedOptions = 0; - - // TODO: Both options and translation profile should probably be sent with skeleton data instead to - // save bandwidth, since they aren't going to change from frame to frame. - // However, at a minimum we'd need to track a subject data version, so we could throw - // out stale packets that had data we could no longer process if an update occurred - // that changed those settings. - - // TODO: It would also be nice just to use a BitWriter / BitReader here, but that would require - // some additional fixup in the FForwardingChannels plugin. - - if (!bIsLoading) - { - const bool bIsTranslationProfileValid = Data.TranslationProfileHandle.IsValid(); - - PackedOptions = ( - Data.Options.bWithSceneTime << 7 | - Data.Options.bWithStringMetaData << 5 | - Data.Options.bWithPropertyValues << 4 | - Data.Options.bWithTransformTranslation << 3 | - Data.Options.bWithTransformRotation << 2 | - Data.Options.bWithTransformScale << 1 | - (bIsTranslationProfileValid ? 1 : 0) - ); - } - - InAr << PackedOptions; - - if (bIsLoading) - { - Data.Options.bWithSceneTime = (0x1 & (PackedOptions >> 7)); - Data.Options.bWithStringMetaData = (0x1 & (PackedOptions >> 5)); - Data.Options.bWithPropertyValues = (0x1 & (PackedOptions >> 4)); - Data.Options.bWithTransformTranslation = (0x1 & (PackedOptions >> 3)); - Data.Options.bWithTransformRotation = (0x1 & (PackedOptions >> 2)); - Data.Options.bWithTransformScale = (0x1 & (PackedOptions >> 1)); - - } - - const bool bIsTranslationProfileValid = (0x1 & (PackedOptions)) != 0; - if (bIsTranslationProfileValid) - { - InAr << Data.TranslationProfileHandle; - } - - if (Data.Options.bWithSceneTime && !InAr.IsError()) - { - FQualifiedFrameTime& SceneTime = Data.MetaData.SceneTime; - - InAr << SceneTime.Time.FrameNumber.Value; - InAr << SceneTime.Rate.Numerator; - InAr << SceneTime.Rate.Denominator; - - float SubFrame = SceneTime.Time.GetSubFrame(); - InAr << SubFrame; - - if (bIsLoading) - { - SceneTime.Time = FFrameTime(SceneTime.Time.FrameNumber, SubFrame); - } - } - - if (Data.Options.bWithStringMetaData && !InAr.IsError()) - { - InAr << Data.MetaData.StringMetaData; - } - - if (Data.Options.WithTransforms() && !InAr.IsError()) - { - int32 NumTransforms = Data.Transforms.Num(); - InAr << NumTransforms; - - if (bIsLoading) - { - Data.Transforms.SetNum(NumTransforms); - } - - // TODO: Quantization, Compression, etc.? - FVector Translation(0.f); - FQuat Rotation = FQuat::Identity; - FVector Scale(1.f,1.f,1.f); - for (FTransform& Transform : Data.Transforms) - { - if (bIsLoading) - { - if (Data.Options.bWithTransformTranslation) - { - InAr << Translation; - } - if (Data.Options.bWithTransformRotation) - { - InAr << Rotation; - } - if (Data.Options.bWithTransformScale) - { - InAr << Scale; - } - - Transform.SetComponents(Rotation, Translation, Scale); - } - else - { - if (Data.Options.bWithTransformTranslation) - { - Translation = Transform.GetTranslation(); - InAr << Translation; - } - if (Data.Options.bWithTransformRotation) - { - Rotation = Transform.GetRotation(); - InAr << Rotation; - } - if (Data.Options.bWithTransformScale) - { - Scale = Transform.GetScale3D(); - InAr << Scale; - } - } - } - } - - if (Data.Options.bWithPropertyValues && !InAr.IsError()) - { - int32 NumProperties = Data.PropertyValues.Num(); - InAr << NumProperties; - - if (bIsLoading) - { - Data.PropertyValues.SetNumUninitialized(NumProperties); - } - - for (float& PropertyValue : Data.PropertyValues) - { - InAr << PropertyValue; - } - } - } - - TUniquePtr FLiveLinkAnimationFramePacket::CreatePacket( - const FLiveStreamAnimationHandle InSubjectHandle, - FLiveStreamAnimationLiveLinkFrameData&& InFrameData) - { - if (!InSubjectHandle.IsValid()) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkAnimationFramePacket::CreatePacket: Invalid subject handle.")); - return nullptr; - } - - const bool bWithTransforms = InFrameData.Options.WithTransforms(); - const int32 NumTransforms = bWithTransforms ? InFrameData.Transforms.Num() : 0; - const int32 NumProperties = InFrameData.Options.bWithPropertyValues ? InFrameData.PropertyValues.Num() : 0; - - // We need at least some data to be sent, so either (or both) property values - // or transform data must be enabled. - if ((NumTransforms + NumProperties) == 0) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkAnimationFramePacket::CreatePacket: Must enable at least one transform component or property values")); - return nullptr; - } - - return TUniquePtr(new FLiveLinkAnimationFramePacket( - InSubjectHandle, - MoveTemp(InFrameData) - )); - } - - void FLiveLinkAnimationFramePacket::WriteToStream(FWriteToStreamParams& Params) - { - const FLiveLinkAnimationFramePacket& Packet = static_cast(Params.InPacket); - SerializeFrameData( - Params.Writer, - const_cast(Packet.FrameData) - ); - } - - TUniquePtr FLiveLinkAnimationFramePacket::ReadFromStream(FReadFromStreamParams& Params) - { - FLiveStreamAnimationLiveLinkFrameData FrameData; - SerializeFrameData(Params.Reader, FrameData); - - if (!Params.Reader.IsError()) - { - return FLiveLinkAnimationFramePacket::CreatePacket(Params.SubjectHandle, MoveTemp(FrameData)); - } - - return nullptr; - } - //~ End FLiveLinkAnimationFramePacket Serialization. -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkPacket.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkPacket.h deleted file mode 100644 index 390726e9b186..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkPacket.h +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "LiveStreamAnimationFwd.h" -#include "LiveLink/LiveStreamAnimationLiveLinkSourceOptions.h" -#include "LiveLink/LiveStreamAnimationLiveLinkFrameData.h" -#include "LiveStreamAnimationHandle.h" -#include "Roles/LiveLinkAnimationTypes.h" -#include "Templates/UniquePtr.h" -#include "LiveStreamAnimationPacket.h" - - -namespace LiveStreamAnimation -{ - struct FWriteToStreamParams; - struct FReadFromStreamParams; - - /** The types of Packets we'll process for Live Link. */ - enum class ELiveLinkPacketType : uint8 - { - AddOrUpdateSubject, //! Used to add a new Live Link Subject, or to update the skeleton - //! data of an already existing Live Link Subject. - - RemoveSubject, //! Used to remove a Live Link Subject. - - AnimationFrame //! Used to send a new animation update for a given subject. - //! Typically sent unreliably. - }; - - /** - * Generic packet that is used as a base for all Live Link packets. - * @see ELiveLinkPacketType for the types of packets. - */ - class FLiveLinkPacket - { - public: - - static constexpr ELiveStreamAnimationPacketType GetAnimationPacketType() - { - return ELiveStreamAnimationPacketType::LiveLink; - } - - virtual ~FLiveLinkPacket() = 0; - - ELiveLinkPacketType GetPacketType() const - { - return PacketType; - } - - FLiveStreamAnimationHandle GetSubjectHandle() const - { - return SubjectHandle; - } - - /** - * Writes a LiveLinkPacket to the given archive. - * - * @param InWriter The archive to write into. - * @param InPacket The packet to write. - */ - static void WriteToStream(class FArchive& InWriter, const FLiveLinkPacket& InPacket); - - /** - * Reads a LiveLinkPacket from the given archive. - * The type read can be determined by using GetPacketType() on the resulting packet. - * If we fail to read the packet, nullptr will be returned. - * - * @param InReader The archive to read from. - * - * @return The read packet, or null if serialization failed. - */ - static TUniquePtr ReadFromStream(class FArchive& InReader); - - protected: - - FLiveLinkPacket(const ELiveLinkPacketType InPacketType, const FLiveStreamAnimationHandle InSubjectHandle) - : PacketType(InPacketType) - , SubjectHandle(InSubjectHandle) - { - } - - private: - - const ELiveLinkPacketType PacketType; - const FLiveStreamAnimationHandle SubjectHandle; - }; - - class FLiveLinkAddOrUpdateSubjectPacket : public FLiveLinkPacket - { - public: - - virtual ~FLiveLinkAddOrUpdateSubjectPacket() {} - - const FLiveStreamAnimationLiveLinkStaticData& GetStaticData() const - { - return StaticData; - } - - /** - * Creates a new AddOrUpdateSubject Packet. - * May return null if the passed in parameters aren't valid. - * - * @param InSubjectHandle Handle that should be assigned to this subject. - * @param InSubjectName The Name for the Live Link Subject. - * @param InStaticData Live Link data needed to instantiate the subject. - * - * - * @return The newly created packet. - */ - static TUniquePtr CreatePacket( - const FLiveStreamAnimationHandle InSubjectHandle, - FLiveStreamAnimationLiveLinkStaticData&& InStaticData); - - private: - - friend FLiveLinkPacket; - static void WriteToStream(struct FWriteToStreamParams&); - static TUniquePtr ReadFromStream(struct FReadFromStreamParams&); - - FLiveLinkAddOrUpdateSubjectPacket( - const FLiveStreamAnimationHandle InSubjectHandle, - FLiveStreamAnimationLiveLinkStaticData&& InStaticData) - - : FLiveLinkPacket(ELiveLinkPacketType::AddOrUpdateSubject, InSubjectHandle) - , StaticData(MoveTemp(InStaticData)) - { - } - - const FLiveStreamAnimationLiveLinkStaticData StaticData; - }; - - class FLiveLinkRemoveSubjectPacket : public FLiveLinkPacket - { - public: - - virtual ~FLiveLinkRemoveSubjectPacket() {} - - /** - * Creates a new RemoveSubject Packet. - * May return null if the passed in parameters aren't valid. - * - * @param InSubjectHandle Subject to remove. - * - * @return The newly created packet. - */ - static TUniquePtr CreatePacket(const FLiveStreamAnimationHandle InSubjectHandle); - - private: - - friend FLiveLinkPacket; - static void WriteToStream(struct FWriteToStreamParams&); - static TUniquePtr ReadFromStream(struct FReadFromStreamParams&); - - FLiveLinkRemoveSubjectPacket(const FLiveStreamAnimationHandle InSubjectHandle) - : FLiveLinkPacket(ELiveLinkPacketType::RemoveSubject, InSubjectHandle) - { - } - }; - - class FLiveLinkAnimationFramePacket : public FLiveLinkPacket - { - public: - - virtual ~FLiveLinkAnimationFramePacket() {} - - - const FLiveStreamAnimationLiveLinkFrameData& GetFrameData() const - { - return FrameData; - } - - /** - * Creates a new AnimationFrame Packet. - * May return null if the passed in parameters aren't valid. - * - * @param InSubjectHandle Subject to update. - * @param InOptions The options to use when serializing the animation data. - * @param InFrameData The Animation data that we'll read from. - * - * @return The newly created packet. - */ - static TUniquePtr CreatePacket( - const FLiveStreamAnimationHandle InSubjectHandle, - FLiveStreamAnimationLiveLinkFrameData&& InFrameData); - - private: - - friend FLiveLinkPacket; - static void WriteToStream(struct FWriteToStreamParams&); - static TUniquePtr ReadFromStream(struct FReadFromStreamParams&); - - FLiveLinkAnimationFramePacket( - const FLiveStreamAnimationHandle InSubjectHandle, - FLiveStreamAnimationLiveLinkFrameData&& InFrameData) - - : FLiveLinkPacket(ELiveLinkPacketType::AnimationFrame, InSubjectHandle) - , FrameData(MoveTemp(InFrameData)) - { - } - - const FLiveStreamAnimationLiveLinkFrameData FrameData; - }; -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkStreamingHelper.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkStreamingHelper.cpp deleted file mode 100644 index 73f44587c1e7..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkStreamingHelper.cpp +++ /dev/null @@ -1,493 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserverd. - -#include "LiveLinkStreamingHelper.h" - -#include "LiveStreamAnimationLog.h" -#include "LiveStreamAnimationSubsystem.h" -#include "LiveStreamAnimationSettings.h" -#include "LiveStreamAnimationPacket.h" -#include "LiveLink/LiveLinkPacket.h" -#include "LiveLink/LiveStreamAnimationLiveLinkSource.h" -#include "LiveLink/Test/SkelMeshToLiveLinkSource.h" - -#include "Roles/LiveLinkAnimationRole.h" -#include "ILiveLinkClient.h" -#include "Serialization/MemoryReader.h" -#include "Features/IModularFeatures.h" -#include "CoreGlobals.h" - -namespace LiveStreamAnimation -{ - static ILiveLinkClient* GetLiveLinkClient() - { - IModularFeatures& ModularFeatures = IModularFeatures::Get(); - if (!ModularFeatures.IsModularFeatureAvailable(ILiveLinkClient::ModularFeatureName)) - { - UE_LOG(LogLiveStreamAnimation, Error, TEXT("GetLiveLinkClient: Live Link Unavailable.")); - return nullptr; - } - - return &ModularFeatures.GetModularFeature(ILiveLinkClient::ModularFeatureName); - } - - FLiveLinkStreamingHelper::FLiveLinkStreamingHelper(ULiveStreamAnimationSubsystem& InSubsystem) - : Subsystem(InSubsystem) - , OnRoleChangedHandle(Subsystem.GetOnRoleChanged().AddRaw(this, &FLiveLinkStreamingHelper::OnRoleChanged)) - , OnFrameTranslatorChangedHandle(ULiveStreamAnimationSettings::AddFrameTranslatorChangedCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FLiveLinkStreamingHelper::OnFrameTranslatorChanged))) - { - if (ELiveStreamAnimationRole::Processor == Subsystem.GetRole()) - { - StartProcessingPackets(); - } - } - - FLiveLinkStreamingHelper::~FLiveLinkStreamingHelper() - { - RemoveAllSubjects(); - StopProcessingPackets(); - - if (SkelMeshToLiveLinkSource.IsValid()) - { - if (!IsEngineExitRequested()) - { - if (ILiveLinkClient* LiveLinkClient = GetLiveLinkClient()) - { - LiveLinkClient->RemoveSource(SkelMeshToLiveLinkSource); - } - } - } - - Subsystem.GetOnRoleChanged().Remove(OnRoleChangedHandle); - ULiveStreamAnimationSettings::RemoveFrameTranslatorChangedCallback(OnFrameTranslatorChangedHandle); - } - - void FLiveLinkStreamingHelper::HandleLiveLinkPacket(const TSharedRef& Packet) - { - // TODO: We could probably add a way to peak Live Link Packet Type - // and just ignore Animation updates if we aren't going to - // process them, since we don't need to keep those records - // up to date. - // This could help perf, especially since non-animation updates - // would be rare. - - FMemoryReaderView Reader(Packet->GetPacketData()); - TUniquePtr LiveLinkPacketUniquePtr(FLiveLinkPacket::ReadFromStream(Reader)); - - if (FLiveLinkPacket* LiveLinkPacket = LiveLinkPacketUniquePtr.Get()) - { - if (FLiveStreamAnimationLiveLinkSource* LocalLiveLinkSource = LiveLinkSource.Get()) - { - LocalLiveLinkSource->HandlePacket(MoveTemp(*LiveLinkPacket)); - } - - const FLiveStreamAnimationHandle SubjectHandle = LiveLinkPacket->GetSubjectHandle(); - - // Now, update our records. - switch (LiveLinkPacket->GetPacketType()) - { - case ELiveLinkPacketType::RemoveSubject: - TrackedSubjects.Remove(SubjectHandle); - break; - - case ELiveLinkPacketType::AddOrUpdateSubject: - { - const FLiveLinkAddOrUpdateSubjectPacket& CastedPacket = static_cast(*LiveLinkPacket); - if (FLiveLinkTrackedSubject * FoundSubject = TrackedSubjects.Find(SubjectHandle)) - { - FoundSubject->LastKnownSkeleton = CastedPacket.GetStaticData(); - } - else - { - FLiveLinkTrackedSubject NewSubject{ - SubjectHandle.GetName(), // For processors and proxies, we don't care about the originating Live Link name. - // Instead we use the associated handle name. - SubjectHandle, - FLiveStreamAnimationLiveLinkSourceOptions(), // It's OK to use the default options here, because - // we won't actually be tracking anim, just forwarding them. - - FLiveStreamAnimationHandle(), - CastedPacket.GetStaticData() - }; - - TrackedSubjects.Add(SubjectHandle, NewSubject); - } - } - - break; - - default: - break; - } - } - else - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::HandleLiveLinkPacket: Received invalid Live Link Packet!")); - } - } - - void FLiveLinkStreamingHelper::StartProcessingPackets() - { - if (!LiveLinkSource.IsValid()) - { - if (ILiveLinkClient* LiveLinkClient = GetLiveLinkClient()) - { - LiveLinkSource = MakeShared(ULiveStreamAnimationSettings::GetFrameTranslator()); - LiveLinkClient->AddSource(StaticCastSharedPtr(LiveLinkSource)); - - // If we've already received data, go ahead and get our Source back up to date. - for (auto It = TrackedSubjects.CreateIterator(); It; ++It) - { - const FLiveLinkTrackedSubject& TrackedSubject = It.Value(); - - TUniquePtr Packet = FLiveLinkAddOrUpdateSubjectPacket::CreatePacket( - TrackedSubject.SubjectHandle, - FLiveLinkSkeletonStaticData(TrackedSubject.LastKnownSkeleton)); - - if (Packet.IsValid()) - { - LiveLinkSource->HandlePacket(MoveTemp(*Packet)); - } - } - } - } - } - - void FLiveLinkStreamingHelper::StopProcessingPackets() - { - if (!IsEngineExitRequested()) - { - if (LiveLinkSource.IsValid()) - { - if (ILiveLinkClient* LiveLinkClient = GetLiveLinkClient()) - { - LiveLinkClient->RemoveSource(StaticCastSharedPtr(LiveLinkSource)); - } - } - } - } - - bool FLiveLinkStreamingHelper::StartTrackingSubject( - const FName LiveLinkSubject, - const FLiveStreamAnimationHandle SubjectHandle, - const FLiveStreamAnimationLiveLinkSourceOptions Options, - const FLiveStreamAnimationHandle TranslationHandle) - { - if (LiveLinkSubject == NAME_None) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::StartTrackingSubject: Invalid LiveLinkSubject.")); - return false; - } - - if (!SubjectHandle.IsValid()) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::StartTrackingSubject: Invalid SubjectHandle.")); - return false; - } - - if (!Options.IsValid()) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::StartTrackingSubject: Invalid Options.")); - return false; - } - - if (FLiveLinkTrackedSubject* ExistingSubject = TrackedSubjects.Find(SubjectHandle)) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::StartTrackingSubject: Subject is already tracked. ExistingSubject = (%s)"), - *ExistingSubject->ToString()); - - return ExistingSubject->LiveLinkSubject == LiveLinkSubject; - } - - if (ILiveLinkClient * LiveLinkClient = GetLiveLinkClient()) - { - FLiveLinkSubjectName LiveLinkSubjectName(LiveLinkSubject); - - FDelegateHandle StaticDataReceivedHandle; - FOnLiveLinkSubjectStaticDataAdded::FDelegate OnStaticDataReceived; - OnStaticDataReceived.BindRaw(this, &FLiveLinkStreamingHelper::ReceivedStaticData, SubjectHandle); - - FDelegateHandle FrameDataReceivedHandle; - FOnLiveLinkSubjectFrameDataAdded::FDelegate OnFrameDataReceived; - OnFrameDataReceived.BindRaw(this, &FLiveLinkStreamingHelper::ReceivedFrameData, SubjectHandle); - - TSubclassOf SubjectRole; - FLiveLinkStaticDataStruct StaticData; - - bool bSuccess = false; - - const bool bWasRegistered = LiveLinkClient->RegisterForSubjectFrames( - LiveLinkSubjectName, - OnStaticDataReceived, - OnFrameDataReceived, - StaticDataReceivedHandle, - FrameDataReceivedHandle, - SubjectRole, - &StaticData); - - FLiveLinkTrackedSubject TrackedSubject{ - LiveLinkSubjectName, - SubjectHandle, - Options, - TranslationHandle, - FLiveLinkSkeletonStaticData(), - StaticDataReceivedHandle, - FrameDataReceivedHandle, - }; - - if (bWasRegistered) - { - if (!SubjectRole->IsChildOf(ULiveLinkAnimationRole::StaticClass())) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::StartTrackingSubject: Subject had invalid role, subject won't be sent. Subject = (%s), Role = %s"), - *TrackedSubject.ToString(), *GetPathNameSafe(SubjectRole.Get())); - } - else if (!StaticData.IsValid()) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::StartTrackingSubject: Subject didn't have static data. Subject will be sent later, when static data is received. Subject = (%s)"), - *TrackedSubject.ToString()); - - bSuccess = true; - TrackedSubjects.Add(SubjectHandle, TrackedSubject); - } - else - { - if (const FLiveLinkSkeletonStaticData* SkeletonDataPtr = StaticData.Cast()) - { - TrackedSubject.LastKnownSkeleton = *SkeletonDataPtr; - } - - if (SendPacketToServer(CreateAddOrUpdateSubjectPacket(TrackedSubject))) - { - bSuccess = true; - TrackedSubjects.Add(SubjectHandle, TrackedSubject); - } - else - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::StartTrackingSubject: Failed to send add subject packet. Subject = (%s)"), - *TrackedSubject.ToString()); - } - } - - if (!bSuccess) - { - LiveLinkClient->UnregisterSubjectFramesHandle(TrackedSubject.LiveLinkSubject, TrackedSubject.StaticDataReceivedHandle, TrackedSubject.FrameDataReceivedHandle); - } - } - else - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::StartTrackingSubject: Failed to register subject. Subject = (%s)"), - *TrackedSubject.ToString()); - } - - return bSuccess; - } - - return false; - } - - void FLiveLinkStreamingHelper::StopTrackingSubject(const FLiveStreamAnimationHandle SubjectHandle) - { - FLiveLinkTrackedSubject TrackedSubject; - if (TrackedSubjects.RemoveAndCopyValue(SubjectHandle, TrackedSubject)) - { - if (ILiveLinkClient* LiveLinkClient = GetLiveLinkClient()) - { - LiveLinkClient->UnregisterSubjectFramesHandle(TrackedSubject.LiveLinkSubject, TrackedSubject.StaticDataReceivedHandle, TrackedSubject.FrameDataReceivedHandle); - if (!SendPacketToServer(CreateRemoveSubjectPacket(TrackedSubject))) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::StopTrackingSubject: Failed to send remove packet to server. Subject = (%s)"), - *TrackedSubject.ToString()); - } - } - } - else - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::StopTrackingSubject: Unable to find subject. SubjectHandle = %s"), - *SubjectHandle.ToString()); - } - } - - void FLiveLinkStreamingHelper::RemoveAllSubjects() - { - if (!IsEngineExitRequested()) - { - if (ILiveLinkClient * LiveLinkClient = GetLiveLinkClient()) - { - for (auto It = TrackedSubjects.CreateIterator(); It; ++It) - { - // Don't send packets at this point, because we're shutting the subsystem down and any - // channels should have been closed already. - const FLiveLinkTrackedSubject& TrackedSubject = It.Value(); - LiveLinkClient->UnregisterSubjectFramesHandle(TrackedSubject.LiveLinkSubject, TrackedSubject.StaticDataReceivedHandle, TrackedSubject.FrameDataReceivedHandle); - } - } - - TrackedSubjects.Empty(); - } - } - - void FLiveLinkStreamingHelper::GetJoinInProgressPackets(TArray>& JoinInProgressPackets) - { - JoinInProgressPackets.Reserve(JoinInProgressPackets.Num() + TrackedSubjects.Num()); - - for (auto It = TrackedSubjects.CreateIterator(); It; ++It) - { - // We send these packets separately, in case the connection already had the subject registered - // but the skeleton changed since they were connected. - FLiveLinkTrackedSubject& TrackedSubject = It.Value(); - TSharedPtr AddOrUpdateSubjectPacket = CreateAddOrUpdateSubjectPacket(TrackedSubject); - if (AddOrUpdateSubjectPacket.IsValid()) - { - JoinInProgressPackets.Add(AddOrUpdateSubjectPacket.ToSharedRef()); - } - } - } - - void FLiveLinkStreamingHelper::ReceivedStaticData( - FLiveLinkSubjectKey InSubjectKey, - TSubclassOf InSubjectRole, - const FLiveLinkStaticDataStruct& InStaticData, - const FLiveStreamAnimationHandle SubjectHandle) - { - if (FLiveLinkTrackedSubject* TrackedSubject = TrackedSubjects.Find(SubjectHandle)) - { - bool bSentPacket = false; - if (const FLiveLinkSkeletonStaticData* StaticData = InStaticData.Cast()) - { - TrackedSubject->LastKnownSkeleton = *StaticData; - bSentPacket = SendPacketToServer(CreateAddOrUpdateSubjectPacket(*TrackedSubject)); - } - - if (!bSentPacket) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::ReceivedStaticData: Failed to send static data packet to server. Subject = (%s)"), - *TrackedSubject->ToString()); - } - } - else - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::ReceivedStaticData: Failed to find registered subject. SubjectHandle = (%s)"), - *SubjectHandle.ToString()); - } - } - - void FLiveLinkStreamingHelper::ReceivedFrameData( - FLiveLinkSubjectKey InSubjectKey, - TSubclassOf InSubjectRole, - const FLiveLinkFrameDataStruct& InFrameData, - const FLiveStreamAnimationHandle SubjectHandle) - { - if (const FLiveLinkTrackedSubject* TrackedSubject = TrackedSubjects.Find(SubjectHandle)) - { - bool bSentPacket = false; - if (const FLiveLinkAnimationFrameData * AnimationData = InFrameData.Cast()) - { - bSentPacket = SendPacketToServer(CreateAnimationFramePacket(*TrackedSubject, FLiveLinkAnimationFrameData(*AnimationData))); - } - - if (!bSentPacket) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::ReceivedFrameData: Failed to send anim packet to server. Subject = (%s)"), - *TrackedSubject->ToString()); - } - } - else - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveLinkStreamingHelper::ReceivedFrameData: Failed to find registered subject. SubjectHandle = (%s)"), - *SubjectHandle.ToString()); - } - } - - bool FLiveLinkStreamingHelper::SendPacketToServer(const TSharedPtr& PacketToSend) - { - if (PacketToSend.IsValid()) - { - Subsystem.SendPacketToServer(PacketToSend.ToSharedRef()); - return true; - } - - return false; - } - - static TSharedPtr WrapLiveLinkPacket( - const TUniquePtr& PacketToSend, - const bool bReliable) - { - TSharedPtr AnimPacket; - - if (PacketToSend.IsValid()) - { - AnimPacket = FLiveStreamAnimationPacket::CreateFromPacket(*PacketToSend); - if (AnimPacket.IsValid()) - { - AnimPacket->SetReliable(bReliable); - } - } - - return AnimPacket; - } - - TSharedPtr FLiveLinkStreamingHelper::CreateAddOrUpdateSubjectPacket(const FLiveLinkTrackedSubject& Subject) - { - return WrapLiveLinkPacket( - FLiveLinkAddOrUpdateSubjectPacket::CreatePacket( - Subject.SubjectHandle, - FLiveLinkSkeletonStaticData(Subject.LastKnownSkeleton)), - true); - } - - TSharedPtr FLiveLinkStreamingHelper::CreateRemoveSubjectPacket(const FLiveLinkTrackedSubject& Subject) - { - return WrapLiveLinkPacket( - FLiveLinkRemoveSubjectPacket::CreatePacket(Subject.SubjectHandle), - true); - } - - TSharedPtr FLiveLinkStreamingHelper::CreateAnimationFramePacket(const FLiveLinkTrackedSubject& Subject, FLiveLinkAnimationFrameData&& AnimationData) - { - return WrapLiveLinkPacket( - FLiveLinkAnimationFramePacket::CreatePacket( - Subject.SubjectHandle, - FLiveStreamAnimationLiveLinkFrameData(MoveTemp(AnimationData), Subject.Options, Subject.TranslationHandle)), - false); - } - - void FLiveLinkStreamingHelper::OnRoleChanged(ELiveStreamAnimationRole NewRole) - { - if (ELiveStreamAnimationRole::Processor == NewRole) - { - StartProcessingPackets(); - } - else - { - StopProcessingPackets(); - } - } - - void FLiveLinkStreamingHelper::OnFrameTranslatorChanged() - { - if (FLiveStreamAnimationLiveLinkSource* LocalSource = LiveLinkSource.Get()) - { - LocalSource->SetFrameTranslator(ULiveStreamAnimationSettings::GetFrameTranslator()); - } - } - - TWeakPtr FLiveLinkStreamingHelper::GetOrCreateSkelMeshToLiveLinkSource() - { - if (ELiveStreamAnimationRole::Tracker == Subsystem.GetRole()) - { - if (!SkelMeshToLiveLinkSource.IsValid()) - { - if (ILiveLinkClient* LiveLinkClient = GetLiveLinkClient()) - { - SkelMeshToLiveLinkSource = MakeShared(); - LiveLinkClient->AddSource(SkelMeshToLiveLinkSource); - } - } - - return SkelMeshToLiveLinkSource; - } - - return nullptr; - } -}; diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkStreamingHelper.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkStreamingHelper.h deleted file mode 100644 index e1c83f2dcf0b..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveLinkStreamingHelper.h +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserverd. - -#pragma once - -#include "CoreMinimal.h" -#include "LiveStreamAnimationFwd.h" -#include "LiveLinkTypes.h" -#include "Roles/LiveLinkAnimationTypes.h" -#include "LiveStreamAnimationHandle.h" -#include "LiveLink/LiveStreamAnimationLiveLinkSourceOptions.h" - -class ULiveLinkRole; -class ULiveStreamAnimationSubsystem; - -namespace LiveStreamAnimation -{ - class FSkelMeshToLiveLinkSource; - - class FLiveLinkStreamingHelper - { - private: - - struct FLiveLinkTrackedSubject - { - /** The actual Live Link subject we're reading frames from. */ - FLiveLinkSubjectName LiveLinkSubject; - - /** Streaming handle that we'll use to refer to this subject over the network. */ - FLiveStreamAnimationHandle SubjectHandle; - - /** Options used for animation frame updates. */ - FLiveStreamAnimationLiveLinkSourceOptions Options; - - /** Translation profile we will use for this subject. */ - FLiveStreamAnimationHandle TranslationHandle; - - /** The last sent skeleton data. */ - FLiveLinkSkeletonStaticData LastKnownSkeleton; - - FDelegateHandle StaticDataReceivedHandle; - FDelegateHandle FrameDataReceivedHandle; - - FString ToString() const - { - return FString::Printf(TEXT("LiveLinkSubject = %s, SubjectHandle = %s"), - *LiveLinkSubject.ToString(), *SubjectHandle.ToString()); - } - }; - - public: - - FLiveLinkStreamingHelper(ULiveStreamAnimationSubsystem& InSubsystem); - - ~FLiveLinkStreamingHelper(); - - void HandleLiveLinkPacket(const TSharedRef& Packet); - - void StartProcessingPackets(); - - void StopProcessingPackets(); - - bool StartTrackingSubject( - const FName LiveLinkSubject, - const FLiveStreamAnimationHandle SubjectHandle, - const FLiveStreamAnimationLiveLinkSourceOptions Options, - const FLiveStreamAnimationHandle TranslationHandle); - - void StopTrackingSubject(const FLiveStreamAnimationHandle SubjectHandle); - - void RemoveAllSubjects(); - - void GetJoinInProgressPackets(TArray>& JoinInProgressPackets); - - TWeakPtr GetOrCreateSkelMeshToLiveLinkSource(); - - private: - - void ReceivedStaticData( - FLiveLinkSubjectKey InSubjectKey, - TSubclassOf InSubjectRole, - const FLiveLinkStaticDataStruct& InStaticData, - const FLiveStreamAnimationHandle SubjectHandle); - - void ReceivedFrameData( - FLiveLinkSubjectKey InSubjectKey, - TSubclassOf InSubjectRole, - const FLiveLinkFrameDataStruct& InFrameData, - const FLiveStreamAnimationHandle SubjectHandle); - - bool SendPacketToServer(const TSharedPtr& PacketToSend); - - TSharedPtr CreateAddOrUpdateSubjectPacket(const FLiveLinkTrackedSubject& Subject); - - TSharedPtr CreateRemoveSubjectPacket(const FLiveLinkTrackedSubject& Subject); - - TSharedPtr CreateAnimationFramePacket(const FLiveLinkTrackedSubject& Subject, FLiveLinkAnimationFrameData&& AnimationData); - - void OnRoleChanged(ELiveStreamAnimationRole NewRole); - void OnFrameTranslatorChanged(); - - TSharedPtr SkelMeshToLiveLinkSource; - TSharedPtr LiveLinkSource; - TMap TrackedSubjects; - ULiveStreamAnimationSubsystem& Subsystem; - FDelegateHandle OnRoleChangedHandle; - FDelegateHandle OnFrameTranslatorChangedHandle; - }; -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkFrameData.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkFrameData.cpp deleted file mode 100644 index 167643cb94af..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkFrameData.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "LiveLink/LiveStreamAnimationLiveLinkFrameData.h" - -FLiveStreamAnimationLiveLinkStaticData::FLiveStreamAnimationLiveLinkStaticData() -{ -} - -FLiveStreamAnimationLiveLinkStaticData::FLiveStreamAnimationLiveLinkStaticData(FLiveLinkSkeletonStaticData&& SkeletonData) - : FLiveLinkSkeletonStaticData(MoveTemp(SkeletonData)) -{ -} - -FLiveStreamAnimationLiveLinkFrameData::FLiveStreamAnimationLiveLinkFrameData() -{ -} - -FLiveStreamAnimationLiveLinkFrameData::FLiveStreamAnimationLiveLinkFrameData( - FLiveLinkAnimationFrameData&& AnimFrameData, - const FLiveStreamAnimationLiveLinkSourceOptions& InOptions, - const FLiveStreamAnimationHandle& InTranslationProfileHandle) - - : FLiveLinkAnimationFrameData(MoveTemp(AnimFrameData)) - , Options(InOptions) - , TranslationProfileHandle(InTranslationProfileHandle) -{ -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.cpp deleted file mode 100644 index 7a17e5d0c407..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.cpp +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.h" -#include "LiveLink/LiveStreamAnimationLiveLinkRole.h" -#include "LiveLink/LiveStreamAnimationLiveLinkFrameData.h" -#include "LiveStreamAnimationLog.h" -#include "UObject/UnrealType.h" -#include "Animation/Skeleton.h" -#include "Misc/ScopeExit.h" -#include "Roles/LiveLinkAnimationRole.h" - -class FLiveStreamAnimationLiveLinkFrameTranslator : public ILiveLinkFrameTranslatorWorker -{ -public: - - FLiveStreamAnimationLiveLinkFrameTranslator(TMap&& InTranslationProfiles) - : TranslationProfiles(MoveTemp(InTranslationProfiles)) - { - } - - virtual ~FLiveStreamAnimationLiveLinkFrameTranslator() - { - } - - virtual TSubclassOf GetFromRole() const override - { - return ULiveStreamAnimationLiveLinkRole::StaticClass(); - } - - virtual TSubclassOf GetToRole() const override - { - return ULiveLinkAnimationRole::StaticClass(); - } - - virtual bool Translate(const FLiveLinkStaticDataStruct& InStaticData, const FLiveLinkFrameDataStruct& InFrameData, FLiveLinkSubjectFrameData& OutTranslatedFrame) const override - { - if (const FLiveStreamAnimationLiveLinkFrameData* FrameData = InFrameData.Cast()) - { - const FLiveStreamAnimationLiveLinkSourceOptions Options = FrameData->Options; - const FLiveStreamAnimationHandle TranslationProfileHandle = FrameData->TranslationProfileHandle; - - FLiveLinkAnimationFrameData* AnimFrameData = new FLiveLinkAnimationFrameData; - *AnimFrameData = *FrameData; - - ON_SCOPE_EXIT - { - OutTranslatedFrame.FrameData.InitializeWith(AnimFrameData); - OutTranslatedFrame.StaticData.InitializeWith(InStaticData); - }; - - // We don't have any transforms, so we don't need to translate. - if (!Options.WithTransforms()) - { - return true; - } - - // Handle was invalid, so we no translation is necessary. - if (!TranslationProfileHandle.IsValid()) - { - return true; - } - - const FLiveStreamAnimationLiveLinkTranslationProfile* TranslationProfile = TranslationProfiles.Find(TranslationProfileHandle); - const FLiveLinkSkeletonStaticData* StaticData = InStaticData.Cast(); - - // Skeleton was invalid, or we don't need to do any translation, so we're done. - if (!TranslationProfile || !StaticData) - { - return true; - } - - // If we only have a partial transforms, then we will need to get their ref poses. - // This is also where we could do quantization, etc. - if (!Options.bWithTransformTranslation || - !Options.bWithTransformRotation || - !Options.bWithTransformScale) - { - auto UpdateTransform = [Options](FTransform& OutTransform, const FTransform& DefaultTransform) - { - const FVector Translation = Options.bWithTransformTranslation ? OutTransform.GetTranslation() : DefaultTransform.GetTranslation(); - const FQuat Rotation = Options.bWithTransformRotation ? OutTransform.GetRotation() : DefaultTransform.GetRotation(); - const FVector Scale = Options.bWithTransformScale ? OutTransform.GetScale3D() : DefaultTransform.GetScale3D(); - - OutTransform.SetComponents(Rotation, Translation, Scale); - }; - - // TODO: Could probably add some Test Sanity Checks to ensure cached bone lookup matches Live Link Skeleton. - // Bonus points, have some status information on whether or not we already ran the validation, - // and whether or not it succeeded, so we can do this in shipping (requires locking, etc.) - - const TArray& BoneTransformsByIndex = TranslationProfile->GetBoneTransformsByIndex(); - if (AnimFrameData->Transforms.Num() == BoneTransformsByIndex.Num()) - { - for (int32 i = 0; i < AnimFrameData->Transforms.Num(); ++i) - { - UpdateTransform(AnimFrameData->Transforms[i], BoneTransformsByIndex[i]); - } - } - else - { - const TMap& PoseTransforms = TranslationProfile->GetBoneTransformsByName(); - for (int32 i = 0; i < StaticData->BoneNames.Num(); ++i) - { - const FName BoneName = StaticData->BoneNames[i]; - const FTransform& DefaultBoneTransform = PoseTransforms.FindChecked(BoneName); - FTransform& OutPoseTransform = AnimFrameData->Transforms[i]; - - UpdateTransform(OutPoseTransform, DefaultBoneTransform); - } - } - } - } - - return true; - } - -private: - - TMap TranslationProfiles; -}; - -bool FLiveStreamAnimationLiveLinkTranslationProfile::UpdateTransformMappings() -{ - BoneTransformsByName.Empty(); - BoneTransformsByIndex.Empty(); - - if (USkeleton* LocalSkeleton = Skeleton.Get()) - { - const FReferenceSkeleton& ReferenceSkeleton = LocalSkeleton->GetReferenceSkeleton(); - const TArray& RefBoneInfo = ReferenceSkeleton.GetRawRefBoneInfo(); - const TArray& RefBonePose = ReferenceSkeleton.GetRefBonePose(); - - BoneTransformsByName.Reserve(RefBoneInfo.Num()); - - // RefBoneInfo and RefBonePose should necessarily have the same number of entries, - // and each entry at the same index in each should necessarily reference the same - // bone information. - for (int32 i = 0; i < RefBoneInfo.Num() && i < RefBonePose.Num(); ++i) - { - const FName SkelBoneName = RefBoneInfo[i].Name; - const FName* FoundRemappedBone = BoneRemappings.Find(SkelBoneName); - const FName UseBoneName = FoundRemappedBone ? *FoundRemappedBone : SkelBoneName; - - if (BoneTransformsByName.Contains(UseBoneName)) - { - UE_LOG(LogLiveStreamAnimation, Warning, - TEXT("FLiveStreamAnimationLiveLinkTranslationProfile::UpdateTransformMappings: Duplicate bone name found when creating BoneMappings. This may cause broken animation. Bone=%s")); - } - - BoneTransformsByName.Add(UseBoneName, RefBonePose[i]); - } - - if (BonesToUse.Num() > 0) - { - TSet FoundBones; - FoundBones.Reserve(BonesToUse.Num()); - BoneTransformsByIndex.Reserve(BonesToUse.Num()); - - for (const FName& BoneToUse : BonesToUse) - { - if (FoundBones.Contains(BoneToUse)) - { - UE_LOG(LogLiveStreamAnimation, Warning, - TEXT("FLiveStreamAnimationLiveLinkTranslationProfile::UpdateTransformMappings: Duplicate bone name, cannot use cached mappings. Bone=%s"), - *BoneToUse.ToString()); - - BoneTransformsByIndex.Empty(); - break; - } - - if (FTransform* FoundTransform = BoneTransformsByName.Find(BoneToUse)) - { - BoneTransformsByIndex.Add(*FoundTransform); - } - else - { - UE_LOG(LogLiveStreamAnimation, Warning, - TEXT("FLiveStreamAnimationLiveLinkTranslationProfile::UpdateTransformMappings: Invalid bone name, cannot use cached mappings. Bone=%s"), - *BoneToUse.ToString()); - - BoneTransformsByIndex.Empty(); - break; - } - } - } - - return true; - } - - return false; -} - -TSubclassOf ULiveStreamAnimationLiveLinkFrameTranslator::GetFromRole() const -{ - return ULiveStreamAnimationLiveLinkRole::StaticClass(); -} - -TSubclassOf ULiveStreamAnimationLiveLinkFrameTranslator::GetToRole() const -{ - return ULiveLinkAnimationRole::StaticClass(); -} - -ULiveStreamAnimationLiveLinkFrameTranslator::FWorkerSharedPtr ULiveStreamAnimationLiveLinkFrameTranslator::FetchWorker() -{ - // TODO: This won't be needed in live scenarios, but for testing purposes - // it would *probably* be smart to hook into USkeleton's Bone Hierarchy Update - // and invalidate our old worker. - if (!Worker.IsValid()) - { - TMap LocalTranslationProfiles; - LocalTranslationProfiles.Reserve(TranslationProfiles.Num()); - - for (auto It = TranslationProfiles.CreateIterator(); It; ++It) - { - const FLiveStreamAnimationHandle Handle(It.Key()); - if (!Handle.IsValid()) - { - UE_LOG(LogLiveStreamAnimation, Warning, - TEXT("ULiveStreamAnimationLiveLinkFrameTranslator::FetchWorker: %s is not a registered LiveStreamAnimationHandle! Skipping translation profile. Class=%s"), - *It.Key().Handle.ToString(), *GetClass()->GetName()); - - continue; - } - - FLiveStreamAnimationLiveLinkTranslationProfile LocalTranslationProfile = It.Value(); - if (!LocalTranslationProfile.UpdateTransformMappings()) - { - UE_LOG(LogLiveStreamAnimation, Warning, - TEXT("ULiveStreamAnimationLiveLinkFrameTranslator::FetchWorker: %s failed to update bone mappings for Skeleton %s! Skipping translation profile. Class=%s"), - *It.Key().Handle.ToString(), *LocalTranslationProfile.Skeleton.ToString(), *GetClass()->GetName()); - - continue; - } - - LocalTranslationProfiles.Emplace(Handle, MoveTemp(LocalTranslationProfile)); - } - - Worker = MakeShared(MoveTemp(LocalTranslationProfiles)); - } - - return Worker; -} - -#if WITH_EDITOR -void ULiveStreamAnimationLiveLinkFrameTranslator::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) -{ - if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(ULiveStreamAnimationLiveLinkFrameTranslator, TranslationProfiles)) - { - Worker.Reset(); - } - - Super::PostEditChangeProperty(PropertyChangedEvent); -} - -void ULiveStreamAnimationLiveLinkFrameTranslator::PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent) -{ - if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(ULiveStreamAnimationLiveLinkFrameTranslator, TranslationProfiles)) - { - Worker.Reset(); - } - - Super::PostEditChangeChainProperty(PropertyChangedEvent); -} -#endif \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkSource.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkSource.cpp deleted file mode 100644 index 88618c018fcd..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkSource.cpp +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserverd. - -#include "LiveStreamAnimationLiveLinkSource.h" -#include "LiveStreamAnimationLog.h" -#include "LiveLink/LiveLinkPacket.h" -#include "LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.h" -#include "LiveLink/LiveStreamAnimationLiveLinkRole.h" - -#include "Roles/LiveLinkAnimationRole.h" -#include "Roles/LiveLinkAnimationTypes.h" -#include "LiveLinkPresetTypes.h" -#include "ILiveLinkClient.h" -#include "LiveLinkSubjectSettings.h" - -namespace LiveStreamAnimation -{ - FLiveStreamAnimationLiveLinkSource::FLiveStreamAnimationLiveLinkSource(ULiveStreamAnimationLiveLinkFrameTranslator* InTranslator) - : FrameTranslator(InTranslator) - { - } - - void FLiveStreamAnimationLiveLinkSource::ReceiveClient(ILiveLinkClient* InClient, FGuid InSourceGuid) - { - LiveLinkClient = InClient; - SourceGuid = InSourceGuid; - } - - void FLiveStreamAnimationLiveLinkSource::Update() - { - } - - bool FLiveStreamAnimationLiveLinkSource::CanBeDisplayedInUI() const - { - return false; - } - - bool FLiveStreamAnimationLiveLinkSource::IsSourceStillValid() const - { - // TODO: Maybe allow a way for users to test and see if we still are still connected to our server somehow? - return true; - } - - bool FLiveStreamAnimationLiveLinkSource::RequestSourceShutdown() - { - Reset(); - return true; - } - - FText FLiveStreamAnimationLiveLinkSource::GetSourceType() const - { - return NSLOCTEXT("LiveStreamAnimation", "LiveLinkSourceType", "Live Stream Animation Source"); - } - - FText FLiveStreamAnimationLiveLinkSource::GetSourceMachineName() const - { - // TODO: Maybe allow a user provided name somehow? - return NSLOCTEXT("LiveStreamAnimation", "LiveLinkSourceMachineNameNetworked", "Live Stream Animation Network"); - } - - FText FLiveStreamAnimationLiveLinkSource::GetSourceStatus() const - { - static FText ConnectedText = NSLOCTEXT("LiveStreamAnimation", "LiveLinkSourceState_Connected", "Connected"); - static FText DisconnectedText = NSLOCTEXT("LiveStreamAnimation", "LiveLinkSourceState_Disconnected", "Disconnected"); - - return bIsConnectedToMesh ? ConnectedText : DisconnectedText; - } - - void FLiveStreamAnimationLiveLinkSource::Reset() - { - SourceGuid = FGuid(); - LiveLinkClient = nullptr; - } - - bool FLiveStreamAnimationLiveLinkSource::HandlePacket(FLiveLinkPacket&& InPacket) - { - if (LiveLinkClient) - { - switch (InPacket.GetPacketType()) - { - case ELiveLinkPacketType::AddOrUpdateSubject: - return HandleAddOrUpdateSubjectPacket(static_cast(MoveTemp(InPacket))); - - case ELiveLinkPacketType::RemoveSubject: - return HandleRemoveSubjectPacket(static_cast(MoveTemp(InPacket))); - - case ELiveLinkPacketType::AnimationFrame: - return HandleAnimationFramePacket(static_cast(MoveTemp(InPacket))); - - default: - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveStreamAnimationLiveLinkSource::HandlePacket: Invalid packet type %d"), static_cast(InPacket.GetPacketType())); - return false; - } - } - - return false; - } - - bool FLiveStreamAnimationLiveLinkSource::HandleAddOrUpdateSubjectPacket(FLiveLinkAddOrUpdateSubjectPacket&& InPacket) - { - const FLiveStreamAnimationHandle Handle = InPacket.GetSubjectHandle(); - - // If we already mapped this subject, don't do anything but warn. - - // TODO: We might want to make this a remap / changing of skeleton data, but for - // now we'll just assume nothing's changed. - // We should also probably listen for removal events from LiveLink directly. - - if (const FLiveLinkSubjectKey* FoundKey = MappedSubjects.Find(Handle)) - { - UE_LOG(LogLiveStreamAnimation, Warning, - TEXT("FLiveStreamAnimationLiveLinkSource::HandleAddOrUpdateSubjectPacket: Found existing subject. Handle=%s, FoundSubject=%s"), - *Handle.ToString(), *FoundKey->SubjectName.ToString()); - - return true; - } - - FLiveLinkSubjectKey NewKey; - NewKey.Source = SourceGuid; - NewKey.SubjectName = Handle.GetName(); - - FLiveLinkSubjectPreset Presets; - Presets.Key = NewKey; - Presets.Role = ULiveStreamAnimationLiveLinkRole::StaticClass(); - Presets.bEnabled = true; - - if (ULiveStreamAnimationLiveLinkFrameTranslator* LocalFrameTranslator = FrameTranslator.Get()) - { - Presets.Settings = NewObject(); - Presets.Settings->Translators.Add(LocalFrameTranslator); - } - - if (!LiveLinkClient->CreateSubject(Presets)) - { - UE_LOG(LogLiveStreamAnimation, Warning, - TEXT("FLiveStreamAnimationLiveLinkSource::HandleAddOrUpdateSubjectPacket: Failed to create subject. Handle=%s"), - *Handle.ToString()); - return false; - } - - UE_LOG(LogLiveStreamAnimation, Log, - TEXT("FLiveStreamAnimationLiveLinkSource::HandleRemoveSubjectPacket: Added subject to find subject. Handle=%s"), - *Handle.ToString()); - - FLiveLinkStaticDataStruct DataStruct; - DataStruct.InitializeWith(&InPacket.GetStaticData()); - LiveLinkClient->PushSubjectStaticData_AnyThread(NewKey, Presets.Role, MoveTemp(DataStruct)); - MappedSubjects.Emplace(Handle, NewKey); - return true; - } - - bool FLiveStreamAnimationLiveLinkSource::HandleRemoveSubjectPacket(FLiveLinkRemoveSubjectPacket&& InPacket) - { - const FLiveStreamAnimationHandle Handle = InPacket.GetSubjectHandle(); - FLiveLinkSubjectKey FoundKey; - - if (!MappedSubjects.RemoveAndCopyValue(Handle, FoundKey)) - { - UE_LOG(LogLiveStreamAnimation, Warning, - TEXT("FLiveStreamAnimationLiveLinkSource::HandleRemoveSubjectPacket: Failed to find subject. Handle=%s"), - *Handle.ToString()); - return true; - } - - UE_LOG(LogLiveStreamAnimation, Log, - TEXT("FLiveStreamAnimationLiveLinkSource::HandleRemoveSubjectPacket: Failed to find subject. Handle=%s, Subject=%s"), - *Handle.ToString(), *FoundKey.SubjectName.ToString()); - - LiveLinkClient->RemoveSubject_AnyThread(FoundKey); - return true; - } - - bool FLiveStreamAnimationLiveLinkSource::HandleAnimationFramePacket(FLiveLinkAnimationFramePacket&& InPacket) - { - const FLiveStreamAnimationHandle Handle = InPacket.GetSubjectHandle(); - if (const FLiveLinkSubjectKey* FoundKey = MappedSubjects.Find(Handle)) - { - UE_LOG(LogLiveStreamAnimation, Verbose, - TEXT("FLiveStreamAnimationLiveLinkSource::HandleAnimationFramePacket: Added animation frame. Handle=%s, Subject=%s"), - *Handle.ToString(), *FoundKey->SubjectName.ToString()); - - FLiveLinkFrameDataStruct Data; - Data.InitializeWith(&InPacket.GetFrameData()); - LiveLinkClient->PushSubjectFrameData_AnyThread(*FoundKey, MoveTemp(Data)); - return true; - } - - UE_LOG(LogLiveStreamAnimation, Verbose, - TEXT("FLiveStreamAnimationLiveLinkSource::HandleAnimationFramePacket: Failed to find subject. Handle=%s"), - *Handle.ToString()); - - return false; - } - - void FLiveStreamAnimationLiveLinkSource::SetFrameTranslator(ULiveStreamAnimationLiveLinkFrameTranslator* NewFrameTranslator) - { - FrameTranslator = NewFrameTranslator; - - // TODO: Update the individual Subjects. - // Updating the subjects should be a *very* rare occurrence though, as most - // of the time the translator will be set up in Configs or in Blueprints before - // we've received any data from the network. - } -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkSource.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkSource.h deleted file mode 100644 index cbaa214b2158..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/LiveStreamAnimationLiveLinkSource.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserverd. - -#pragma once - -#include "CoreMinimal.h" -#include "LiveStreamAnimationFwd.h" -#include "ILiveLinkSource.h" -#include "LiveLinkTypes.h" -#include "LiveStreamAnimationHandle.h" - -class ULiveStreamAnimationLiveLinkFrameTranslator; - -namespace LiveStreamAnimation -{ - class FLiveStreamAnimationLiveLinkSource : public ILiveLinkSource - { - public: - - FLiveStreamAnimationLiveLinkSource(ULiveStreamAnimationLiveLinkFrameTranslator* InFrameTranslator); - - //~ Begin ILiveLinkSource Interface - virtual void ReceiveClient(ILiveLinkClient* InClient, FGuid InSourceGuid) override; - virtual void Update() override; - virtual bool CanBeDisplayedInUI() const override; - virtual bool IsSourceStillValid() const override; - virtual bool RequestSourceShutdown() override; - virtual FText GetSourceType() const override; - virtual FText GetSourceMachineName() const override; - virtual FText GetSourceStatus() const override; - //~ End ILiveLinkSource Interface - - bool HandlePacket(class FLiveLinkPacket&& InPacket); - void SetFrameTranslator(ULiveStreamAnimationLiveLinkFrameTranslator* NewFrameTranslator); - - private: - - void Reset(); - - bool HandleAddOrUpdateSubjectPacket(class FLiveLinkAddOrUpdateSubjectPacket&& InPacket); - bool HandleRemoveSubjectPacket(class FLiveLinkRemoveSubjectPacket&& InPacket); - bool HandleAnimationFramePacket(class FLiveLinkAnimationFramePacket&& InPacket); - - ILiveLinkClient* LiveLinkClient; - FGuid SourceGuid; - bool bIsConnectedToMesh; - - TMap MappedSubjects; - TWeakObjectPtr FrameTranslator; - }; -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/Test/SkelMeshToLiveLinkSource.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/Test/SkelMeshToLiveLinkSource.cpp deleted file mode 100644 index 7629d1811159..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveLink/Test/SkelMeshToLiveLinkSource.cpp +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserverd. - -#include "LiveLink/Test/SkelMeshToLiveLinkSource.h" -#include "LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.h" -#include "LiveStreamAnimationSubsystem.h" -#include "LiveStreamAnimationSettings.h" - -#include "Roles/LiveLinkAnimationRole.h" -#include "Roles/LiveLinkAnimationTypes.h" -#include "ILiveLinkClient.h" -#include "LiveLinkPresetTypes.h" -#include "Components/SkeletalMeshComponent.h" -#include "ReferenceSkeleton.h" -#include "Engine/SkeletalMesh.h" - -ULiveLinkTestSkelMeshTrackerComponent::ULiveLinkTestSkelMeshTrackerComponent() -{ - PrimaryComponentTick.bCanEverTick = true; -} - -void ULiveLinkTestSkelMeshTrackerComponent::StartTrackingSkelMesh(FName InLiveLinkSubjectName) -{ - using namespace LiveStreamAnimation; - - StopTrackingSkelMesh(); - - TSharedPtr PinnedSource = Source.Pin(); - if (!PinnedSource.IsValid()) - { - UWorld* World = GetWorld(); - - if (ULiveStreamAnimationSubsystem* Subsystem = UGameInstance::GetSubsystem(World ? World->GetGameInstance() : nullptr)) - { - Source = Subsystem->GetOrCreateSkelMeshToLiveLinkSource(); - PinnedSource = Source.Pin(); - } - - if (!PinnedSource.IsValid()) - { - return; - } - } - - if (InLiveLinkSubjectName != NAME_None) - { - if (USkeletalMeshComponent* LocalSkelMeshComp = GetSkelMeshComp()) - { - if (const FReferenceSkeleton* RefSkel = LocalSkelMeshComp->SkeletalMesh ? &LocalSkelMeshComp->SkeletalMesh->RefSkeleton : nullptr) - { - if (ILiveLinkClient* LiveLinkClient = PinnedSource->GetLiveLinkClient()) - { - FLiveLinkSubjectPreset SubjectPresets; - SubjectPresets.Key = FLiveLinkSubjectKey(PinnedSource->GetGuid(), InLiveLinkSubjectName); - SubjectPresets.Role = ULiveLinkAnimationRole::StaticClass(); - SubjectPresets.bEnabled = true; - - const bool bCreatedSubjectSuccessfully = LiveLinkClient->CreateSubject(SubjectPresets); - if (!bCreatedSubjectSuccessfully) - { - return; - } - - SubjectName = InLiveLinkSubjectName; - - TArray> BoneAndParentIndices; - const TArray& RawRefBoneInfo = RefSkel->GetRefBoneInfo(); - if (BonesToTrack.Num() > 0) - { - TSet TempBonesToUse; - - for (const FBoneReference& BoneReference : BonesToTrack) - { - const int32 BoneIndex = RefSkel->FindRawBoneIndex(BoneReference.BoneName); - if (INDEX_NONE != BoneIndex) - { - TempBonesToUse.Add(BoneIndex); - } - } - - if (TempBonesToUse.Num() > 0) - { - // Forcibly add our root bone so we don't get into a weird state. - // TODO: We should probably check to see if this was already added. - for (int32 i = 0; i < RawRefBoneInfo.Num(); ++i) - { - if (INDEX_NONE == RawRefBoneInfo[i].ParentIndex) - { - TempBonesToUse.Add(i); - break; - } - } - - // Go ahead and generate our simple skelton. - // This is done by scanning backwards and associating the bones with their parents. - // If we aren't tracking a parent, we will use that parent's parent (recursively until we hit the root). - for (int32 Bone : TempBonesToUse) - { - int32 Parent = Bone; - - do - { - Parent = RawRefBoneInfo[Parent].ParentIndex; - } while (INDEX_NONE != Parent && !TempBonesToUse.Contains(Parent)); - - BoneAndParentIndices.Add(TTuple(Bone, Parent)); - } - } - } - - if (BoneAndParentIndices.Num() == 0) - { - for (int32 i = 0; i < RawRefBoneInfo.Num(); ++i) - { - BoneAndParentIndices.Add(TTuple(i, RawRefBoneInfo[i].ParentIndex)); - } - } - - FLiveLinkSkeletonStaticData SkeletonData; - SkeletonData.BoneNames.Reserve(BoneAndParentIndices.Num()); - SkeletonData.BoneParents.Reserve(BoneAndParentIndices.Num()); - UsingBones.Empty(BoneAndParentIndices.Num()); - - for (const TTuple& BoneAndParent : BoneAndParentIndices) - { - SkeletonData.BoneNames.Add(RawRefBoneInfo[BoneAndParent.Get<0>()].Name); - SkeletonData.BoneParents.Add(BoneAndParent.Get<1>()); - UsingBones.Add(BoneAndParent.Get<0>()); - } - - UsingSkelMeshComp = LocalSkelMeshComp; - - FLiveLinkStaticDataStruct StaticData; - StaticData.InitializeWith(&SkeletonData); - - LiveLinkClient->PushSubjectStaticData_AnyThread(GetSubjectKey(), ULiveLinkAnimationRole::StaticClass(), MoveTemp(StaticData)); - PrimaryComponentTick.AddPrerequisite(LocalSkelMeshComp, LocalSkelMeshComp->PrimaryComponentTick); - } - } - } - } -} - -void ULiveLinkTestSkelMeshTrackerComponent::StopTrackingSkelMesh() -{ - using namespace LiveStreamAnimation; - - if (USkeletalMeshComponent* LocalSkelMeshComp = UsingSkelMeshComp.Get()) - { - PrimaryComponentTick.RemovePrerequisite(LocalSkelMeshComp, LocalSkelMeshComp->PrimaryComponentTick); - } - - TSharedPtr PinnedSource = Source.Pin(); - if (PinnedSource.IsValid()) - { - if (ILiveLinkClient * LiveLinkClient = PinnedSource->GetLiveLinkClient()) - { - LiveLinkClient->RemoveSubject_AnyThread(GetSubjectKey()); - } - } - - UsingSkelMeshComp = nullptr; - UsingBones.Empty(); -} - -void ULiveLinkTestSkelMeshTrackerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) -{ - using namespace LiveStreamAnimation; - - Super::TickComponent(DeltaTime, TickType, ThisTickFunction); - if (USkeletalMeshComponent* LocalSkelMeshComp = UsingSkelMeshComp.Get()) - { - TSharedPtr PinnedSource = Source.Pin(); - if (ILiveLinkClient * LiveLinkClient = PinnedSource->GetLiveLinkClient()) - { - FLiveLinkAnimationFrameData Frames; - - TArray Transforms = LocalSkelMeshComp->GetBoneSpaceTransforms(); - if (UsingBones.Num() == Transforms.Num()) - { - Frames.Transforms = MoveTemp(Transforms); - } - else - { - Frames.Transforms.Reserve(UsingBones.Num()); - for (const int32 BoneIndex : UsingBones) - { - Frames.Transforms.Add(Transforms[BoneIndex]); - } - } - - FLiveLinkFrameDataStruct FrameData; - FrameData.InitializeWith(&Frames); - - LiveLinkClient->PushSubjectFrameData_AnyThread(GetSubjectKey(), MoveTemp(FrameData)); - } - } -} - -void ULiveLinkTestSkelMeshTrackerComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) -{ - StopTrackingSkelMesh(); - - Super::EndPlay(EndPlayReason); -} - -class USkeleton* ULiveLinkTestSkelMeshTrackerComponent::GetSkeleton(bool& bInvalidSkeletonIsError) -{ - bInvalidSkeletonIsError = false; - - auto GetSkelFromSkelMeshComp = [](USkeletalMeshComponent* Comp) - { - return (Comp && Comp->SkeletalMesh) ? Comp->SkeletalMesh->Skeleton : nullptr; - }; - - USkeleton* Skeleton = GetSkelFromSkelMeshComp(GetSkelMeshComp()); - - // If this happens, it's likely because we're in a Blueprint. - if (Skeleton == nullptr) - { - if (const ULiveStreamAnimationLiveLinkFrameTranslator* LocalTranslator = ULiveStreamAnimationSettings::GetFrameTranslator()) - { - if (const FLiveStreamAnimationLiveLinkTranslationProfile* Profile = LocalTranslator->GetTranslationProfile(TranslationProfile)) - { - Skeleton = Profile->Skeleton.LoadSynchronous(); - } - } - } - - if (Skeleton == nullptr) - { - if (UClass* Class = Cast(GetOuter())) - { - if (SkelMeshComp.ComponentProperty != NAME_None) - { - if (FObjectPropertyBase* ObjProp = FindFProperty(Class, SkelMeshComp.ComponentProperty)) - { - if (UObject* CDO = Class->GetDefaultObject()) - { - Skeleton = GetSkelFromSkelMeshComp(Cast(ObjProp->GetObjectPropertyValue_InContainer(CDO))); - } - } - } - } - } - - return Skeleton; -} - -ILiveLinkClient* ULiveLinkTestSkelMeshTrackerComponent::GetLiveLinkClient() const -{ - using namespace LiveStreamAnimation; - - TSharedPtr PinnedSource = Source.Pin(); - return PinnedSource.IsValid() ? PinnedSource->GetLiveLinkClient() : nullptr; -} - -FLiveLinkSubjectKey ULiveLinkTestSkelMeshTrackerComponent::GetSubjectKey() const -{ - using namespace LiveStreamAnimation; - - TSharedPtr PinnedSource = Source.Pin(); - return FLiveLinkSubjectKey(PinnedSource->GetGuid(), SubjectName); -} - -USkeletalMeshComponent* ULiveLinkTestSkelMeshTrackerComponent::GetSkelMeshComp() const -{ - if (USkeletalMeshComponent* LocalSkelMeshComp = WeakSkelMeshComp.Get()) - { - return LocalSkelMeshComp; - } - - return Cast(SkelMeshComp.GetComponent(GetOwner())); -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationChannel.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationChannel.cpp deleted file mode 100644 index cb05e125b9c6..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationChannel.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "LiveStreamAnimationChannel.h" -#include "LiveStreamAnimationSubsystem.h" -#include "LiveStreamAnimationPacket.h" - -#include "ForwardingChannel.h" -#include "ForwardingChannelsSubsystem.h" -#include "ForwardingChannelsUtils.h" - -#include "Net/DataBunch.h" -#include "Engine/NetConnection.h" - - -ULiveStreamAnimationChannel::ULiveStreamAnimationChannel() -{ - ChName = ULiveStreamAnimationSubsystem::GetChannelName(); -} - -void ULiveStreamAnimationChannel::Init(UNetConnection* InConnection, int32 InChIndex, EChannelCreateFlags CreateFlags) -{ - using namespace ForwardingChannels; - - Super::Init(InConnection, InChIndex, CreateFlags); - ForwardingChannel = CreateDefaultForwardingChannel(*this, FCreateChannelParams(ChName)); -} - -void ULiveStreamAnimationChannel::ReceivedBunch(FInBunch& Bunch) -{ - using namespace LiveStreamAnimation; - if (ForwardingChannel.IsValid() && Connection && Connection->Driver && Connection->Driver->World) - { - if (ULiveStreamAnimationSubsystem* LiveStreamAnimationSubsystem = UGameInstance::GetSubsystem(Connection->Driver->World->GetGameInstance())) - { - while (!Bunch.AtEnd()) - { - TSharedPtr NewPacket = FLiveStreamAnimationPacket::ReadFromStream(Bunch); - if (NewPacket.IsValid()) - { - NewPacket->SetReliable(Bunch.bReliable); - LiveStreamAnimationSubsystem->ReceivedPacket(NewPacket.ToSharedRef(), *ForwardingChannel); - } - else - { - // Unable to serialize the data because the serializer doesn't exist or there was a problem with this packet - Bunch.SetError(); - break; - } - } - } - } -} - -void ULiveStreamAnimationChannel::Tick() -{ - using namespace ForwardingChannels; - using namespace LiveStreamAnimation; - - if (ForwardingChannel.IsValid()) - { - DefaultFlushPacketsForChannel( - /*Channel=*/*this, - /*ForwardingChannel=*/*ForwardingChannel, - /*SendFlags=*/EDefaultSendPacketFlags::AllowMerging | EDefaultSendPacketFlags::IgnoreSaturation, - /*IsPacketReliable=*/[](const FLiveStreamAnimationPacket& Packet) - { - return Packet.IsReliable(); - }, - /*WritePacket=*/&FLiveStreamAnimationPacket::WriteToStream - ); - } -} - -bool ULiveStreamAnimationChannel::CleanUp(const bool bForDestroy, EChannelCloseReason CloseReason) -{ - ForwardingChannel.Reset(); - return Super::CleanUp(bForDestroy, CloseReason); -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationHandle.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationHandle.cpp deleted file mode 100644 index c7f8b6bea7df..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationHandle.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "LiveStreamAnimationHandle.h" -#include "LiveStreamAnimationLog.h" -#include "LiveStreamAnimationSettings.h" - -#include "Serialization/Archive.h" - -class FArchive& operator<<(class FArchive& InAr, FLiveStreamAnimationHandle& SubjectHandle) -{ - if (InAr.IsSaving() && !SubjectHandle.IsValid()) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("Failed to serialize FLiveStreamAnimationHandle. (Invalid handle while saving).")); - InAr.SetError(); - } - - uint32 Handle = static_cast(SubjectHandle.Handle); - InAr.SerializeIntPacked(Handle); - SubjectHandle.Handle = static_cast(Handle); - - if (InAr.IsLoading() && !SubjectHandle.IsValid()) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("Failed to serialize FLiveStreamAnimationHandle. (Invalid handle while loading).")); - InAr.SetError(); - } - - return InAr; -} - -FName FLiveStreamAnimationHandle::GetName() const -{ - const TArrayView HandleNames = ULiveStreamAnimationSettings::GetHandleNames(); - return HandleNames.IsValidIndex(Handle) ? HandleNames[Handle] : NAME_None; -} - -int32 FLiveStreamAnimationHandle::ValidateHandle(FName InName) -{ - return ULiveStreamAnimationSettings::GetHandleNames().Find(InName); -} - -int32 FLiveStreamAnimationHandle::ValidateHandle(int32 InHandle) -{ - const TArrayView HandleNames = ULiveStreamAnimationSettings::GetHandleNames(); - return HandleNames.IsValidIndex(InHandle) ? InHandle : INDEX_NONE; -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationLog.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationLog.cpp deleted file mode 100644 index 9f8c73dbf4cb..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationLog.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "LiveStreamAnimationLog.h" - -DEFINE_LOG_CATEGORY(LogLiveStreamAnimation); \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationModule.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationModule.cpp deleted file mode 100644 index e10c88dd997a..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationModule.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "LiveStreamAnimationModule.h" - -IMPLEMENT_MODULE(FLiveStreamAnimationModule, LiveStreamAnimation) - -void FLiveStreamAnimationModule::StartupModule() -{ -} - -void FLiveStreamAnimationModule::ShutdownModule() -{ -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationModule.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationModule.h deleted file mode 100644 index 4a0d2955b07f..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationModule.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "Modules/ModuleManager.h" -#include "Modules/ModuleInterface.h" -#include "Net/UnrealNetwork.h" -#include "Tickable.h" - -class IOnlineSubsystemUtils; - -class FLiveStreamAnimationModule : public IModuleInterface -{ -public: - - FLiveStreamAnimationModule() = default; - virtual ~FLiveStreamAnimationModule() = default; - - // IModuleInterface - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - static inline bool IsAvailable() - { - return FModuleManager::Get().IsModuleLoaded(GetModuleName()); - } - -protected: - - static FName GetModuleName() - { - static FName ModuleName = FName(TEXT("LiveStreamAnimation")); - return ModuleName; - } -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationPacket.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationPacket.cpp deleted file mode 100644 index 53f9bfa3cfe5..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationPacket.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "LiveStreamAnimationPacket.h" -#include "LiveStreamAnimationLog.h" - - -namespace LiveStreamAnimation -{ - void FLiveStreamAnimationPacket::WriteToStream(FArchive& InWriter, const FLiveStreamAnimationPacket& InPacket) - { - uint8 LocalPacketType = static_cast(InPacket.PacketType); - uint32 DataSize = InPacket.PacketData.Num(); - InWriter << LocalPacketType; - InWriter.SerializeIntPacked(DataSize); - InWriter.Serialize(const_cast(InPacket.PacketData.GetData()), InPacket.PacketData.Num()); - } - - TSharedPtr FLiveStreamAnimationPacket::ReadFromStream(class FArchive& InReader) - { - uint8 LocalPacketType = 0; - uint32 DataSize = 0; - InReader << LocalPacketType; - InReader.SerializeIntPacked(DataSize); - - if (static_cast(ELiveStreamAnimationPacketType::INVALID) <= LocalPacketType) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveStreamAnimationPacket::ReadFromStream: Invalid packet type %d"), LocalPacketType); - return nullptr; - } - - const int32 SignedDataSize = static_cast(DataSize); - if (SignedDataSize < 0) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveStreamAnimationPacket::ReadFromStream: Invalid data size %d"), SignedDataSize); - return nullptr; - } - - // We could actually pass this to the appropriate packet type to do validation. - // For now, we'll just assume the data is correct and pass it through (letting - // terminal connections handle anything that's been malformed). - - TArray Data; - Data.SetNumUninitialized(SignedDataSize); - InReader.Serialize(Data.GetData(), SignedDataSize); - - if (InReader.IsError()) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("FLiveStreamAnimationPacket::ReadFromStream: Failed to serialize data")); - return nullptr; - } - - return MakeShareable(new FLiveStreamAnimationPacket(static_cast(LocalPacketType), MoveTemp(Data))); - } -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationPacket.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationPacket.h deleted file mode 100644 index cc4d3cefdd94..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationPacket.h +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "LiveStreamAnimationFwd.h" -#include "ForwardingPacket.h" -#include "Templates/SharedPointer.h" -#include "Serialization/Archive.h" -#include "Serialization/MemoryWriter.h" - -namespace LiveStreamAnimation -{ - // It might be worth making this more flexible / extensible through a registration system. - // For now though, it's trivial and less error prone to add new packet types manually. - - /** Packet Subtypes. */ - enum class ELiveStreamAnimationPacketType : uint8 - { - Control, //! Generic control packet. - LiveLink, //! We're holding FLiveLinkPacket data. - INVALID - }; - - /** - * Generic forwarding packet that's used by Live Stream Animation. - * It can hold arbitrary data used by various packet subtypes. - */ - class FLiveStreamAnimationPacket : public ForwardingChannels::FForwardingPacket - { - public: - - virtual ~FLiveStreamAnimationPacket() {} - - ELiveStreamAnimationPacketType GetPacketType() const - { - return PacketType; - } - - bool IsReliable() const - { - return bReliable; - } - - void SetReliable(bool bInReliable) - { - bReliable = bInReliable; - } - - TArrayView GetPacketData() const - { - return PacketData; - } - - static void WriteToStream(class FArchive& InWriter, const FLiveStreamAnimationPacket& InPacket); - - static TSharedPtr ReadFromStream(class FArchive& InReader); - - template - static TSharedPtr CreateFromPacket(const TPacketClass& Packet) - { - TArray Data; - Data.Reserve(40); - - FMemoryWriter MemoryWriter(Data); - TPacketClass::WriteToStream(MemoryWriter, Packet); - - Data.Shrink(); - - static constexpr ELiveStreamAnimationPacketType PacketType = TPacketClass::GetAnimationPacketType(); - static_assert(PacketType != ELiveStreamAnimationPacketType::INVALID, "Class must provide a valid packet type"); - - return MakeShareable(new FLiveStreamAnimationPacket( - PacketType, - MoveTemp(Data) - )); - } - - private: - - FLiveStreamAnimationPacket(const ELiveStreamAnimationPacketType InPacketType, TArray&& InPacketData) - : PacketType(InPacketType) - , PacketData(MoveTemp(InPacketData)) - { - } - - const ELiveStreamAnimationPacketType PacketType; - TArray PacketData; - bool bReliable = false; - }; -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationSettings.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationSettings.cpp deleted file mode 100644 index d626a6276f47..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationSettings.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "LiveStreamAnimationSettings.h" -#include "LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.h" - -ULiveStreamAnimationSettings::ULiveStreamAnimationSettings() -{ - if (HasAnyFlags(RF_ClassDefaultObject)) - { - // These are just default names, but any can be added / removed - // using GameConfig or through Edit > Plugin Settings > Live Stream Animation - // This list **must** be consistent between all instances of the project, so do - // not attempt to customize the list for Servers or Clients. - HandleNames.Append({ - FName(TEXT("LiveLinkSubject1")), - FName(TEXT("LiveLinkSubject2")), - FName(TEXT("LiveLinkSubject3")), - FName(TEXT("LiveLinkSubject4")), - FName(TEXT("LiveLinkSubject5")), - FName(TEXT("LiveStreamAnimationHandle1")), - FName(TEXT("LiveStreamAnimationHandle2")), - FName(TEXT("LiveStreamAnimationHandle3")), - FName(TEXT("LiveStreamAnimationHandle4")), - FName(TEXT("LiveStreamAnimationHandle5")), - FName(TEXT("LiveLinkFrameTranslation1")), - FName(TEXT("LiveLinkFrameTranslation2")), - FName(TEXT("LiveLinkFrameTranslation3")), - FName(TEXT("LiveLinkFrameTranslation4")), - FName(TEXT("LiveLinkFrameTranslation5")), - }); - } -} - - -class ULiveStreamAnimationLiveLinkFrameTranslator* ULiveStreamAnimationSettings::GetFrameTranslator() -{ - return GetMutableDefault()->FrameTranslator.LoadSynchronous(); -} - -FDelegateHandle ULiveStreamAnimationSettings::AddFrameTranslatorChangedCallback(FSimpleMulticastDelegate::FDelegate&& InDelegate) -{ - return GetMutableDefault()->OnFrameTranslatorChanged.Add(MoveTemp(InDelegate)); -} - -FDelegateHandle ULiveStreamAnimationSettings::AddFrameTranslatorChangedCallback(const FSimpleMulticastDelegate::FDelegate& InDelegate) -{ - return GetMutableDefault()->OnFrameTranslatorChanged.Add(InDelegate); -} - -void ULiveStreamAnimationSettings::RemoveFrameTranslatorChangedCallback(FDelegateHandle DelegateHandle) -{ - GetMutableDefault()->OnFrameTranslatorChanged.Remove(DelegateHandle); -} - -const TArrayView ULiveStreamAnimationSettings::GetHandleNames() -{ - return GetDefault()->HandleNames; -} - -#if WITH_EDITOR -void ULiveStreamAnimationSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) -{ - if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(ULiveStreamAnimationSettings, FrameTranslator)) - { - OnFrameTranslatorChanged.Broadcast(); - } - - Super::PostEditChangeProperty(PropertyChangedEvent); -} - -void ULiveStreamAnimationSettings::PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent) -{ - if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(ULiveStreamAnimationSettings, FrameTranslator)) - { - OnFrameTranslatorChanged.Broadcast(); - } - - Super::PostEditChangeChainProperty(PropertyChangedEvent); -} -#endif - -FName ULiveStreamAnimationSettings::GetContainerName() const -{ - return Super::GetContainerName(); -} - -FName ULiveStreamAnimationSettings::GetCategoryName() const -{ - static const FName PluginCategory(TEXT("Plugins")); - - return PluginCategory; -} - -FName ULiveStreamAnimationSettings::GetSectionName() const -{ - return Super::GetSectionName(); -} - -#if WITH_EDITOR -FText ULiveStreamAnimationSettings::GetSectionText() const -{ - return Super::GetSectionText(); -} - -FText ULiveStreamAnimationSettings::GetSectionDescription() const -{ - return Super::GetSectionDescription(); -} -#endif \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationSubsystem.cpp b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationSubsystem.cpp deleted file mode 100644 index 8c026d803b24..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Private/LiveStreamAnimationSubsystem.cpp +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserverd. - -#include "LiveStreamAnimationSubsystem.h" -#include "LiveStreamAnimationLog.h" -#include "ControlPacket.h" -#include "LiveLink/LiveLinkPacket.h" -#include "LiveLink/LiveLinkStreamingHelper.h" -#include "LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.h" -#include "Engine/NetConnection.h" -#include "EngineLogs.h" -#include "LiveStreamAnimationChannel.h" -#include "ForwardingChannel.h" -#include "ForwardingGroup.h" -#include "ForwardingChannelsUtils.h" - -#include "ForwardingChannelsSubsystem.h" - -ULiveStreamAnimationSubsystem::ULiveStreamAnimationSubsystem() -{ -} - -void ULiveStreamAnimationSubsystem::Initialize(FSubsystemCollectionBase& Collection) -{ - using namespace LiveStreamAnimation; - - bInitialized = true; - bShouldAcceptClientPackets = false; - - Collection.InitializeDependency(UForwardingChannelsSubsystem::StaticClass()); - - if (UForwardingChannelsSubsystem* ForwardingChannelsSubsystem = GetSubsystem()) - { - ForwardingChannelsSubsystem->RegisterForwardingChannelFactory(this); - ForwardingGroup = ForwardingChannelsSubsystem->GetOrCreateForwardingGroup(GetChannelName()); - } - - LiveLinkStreamingHelper = MakeShared(*this); -} - -void ULiveStreamAnimationSubsystem::Deinitialize() -{ - if (UForwardingChannelsSubsystem* ForwardingChannelsSubsystem = GetSubsystem()) - { - ForwardingChannelsSubsystem->UnregisterForwardingChannelFactory(this); - } - - bInitialized = false; - ForwardingGroup.Reset(); - LiveLinkStreamingHelper.Reset(); -} - -FName ULiveStreamAnimationSubsystem::GetChannelName() -{ - static const FName ChannelName(TEXT("LiveStreamAnimation")); - return ChannelName; -} - -void ULiveStreamAnimationSubsystem::SetRole(const ELiveStreamAnimationRole NewRole) -{ - if (!IsEnabledAndInitialized()) - { - return; - } - - if (Role != NewRole) - { - Role = NewRole; - OnRoleChanged.Broadcast(Role); - } -} - -void ULiveStreamAnimationSubsystem::CreateForwardingChannel(UNetConnection* InNetConnection) -{ - using namespace LiveStreamAnimation; - using namespace ForwardingChannels; - - if (!IsEnabledAndInitialized()) - { - return; - } - - // We only allow direct creation of channels on the Server. - // Clients will open their channels automatically upon seeing the server created channel. - if (InNetConnection && InNetConnection->Driver && InNetConnection->Driver->IsServer()) - { - if (ULiveStreamAnimationChannel* Channel = Cast(InNetConnection->CreateChannelByName(GetChannelName(), EChannelCreateFlags::OpenedLocally))) - { - if (FForwardingChannel* ForwardingChannel = Channel->GetForwardingChannel().Get()) - { - TArray> JoinInProgressPackets; - JoinInProgressPackets.Reserve(10); - - TSharedRef InitialPacket = FLiveStreamAnimationPacket::CreateFromPacket(FControlInitialPacket()).ToSharedRef(); - InitialPacket->SetReliable(true); - - JoinInProgressPackets.Add(InitialPacket); - LiveLinkStreamingHelper->GetJoinInProgressPackets(JoinInProgressPackets); - - ForwardingChannel->QueuePackets(JoinInProgressPackets); - } - } - } -} - -void ULiveStreamAnimationSubsystem::SetAcceptClientPackets(bool bInShouldAcceptClientPackets) -{ - SetAcceptClientPackets_Private(bInShouldAcceptClientPackets); -} - -bool ULiveStreamAnimationSubsystem::StartTrackingLiveLinkSubject( - const FName LiveLinkSubject, - const FLiveStreamAnimationHandleWrapper RegisteredName, - const FLiveStreamAnimationLiveLinkSourceOptions Options, - const FLiveStreamAnimationHandleWrapper TranslationProfile) -{ - return StartTrackingLiveLinkSubject( - LiveLinkSubject, - FLiveStreamAnimationHandle(RegisteredName), - Options, - FLiveStreamAnimationHandle(TranslationProfile) - ); -} - -bool ULiveStreamAnimationSubsystem::StartTrackingLiveLinkSubject( - const FName LiveLinkSubject, - const FLiveStreamAnimationHandle RegisteredName, - const FLiveStreamAnimationLiveLinkSourceOptions Options, - const FLiveStreamAnimationHandle TranslationProfile) -{ - using namespace LiveStreamAnimation; - - if (!IsEnabledAndInitialized()) - { - return false; - } - - if (ELiveStreamAnimationRole::Tracker != Role) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("ULiveStreamAnimationSubsystem::StartTrackingLiveLinkSubject: Invalid role. %d"), - static_cast(Role)); - - return false; - } - - return LiveLinkStreamingHelper->StartTrackingSubject( - LiveLinkSubject, - RegisteredName, - Options, - TranslationProfile); -} - -void ULiveStreamAnimationSubsystem::StopTrackingLiveLinkSubject(const FLiveStreamAnimationHandleWrapper RegisteredName) -{ - StopTrackingLiveLinkSubject(FLiveStreamAnimationHandle(RegisteredName)); -} - -void ULiveStreamAnimationSubsystem::StopTrackingLiveLinkSubject(const FLiveStreamAnimationHandle RegisteredName) -{ - using namespace LiveStreamAnimation; - - if (!IsEnabledAndInitialized()) - { - return; - } - - if (ELiveStreamAnimationRole::Tracker != Role) - { - UE_LOG(LogLiveStreamAnimation, Warning, TEXT("ULiveStreamAnimationSubsystem::StartTrackingLiveLinkSubject: Invalid role. %d"), - static_cast(Role)); - - return; - } - - LiveLinkStreamingHelper->StopTrackingSubject(FLiveStreamAnimationHandle(RegisteredName)); -} - -void ULiveStreamAnimationSubsystem::ReceivedPacket(const TSharedRef& Packet, ForwardingChannels::FForwardingChannel& FromChannel) -{ - using namespace LiveStreamAnimation; - - if (!IsEnabledAndInitialized()) - { - return; - } - - // If we're receiving packets from a Forwarded Channel, our - // Forwarding Group better be valid. - if (!ForwardingGroup.IsValid() || ForwardingGroup.Get() != &FromChannel.GetGroup().Get()) - { - UE_LOG(LogLiveStreamAnimation, Error, TEXT("ULiveStreamAnimationSubsystem::ReceivedPacket: Invalid group!")); - ensure(false); - } - - // If we've received a packet from a client and we shouldn't accept those, then throw it out. - if (!FromChannel.IsServerChannel() && !bShouldAcceptClientPackets) - { - return; - } - - // TODO: This could be made more generic through some registration or - // forwarding scheme. - - // We should only receive packets from the server if we're acting as a Proxy - // or Processor. - if (ELiveStreamAnimationRole::Tracker != Role) - { - switch (Packet->GetPacketType()) - { - case ELiveStreamAnimationPacketType::LiveLink: - LiveLinkStreamingHelper->HandleLiveLinkPacket(Packet); - break; - - default: - break; - } - - ForwardingGroup->ForwardPacket(Packet, CreateDefaultForwardingFilter(FromChannel)); - } -} - -bool ULiveStreamAnimationSubsystem::SendPacketToServer(const TSharedRef& Packet) -{ - if (!IsEnabledAndInitialized()) - { - return false; - } - - if (!ForwardingGroup.IsValid()) - { - UE_LOG(LogLiveStreamAnimation, Error, TEXT("ULiveStreamAnimationSubsystem::SendPacketToServer: Invalid group!")); - ensure(false); - return false; - } - - if (ELiveStreamAnimationRole::Tracker == Role) - { - ForwardingGroup->QueuePacketOnServer(Packet); - return true; - } - - return false; -} - -TWeakPtr ULiveStreamAnimationSubsystem::GetOrCreateSkelMeshToLiveLinkSource() -{ - if (!IsEnabledAndInitialized()) - { - return nullptr; - } - - return LiveLinkStreamingHelper->GetOrCreateSkelMeshToLiveLinkSource(); -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkFrameData.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkFrameData.h deleted file mode 100644 index 31502e12ac99..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkFrameData.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "Roles/LiveLinkAnimationTypes.h" -#include "LiveLink/LiveStreamAnimationLiveLinkSourceOptions.h" -#include "LiveStreamAnimationHandle.h" -#include "LiveStreamAnimationLiveLinkFrameData.generated.h" - -USTRUCT(BlueprintType) -struct LIVESTREAMANIMATION_API FLiveStreamAnimationLiveLinkStaticData : public FLiveLinkSkeletonStaticData -{ - GENERATED_BODY() - -public: - - FLiveStreamAnimationLiveLinkStaticData(); - - FLiveStreamAnimationLiveLinkStaticData(FLiveLinkSkeletonStaticData&& SkeletonData); -}; - -USTRUCT(BlueprintType) -struct LIVESTREAMANIMATION_API FLiveStreamAnimationLiveLinkFrameData : public FLiveLinkAnimationFrameData -{ - GENERATED_BODY() - -public: - - FLiveStreamAnimationLiveLinkFrameData(); - - FLiveStreamAnimationLiveLinkFrameData( - FLiveLinkAnimationFrameData&& AnimFrameData, - const FLiveStreamAnimationLiveLinkSourceOptions& InOptions, - const FLiveStreamAnimationHandle& InTranslationProfileHandle); - - FLiveStreamAnimationLiveLinkSourceOptions Options; - FLiveStreamAnimationHandle TranslationProfileHandle; -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.h deleted file mode 100644 index a05277569803..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkFrameTranslator.h +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "LiveLinkFrameTranslator.h" -#include "LiveStreamAnimationHandle.h" -#include "LiveStreamAnimationLiveLinkFrameTranslator.generated.h" - -class USkeleton; - -/** - * A single translation profile that can map one Live Link Subject Skeleton onto one UE Skeleton. - */ -USTRUCT(BlueprintType) -struct LIVESTREAMANIMATION_API FLiveStreamAnimationLiveLinkTranslationProfile -{ - GENERATED_BODY() - -public: - - /** - * The USkeleton that is associated with this profile. - * This is necessary so we can grab Ref Bone Poses when we are only sending partial transforms. - */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Live Stream Animation|Live Link|Translation") - TSoftObjectPtr Skeleton; - - /** - * Map from Skeleton Bone Name to Live Link Subject Bone Name. - * Only bones that have inconsistent naming between the UE Skeleton and the Live Link Skeleton (static data) - * need to have entries. - * - * Every bone name in the skeleton needs to be unique, so remapping multiple source bones onto the same target bone - * (i.e. different keys onto the same value) or remapping a source bone onto a target bone that already exists - * in the skeleton that is not also remapped will cause issues. - * - * Conceptually, this behaves similarly to a ULiveLinkRemapAsset, except we need this information - * up front to remap bones in case we need to grab Ref Bone Poses. - */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Live Stream Animation|Live Link|Translation") - TMap BoneRemappings; - - /** - * When non-empty, this is the full set of bones **from the Live Link Skeleton** for which we will - * be receiving data This is only used as an optimization so we can cache bone indices for faster lookup. - * If this is empty, then we will fall back to using name based Map lookups, which is probably - * fine for most cases. - * - * This should contain the *exact* set of bones that will be needed from the Live Link Skeleton, - * in the exact order in which they will be sent from Live Link. - */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Live Stream Animation|Live Link|Translation") - TArray BonesToUse; - - const TMap& GetBoneTransformsByName() const - { - return BoneTransformsByName; - } - - const TArray& GetBoneTransformsByIndex() const - { - return BoneTransformsByIndex; - } - - bool UpdateTransformMappings(); - -private: - - // TODO: This could probably be cached off when cooking. - /** Bone transforms by name that will be used if BonesToUse is not specified, or seems invalid. */ - TMap BoneTransformsByName; - - // TODO: This could probably be cached off when cooking. - /** Bone transforms by bone index that will be used if BonesToUse is specified and valid. */ - TArray BoneTransformsByIndex; -}; - -/** - * Class that defines how we can translate incoming Live Stream Skeletons - * onto live UE Skeletons. - * - * Individual translations are defined as FLiveStreamAnimationLiveLinkTranslationProfiles. - * - * This is necessary for things like Quantization, Compression, and Stripping unused to work properly - * as we won't have access to the Live Stream Animation frame data inside the Anim BP, and therefore - * need to preprocess the network data. - * - * This could also be changed so we delay the processing of packets completely until we know they - * will be used. - */ -UCLASS(Blueprintable, BlueprintType, Config=Game, ClassGroup = (LiveStreamAnimation)) -class LIVESTREAMANIMATION_API ULiveStreamAnimationLiveLinkFrameTranslator : public ULiveLinkFrameTranslator -{ - GENERATED_BODY() - -public: - - using FWorkerSharedPtr = ULiveLinkFrameTranslator::FWorkerSharedPtr; - - //~ Begin ULiveLinkFrameTranslator Interface - virtual TSubclassOf GetFromRole() const override; - virtual TSubclassOf GetToRole() const override; - virtual FWorkerSharedPtr FetchWorker() override; - //~ End ULiveLinkFrameTranslator Interface - -#if WITH_EDITOR - //~ Begin UObject Interface - virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; - virtual void PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent) override; - //~ End UObject Interface -#endif - - const FLiveStreamAnimationLiveLinkTranslationProfile* GetTranslationProfile(FName TranslationProfileHandleName) const - { - return GetTranslationProfile(FLiveStreamAnimationHandleWrapper(TranslationProfileHandleName)); - } - - const FLiveStreamAnimationLiveLinkTranslationProfile* GetTranslationProfile(FLiveStreamAnimationHandleWrapper TranslationProfileHandle) const - { - return TranslationProfiles.Find(TranslationProfileHandle); - } - -private: - - /** - * Map of Name to Translation profile. - * Each name used *must* be a valid LiveStreamAnimationHandle name, or that entry will be ignored. - * - * See @FLiveStreamAnimationHandle. - * See @ULiveStreamAnimationSubsystem::HandleNames. - */ - UPROPERTY(Config, EditDefaultsOnly, Category = "Live Stream Animation|Live Link|Translation") - TMap TranslationProfiles; - - FWorkerSharedPtr Worker; -}; diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkRole.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkRole.h deleted file mode 100644 index 2eede47f39a2..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkRole.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "Roles/LiveLinkBasicRole.h" -#include "LiveLink/LiveStreamAnimationLiveLinkFrameData.h" -#include "Internationalization/Internationalization.h" -#include "LiveStreamAnimationLiveLinkRole.generated.h" - -UCLASS(BlueprintType, meta = (DisplayName = "Live Stream Animation Role")) -class LIVESTREAMANIMATION_API ULiveStreamAnimationLiveLinkRole : public ULiveLinkBasicRole -{ - GENERATED_BODY() - -public: - - virtual UScriptStruct* GetStaticDataStruct() const override - { - return FLiveStreamAnimationLiveLinkStaticData::StaticStruct(); - } - - virtual UScriptStruct* GetFrameDataStruct() const override - { - return FLiveStreamAnimationLiveLinkFrameData::StaticStruct(); - } - - virtual FText GetDisplayName() const override - { - return NSLOCTEXT("LiveStreamAnimation", "LSA_DisplayName", "Live Stream Animation Live Link Role"); - } -}; diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkSourceOptions.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkSourceOptions.h deleted file mode 100644 index f144042876c5..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/LiveStreamAnimationLiveLinkSourceOptions.h +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "LiveStreamAnimationLiveLinkSourceOptions.generated.h" - -/** - * Options used to specify which parts of FLiveLinkAnimationFrameData should - * be serialized in packets. - */ -USTRUCT(BlueprintType, Category = "Live Stream Animation|Live Link|Options") -struct LIVESTREAMANIMATION_API FLiveStreamAnimationLiveLinkSourceOptions -{ - GENERATED_BODY() - -public: - - FLiveStreamAnimationLiveLinkSourceOptions() - : bWithSceneTime(false) - , bWithStringMetaData(false) - , bWithPropertyValues(false) - , bWithTransformTranslation(true) - , bWithTransformRotation(true) - , bWithTransformScale(true) - { - } - - /** Whether or not we're sending Scene Time (Timecode). */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Live Stream Animation|Live Link|Options") - uint8 bWithSceneTime : 1; - - /** Whether or not we're sending String Meta Data (very expensive). */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Live Stream Animation|Live Link|Options") - uint8 bWithStringMetaData : 1; - - /** - * Whether or not we're sending generic property values. - * Either Property values or one (or more) Transform Components or both must - * be enabled - */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Live Stream Animation|Live Link|Options") - uint8 bWithPropertyValues : 1; - - /** - * Whether or not we're sending Transform Translations. - * Either Property values or one (or more) Transform Components or both must - * be enabled - */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Live Stream Animation|Live Link|Options") - uint8 bWithTransformTranslation : 1; - - /** - * Whether or not we're sending Transform Rotations. - * Either Property values or one (or more) Transform Components or both must - * be enabled - */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Live Stream Animation|Live Link|Options") - uint8 bWithTransformRotation : 1; - - /** - * Whether or not we're sending Transform Scales. - * Either Property values or one (or more) Transform Components or both must - * be enabled - */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Live Stream Animation|Live Link|Options") - uint8 bWithTransformScale : 1; - - /** Whether or not we're sending any transform data. */ - bool WithTransforms() const - { - return bWithTransformTranslation | bWithTransformRotation | bWithTransformScale; - } - - /** Whether or not our current settings are valid. */ - bool IsValid() const - { - return bWithPropertyValues || WithTransforms(); - } -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/Test/SkelMeshToLiveLinkSource.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/Test/SkelMeshToLiveLinkSource.h deleted file mode 100644 index 73fdd9452b71..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveLink/Test/SkelMeshToLiveLinkSource.h +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserverd. - -#pragma once - -#include "CoreMinimal.h" -#include "ILiveLinkSource.h" -#include "LiveLinkTypes.h" -#include "Components/ActorComponent.h" -#include "BoneContainer.h" -#include "Interfaces/Interface_BoneReferenceSkeletonProvider.h" -#include "SkelMeshToLiveLinkSource.generated.h" - -class ULiveStreamAnimationLiveLinkFrameTranslator; - -namespace LiveStreamAnimation -{ - /** - * Bare bones Live Link source that will let us publish tracked skeletal mesh data. - */ - class FSkelMeshToLiveLinkSource : public ILiveLinkSource - { - public: - - virtual void ReceiveClient(ILiveLinkClient* InClient, FGuid InSourceGuid) override - { - LiveLinkClient = InClient; - SourceGuid = InSourceGuid; - } - - virtual void Update() override - { - } - - virtual bool CanBeDisplayedInUI() const override - { - return false; - } - - virtual bool IsSourceStillValid() const override - { - return true; - } - - virtual bool RequestSourceShutdown() override - { - LiveLinkClient = nullptr; - SourceGuid = FGuid(); - return true; - } - - virtual FText GetSourceType() const override - { - return FText(); - } - - virtual FText GetSourceMachineName() const override - { - return FText(); - } - - virtual FText GetSourceStatus() const override - { - return FText(); - } - - ILiveLinkClient* GetLiveLinkClient() const - { - return LiveLinkClient; - } - - FGuid GetGuid() const - { - return SourceGuid; - } - - private: - - ILiveLinkClient* LiveLinkClient; - FGuid SourceGuid; - }; -} - -/** - * Component that can be used to track positions in a Skel Mesh every frame, and publish them as a Live Link subject. - */ -UCLASS(BlueprintType, Blueprintable, Category="Live Stream Animation|Live Link", Meta=(BlueprintSpawnableComponent)) -class LIVESTREAMANIMATION_API ULiveLinkTestSkelMeshTrackerComponent : public UActorComponent, public IBoneReferenceSkeletonProvider -{ - GENERATED_BODY() - -public: - - ULiveLinkTestSkelMeshTrackerComponent(); - - /** - * Start tracking the specified - */ - UFUNCTION(BlueprintCallable, Category = "Live Stream Animation|Live Link") - void StartTrackingSkelMesh(FName InSubjectName); - - UFUNCTION(BlueprintCallable, Category = "Live Stream Animation|Live Link") - void StopTrackingSkelMesh(); - - //~ Begin ActorComponent Interface - virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; - virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; - //~ End ActorComponent Interface - - //~ Begin IBoneReferenceSkeletonProvider Interface - virtual class USkeleton* GetSkeleton(bool& bInvalidSkeletonIsError) override; - //~ End IBoneReferenceSkeletonProviderInterface - -private: - - class ILiveLinkClient* GetLiveLinkClient() const; - - FLiveLinkSubjectKey GetSubjectKey() const; - - UPROPERTY(EditAnywhere, Category = "Live Stream Animation|Live Link") - FName TranslationProfile; - - // The SkeletalMeshComponent that we are going to track. - UPROPERTY(EditAnywhere, Category = "Live Stream Animation|Live Link", meta = (UseComponentPicker, AllowedClasses = "SkeletalMeshComponent", AllowPrivateAccess="True")) - FComponentReference SkelMeshComp; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Live Stream Animation|Live Link", meta = (AllowPrivateAccess = "True")) - TWeakObjectPtr WeakSkelMeshComp; - - // When non-empty, this is the set of bones that we want to track. - // Mocap typically will only track a subset of bones, and this lets us replicate that behavior. - // This needs to be set before StartTrackingSkelMesh is called (or after StopTrackingSkelMesh is called). - UPROPERTY(EditDefaultsOnly, Category = "Live Stream Animation|Live Link", Meta = (AllowPrivateAccess = "true")) - TArray BonesToTrack; - - TWeakObjectPtr UsingSkelMeshComp; - TSet UsingBones; - - // The Subject Name that the tracked Skel Mesh will be published as to Live Link. - FLiveLinkSubjectName SubjectName; - - // The LiveLink source that we created to track the skeleton. - // May become invalid if it is forcibly removed from Live Link. - TWeakPtr Source; - - // If BonesToTrack is non-empty and has at least one valid bone, then we will populate this array - // with the correct bone indices so we can quickly scape - TArray BoneIndicesToTrack; - - USkeletalMeshComponent* GetSkelMeshComp() const; -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationChannel.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationChannel.h deleted file mode 100644 index fa45f742dd3e..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationChannel.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "Engine/Channel.h" -#include "ForwardingChannelsFwd.h" -#include "LiveStreamAnimationChannel.generated.h" - -namespace LiveStreamAnimation -{ - class FLiveStreamAnimationPacket; -} - -UCLASS(Transient) -class ULiveStreamAnimationChannel : public UChannel -{ - GENERATED_BODY() - -public: - - ULiveStreamAnimationChannel(); - - //~ Begin UChannel interface - virtual void Init(UNetConnection* InConnection, int32 InChIndex, EChannelCreateFlags CreateFlags) override; -protected: - virtual void ReceivedBunch(FInBunch& Bunch) override; - virtual void Tick() override; - virtual bool CanStopTicking() const override { return false; } - virtual bool CleanUp(const bool bForDestroy, EChannelCloseReason CloseReason) override; - virtual FString Describe() override - { - return FString(TEXT("LiveStreamAnimation: ")) + UChannel::Describe(); - } - //~ End UChannel Interface - -private: - - TSharedPtr ForwardingChannel; - -public: - - TSharedPtr GetForwardingChannel() - { - return ForwardingChannel; - } - - TSharedPtr GetForwardingChannel() const - { - return ForwardingChannel; - } -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationFwd.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationFwd.h deleted file mode 100644 index 194116bdf4c8..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationFwd.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -struct FLiveStreamAnimationLiveLinkSourceOptions; -struct FLiveStreamAnimationHandle; - -enum class ELiveStreamAnimationRole : uint8; - -namespace LiveStreamAnimation -{ - class FLiveLinkPacket; - class FLiveStreamAnimationLiveLinkSource; - class FLiveStreamAnimationPacket; - class FLiveLinkStreamingHelper; - - enum class ELiveStreamAnimationPacketType : uint8; -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationHandle.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationHandle.h deleted file mode 100644 index 8d8a4641f841..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationHandle.h +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "LiveStreamAnimationHandle.generated.h" - -/** - * Generic handle that can be used to identify things over the network. - * This works by using a preconfigured / preshared list of names (see ULiveStreamAnimationSettings) - * and only replicating indices of that list. - */ -struct LIVESTREAMANIMATION_API FLiveStreamAnimationHandle -{ -public: - - /** - * Create a default / invalid handle. - * Mainly used for serialization purposes. - */ - FLiveStreamAnimationHandle(): - Handle(INDEX_NONE) - { - } - - /** - * Create a handle from the given name. - * We will validate the name is in the prefconfigured list and convert - * it to the appropriate index. - * This handle will be invalid if the name isn't found. - */ - FLiveStreamAnimationHandle(FName InName): - Handle(ValidateHandle(InName)) - { - } - - /** - * Create a handle from the given index. - * We will validate the index is within bounds of the preconfigured list. - * If it is not, this handle will be invalid. - */ - FLiveStreamAnimationHandle(int32 InHandle): - Handle(ValidateHandle(InHandle)) - { - } - - bool IsValid() const - { - return INDEX_NONE != Handle; - } - - FString ToString() const - { - return FString::Printf(TEXT("%d"), Handle); - } - - /** @return The matching handle name, or NAME_None if the handle is invalid. */ - FName GetName() const; - - int32 GetValue() const - { - return Handle; - } - -public: - - friend class FArchive& operator<<(class FArchive& InAr, FLiveStreamAnimationHandle& SubjectHandle); - - friend uint32 GetTypeHash(const FLiveStreamAnimationHandle& ToHash) - { - return static_cast(ToHash.Handle); - } - - friend bool operator==(const FLiveStreamAnimationHandle& LHS, const FLiveStreamAnimationHandle& RHS) - { - return LHS.Handle == RHS.Handle; - } - -private: - - static int32 ValidateHandle(FName NameToCheck); - static int32 ValidateHandle(int32 Handle); - - int32 Handle = INDEX_NONE; -}; - - -/** Blueprint wrapper around FLiveStreamAnimationHandle that is also safe to serialize. */ -USTRUCT(BlueprintType, Category = "Live Stream Animation") -struct LIVESTREAMANIMATION_API FLiveStreamAnimationHandleWrapper -{ - GENERATED_BODY() -public: - - FLiveStreamAnimationHandleWrapper() : - Handle(NAME_None) - { - } - - explicit FLiveStreamAnimationHandleWrapper(FName InName) : - Handle(InName) - { - } - - explicit FLiveStreamAnimationHandleWrapper(int32 InHandle) : - Handle(FLiveStreamAnimationHandle(InHandle).GetName()) - { - } - - explicit FLiveStreamAnimationHandleWrapper(FLiveStreamAnimationHandle InHandle) : - Handle(InHandle.GetName()) - { - } - - bool IsValid() const - { - return Handle != NAME_None && FLiveStreamAnimationHandle(Handle).IsValid(); - } - - operator FLiveStreamAnimationHandle() const - { - return FLiveStreamAnimationHandle(Handle); - } - - friend uint32 GetTypeHash(const FLiveStreamAnimationHandleWrapper& Wrapper) - { - return GetTypeHash(Wrapper.Handle); - } - - friend bool operator==(const FLiveStreamAnimationHandleWrapper& LHS, const FLiveStreamAnimationHandleWrapper& RHS) - { - return LHS.Handle == RHS.Handle; - } - - friend bool operator==(const FLiveStreamAnimationHandleWrapper& LHS, const FName& RHS) - { - return LHS.Handle == RHS; - } - - friend bool operator==(const FName& LHS, const FLiveStreamAnimationHandleWrapper& RHS) - { - return RHS == LHS; - } - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Live Stream Animation") - FName Handle; -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationSettings.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationSettings.h deleted file mode 100644 index 518c1b82553e..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationSettings.h +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "UObject/Object.h" -#include "UObject/ObjectMacros.h" -#include "Containers/ArrayView.h" -#include "Engine/DeveloperSettings.h" -#include "LiveStreamAnimationSettings.generated.h" - -/** - * Common settings for the Live Stream Animation plugin. - */ -UCLASS(Config=Game, DefaultConfig) -class LIVESTREAMANIMATION_API ULiveStreamAnimationSettings : public UDeveloperSettings -{ - GENERATED_BODY() - -public: - - ULiveStreamAnimationSettings(); - - /** - * Get the configured Live Link Frame Translator. - * - * May return null if one hasn't be set. - */ - static class ULiveStreamAnimationLiveLinkFrameTranslator* GetFrameTranslator(); - - /** - * Register to receive notifications whenever the FrameTranslator is changed. - * This should only happen in the Editor when a user changes the settings. - */ - static FDelegateHandle AddFrameTranslatorChangedCallback(FSimpleMulticastDelegate::FDelegate&& InDelegate); - static FDelegateHandle AddFrameTranslatorChangedCallback(const FSimpleMulticastDelegate::FDelegate& InDelegate); - - /** - * Unregister from notifications when the FrameTranslator is changed. - */ - static void RemoveFrameTranslatorChangedCallback(FDelegateHandle DelegateHandle); - - /** - * Get the configured list of Anim Handle Names. - */ - static const TArrayView GetHandleNames(); - - -#if WITH_EDITOR - //~ Begin UObject Interface - virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; - virtual void PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent) override; - //~ End UObject Interface -#endif - - //~ Begin UDeveloperSettingsObject Interface - virtual FName GetContainerName() const; - virtual FName GetCategoryName() const; - virtual FName GetSectionName() const; - -#if WITH_EDITOR - virtual FText GetSectionText() const; - virtual FText GetSectionDescription() const; -#endif - //~ End UDeveloperSettingsObject - -private: - - /** - * The Frame Translator that'll be used to apply networked Live Link packets to usable - * animation frames. - * - * See ULiveStreamAnimationLiveLinkFrameTranslator for more information. - */ - UPROPERTY(Config, Transient, EditAnywhere, Category="LiveStreamAnimation") - TSoftObjectPtr FrameTranslator; - - /** - * List of names that we know and can use for network handles. - * - * See ULiveStreamAnimationSubsystem and FLiveStreamAnimationHandle for more information. - */ - UPROPERTY(Config, Transient, EditAnywhere, Category = "LiveStreamAnimation") - TArray HandleNames; - - //! Used to track changes to the FrameTranslator so systems running in the Editor / PIE - //! can update their state. - FSimpleMulticastDelegate OnFrameTranslatorChanged; -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationSubsystem.h b/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationSubsystem.h deleted file mode 100644 index 4e5ee2848495..000000000000 --- a/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationSubsystem.h +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserverd. - -#pragma once - -#include "Subsystems/GameInstanceSubsystem.h" -#include "Engine/GameInstance.h" -#include "LiveStreamAnimationFwd.h" -#include "ForwardingChannelsFwd.h" -#include "ForwardingChannelFactory.h" -#include "LiveStreamAnimationHandle.h" -#include "Containers/ArrayView.h" -#include "LiveLink/LiveStreamAnimationLiveLinkSourceOptions.h" -#include "LiveStreamAnimationSubsystem.generated.h" - -UENUM(BlueprintType) -enum class ELiveStreamAnimationRole : uint8 -{ - Proxy, //! Subsystem neither creates nor consumes animation data, - //! but is acting as a Proxy to pass through. - - Processor, //! Subsystem is consuming animation packets and evaluating - //! them locally. It also acts as a Proxy. - - Tracker //! Subsystem is evaluating animation locally and generating - //! animation packets that can be sent to other connections. - //! This node will ignore any received packets. -}; - -DECLARE_MULTICAST_DELEGATE_OneParam(FOnLiveStreamAnimationRoleChanged, const ELiveStreamAnimationRole); - -namespace LiveStreamAnimation -{ - class FSkelMeshToLiveLinkSource; -} - -/** - * Subsystem used to help with replicating Animation Data (typically performance capture data) - * through a network to multiple connections, at multiple layers. - * - * Other means are used to manage the connections themselves and this plugin is used to help - * facilitate different animation formats and compression techniques. - * - ************************************************************************************** - *********************************** Typical Setup ************************************ - ************************************************************************************** - * - * This subsystem would be added as a subsystem with your Game Instance. - * - * Every Net Connection that is participating in the replication of data - * will need to open a ULiveStreamAnimation channel. - * - * Using the ForwardingChannels plugin, these channels will automatically register themselves - * with the appropriate Forwarding Group so we can send and receive animation data. - * - ************************************************************************************** - *************************************** Role ***************************************** - ************************************************************************************** - * - * Typically, any node in the network will be either be Tracking Data, Processing Data, - * or Proxying. - * - * Nodes that are Tracking Data are actually evaluating animation data, serializing frames - * into packets, and sending those packets off so others can evaluate them. - * - * Game code should tell the Live Stream Animation Subsystem what - * type of animation data it wants to track and how. At that point, the Subsystem - * will listen for new animation data and generate the appropriate packets. - * These packets are then sent up to a connected server node. - * - * NOTE: These packets *could* also be sent to attached clients, but its assumed - * that a tracker is itself acting as a client with no connections. - * - * Nodes that are Processing Data will receiving animation data and evaluate them. - * Depending on the animation data type, Game code may not need to tell the - * Subsystem exactly what type of data its expecting. - * - * For example, Live Link data will automatically be pushed into the correct Live Link Subject - * and Game code can just register for Live Link updates directly. - * - * Nodes that are acting as Proxies are simply receving animation packets, doing minimal - * validation on them, and passing them along to connected clients. Proxies currently - * do not send data upstream to servers. - * - ************************************************************************************** - ********************************* Join In Progress *********************************** - ************************************************************************************** - * - * Both Proxies and Trackers will maintain some amount of Registration state for - * animation data so when new connections are established they can be properly initialized - * to start receiving new data from the server immediately. - * - * While Trackers may have some cached animation frames, neither Proxies nor Trackers - * will attempt to send that data to newly established connections. - * - * So, in Unreal parlance, Animation Frames can be thought of as Unreliable Multicasts, - * where Registration Data is more akin to Reliable Property Replication. - * - ************************************************************************************** - ***************************** Stream Animation Handles ******************************* - ************************************************************************************** - * - * Live Stream Animation handles are a very simple way to efficiently replicate - * references to names in the Live Stream Animation plugin. - * - * These work similar to Gameplay Tags or fixed FName replication in that designers or anyone - * can setup names that can be shared across all builds (see ULiveStreamAnimationSubsystem::HandleNames), - * and then instead of replicating string data we can simple replicate an index that maps - * to one of these preconfigured names. - * - * The list of available Handle Names is defined in ULiveStreamAnimationSettings, and **must** - * be the same (order and size) on all instances that are sending or receiving animation data. - * - * The main reason why existing engine systems weren't used was just to ensure isolation - * between this plugin and other game systems. - * However, there's no reason why this couldn't be changed later. - * - */ -UCLASS(DisplayName = "Live Stream Animation Subsystem", Transient, Config=Engine, Category = "Live Stream Animation") -class ULiveStreamAnimationSubsystem : public UGameInstanceSubsystem, public IForwardingChannelFactory -{ - GENERATED_BODY() - -public: - - LIVESTREAMANIMATION_API ULiveStreamAnimationSubsystem(); - - //~ Begin USubsystem Interface - LIVESTREAMANIMATION_API virtual void Initialize(FSubsystemCollectionBase& Collection) override; - LIVESTREAMANIMATION_API virtual void Deinitialize() override; - //~ End USubsystem Interface - - //~ Begin IForwardingChannelFactory Interface - virtual void CreateForwardingChannel(class UNetConnection* InNetConnection) override; - virtual void SetAcceptClientPackets(bool bInShouldAcceptClientPackets) override; - //~ End IForwardingChannelFactory Interface - - UFUNCTION(BlueprintCallable, Category = "Live Stream Animation") - LIVESTREAMANIMATION_API void SetRole(const ELiveStreamAnimationRole NewRole); - - UFUNCTION(BlueprintPure, Category = "Live Stream Animation") - LIVESTREAMANIMATION_API ELiveStreamAnimationRole GetRole() const - { - return Role; - } - - /** - * Start tracking a Live Link subject that is active on this machine, serializing its data - * to animation packets, and forward those to others connections. - * Requires Animation Tracking to be enabled. - * - * The Registered Name passed in *must* be available / configured in the AllowedRegisteredNames - * list, and that list is expected to be the same on all instances. - * - * @param LiveLinkSubject The Live Link Subject that we are pulling animation data from locally. - * - * @param RegisteredName The registered Live Link Subject name that will be used for clients - * evaluating animation data remotely. - * This name must be present in the HandleNames list. - * - * @param Options Options describing the type of data we will track and send. - * - * @param TranslationProfile The Translation Profile that we should use for this subject. - * This name must be present in the HandleNames list, otherwise the translation will not - * be applied. - * @see ULiveStreamAnimationLiveLinkFrameTranslator. - * - * @return Whether or not we successfully registered the subject for tracking. - */ - UFUNCTION(BlueprintCallable, Category = "Live Stream Animation|Live Link") - LIVESTREAMANIMATION_API bool StartTrackingLiveLinkSubject( - const FName LiveLinkSubject, - const FLiveStreamAnimationHandleWrapper RegisteredName, - const FLiveStreamAnimationLiveLinkSourceOptions Options, - const FLiveStreamAnimationHandleWrapper TranslationProfile); - - LIVESTREAMANIMATION_API bool StartTrackingLiveLinkSubject( - const FName LiveLinkSubject, - const FLiveStreamAnimationHandle RegisteredName, - const FLiveStreamAnimationLiveLinkSourceOptions Options, - const FLiveStreamAnimationHandle TranslationProfile); - - /** - * Stop tracking a Live Link subject. - * - * @param RegisteredName The registered remote name for the Live Link Subject. - */ - UFUNCTION(BlueprintCallable, Category = "Live Stream Animation|Live Link") - LIVESTREAMANIMATION_API void StopTrackingLiveLinkSubject(const FLiveStreamAnimationHandleWrapper RegisteredName); - LIVESTREAMANIMATION_API void StopTrackingLiveLinkSubject(const FLiveStreamAnimationHandle RegisteredName); - - static FName GetChannelName(); - - void ReceivedPacket(const TSharedRef& Packet, ForwardingChannels::FForwardingChannel& Channel); - - bool SendPacketToServer(const TSharedRef& Packet); - - static bool IsSubsystemEnabledInConfig() - { - return GetDefault()->bEnabled; - } - - bool IsEnabledAndInitialized() const - { - return bEnabled && bInitialized; - } - - FOnLiveStreamAnimationRoleChanged& GetOnRoleChanged() - { - return OnRoleChanged; - } - - TWeakPtr GetOrCreateSkelMeshToLiveLinkSource(); - -private: - - UFUNCTION(BlueprintCallable, Category = "Live Stream Animation", DisplayName = "SetAcceptClientPackets", Meta=(AllowPrivateAccess="True")) - void SetAcceptClientPackets_Private(bool bInShouldAcceptClientPackets) - { - bShouldAcceptClientPackets = bInShouldAcceptClientPackets; - } - - FOnLiveStreamAnimationRoleChanged OnRoleChanged; - - UPROPERTY(Config, Transient) - bool bEnabled = true; - - template - T* GetSubsystem() - { - UGameInstance* GameInstance = GetGameInstance(); - return GameInstance ? GameInstance->GetSubsystem() : nullptr; - } - - bool bInitialized = false; - bool bShouldAcceptClientPackets = false; - - TSharedPtr ForwardingGroup; - TSharedPtr LiveLinkStreamingHelper; - - ELiveStreamAnimationRole Role; -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MattRadford_RnD/MattRadford_RnD.uplugin b/Engine/Plugins/Experimental/MattRadford_RnD/MattRadford_RnD.uplugin deleted file mode 100644 index 45f01fd4bc5c..000000000000 --- a/Engine/Plugins/Experimental/MattRadford_RnD/MattRadford_RnD.uplugin +++ /dev/null @@ -1,15 +0,0 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "1.0", - "FriendlyName": "MattRadford_RnD", - "Description": "", - "Category": "Other", - "CreatedBy": "Matt Radford", - "CreatedByURL": "", - "DocsURL": "", - "MarketplaceURL": "", - "SupportURL": "", - "CanContainContent": true, - "Installed": true -} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/MeshModelingTools.Build.cs b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/MeshModelingTools.Build.cs index ec9a76e1b51b..3c9059db0908 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/MeshModelingTools.Build.cs +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/MeshModelingTools.Build.cs @@ -74,7 +74,7 @@ public class MeshModelingTools : ModuleRules "Engine", "ModelingOperators", "InputCore", - + "PhysicsCore" // ... add private dependencies that you statically link with here ... } ); diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/BakeMeshAttributeMapsTool.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/BakeMeshAttributeMapsTool.cpp index 0cc96b1c856e..66de305848da 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/BakeMeshAttributeMapsTool.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/BakeMeshAttributeMapsTool.cpp @@ -6,11 +6,14 @@ #include "DynamicMesh3.h" #include "DynamicMeshAttributeSet.h" -#include "MeshNormals.h" +#include "MeshTransforms.h" #include "MeshDescriptionToDynamicMesh.h" -#include "Sampling/SphericalFibonacci.h" -#include "Sampling/Gaussians.h" -#include "Image/ImageOccupancyMap.h" +#include "Sampling/MeshImageBakingCache.h" +#include "Sampling/MeshNormalMapBaker.h" +#include "Sampling/MeshOcclusionMapBaker.h" +#include "Sampling/MeshCurvatureMapBaker.h" +#include "Sampling/MeshPropertyMapBaker.h" +#include "Sampling/MeshResampleImageBaker.h" #include "Util/IndexUtil.h" #include "SimpleDynamicMeshComponent.h" @@ -27,8 +30,6 @@ #include "Engine/Classes/Components/StaticMeshComponent.h" #include "Engine/Classes/Engine/StaticMesh.h" -#include "Async/ParallelFor.h" - #define LOCTEXT_NAMESPACE "UBakeMeshAttributeMapsTool" @@ -40,8 +41,9 @@ bool UBakeMeshAttributeMapsToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const { // NOTE: currently can only bake for UStaticMeshComponent - return ToolBuilderUtil::CountComponents(SceneState, CanMakeComponentTarget) == 2 - &&(ToolBuilderUtil::CountComponents(SceneState, [&](UActorComponent* Comp) { return Cast(Comp) != nullptr; }) == 2); + int32 NumTargets = ToolBuilderUtil::CountComponents(SceneState, CanMakeComponentTarget); + return (NumTargets == 1 || NumTargets == 2) + && (ToolBuilderUtil::CountComponents(SceneState, [&](UActorComponent* Comp) { return Cast(Comp) != nullptr; }) == NumTargets); } UInteractiveTool* UBakeMeshAttributeMapsToolBuilder::BuildTool(const FToolBuilderState& SceneState) const @@ -51,8 +53,10 @@ UInteractiveTool* UBakeMeshAttributeMapsToolBuilder::BuildTool(const FToolBuilde TArray Components = ToolBuilderUtil::FindAllComponents(SceneState, CanMakeComponentTarget); TArray> MeshComponents; - MeshComponents.Add( MakeComponentTarget(Cast(Components[0])) ); - MeshComponents.Add( MakeComponentTarget(Cast(Components[1])) ); + for (int32 k = 0; k < Components.Num(); ++k) + { + MeshComponents.Add(MakeComponentTarget(Cast(Components[k]))); + } NewTool->SetSelection(MoveTemp(MeshComponents)); return NewTool; @@ -60,6 +64,15 @@ UInteractiveTool* UBakeMeshAttributeMapsToolBuilder::BuildTool(const FToolBuilde + +TArray UBakeMeshAttributeMapsToolProperties::GetUVLayerNamesFunc() +{ + return UVLayerNamesList; +} + + + + /* * Tool */ @@ -104,11 +117,6 @@ void UBakeMeshAttributeMapsTool::Setup() BaseMeshTangents = MakeShared(&BaseMesh); BaseMeshTangents->CopyTriVertexTangents(*DynamicMeshComponent->GetTangents()); - - FMeshDescriptionToDynamicMesh Converter; - Converter.Convert(ComponentTargets[1]->GetMesh(), DetailMesh); - DetailSpatial.SetMesh(&DetailMesh, true); - UMaterial* Material = LoadObject(nullptr, TEXT("/MeshModelingToolset/Materials/BakePreviewMaterial")); check(Material); if (Material != nullptr) @@ -117,31 +125,73 @@ void UBakeMeshAttributeMapsTool::Setup() DynamicMeshComponent->SetOverrideRenderMaterial(PreviewMaterial); } + bIsBakeToSelf = (ComponentTargets.Num() == 1); + // hide input StaticMeshComponent ComponentTargets[0]->SetOwnerVisibility(false); - //ComponentTargets[1]->SetOwnerVisibility(false); + Settings = NewObject(this); Settings->RestoreProperties(this); + Settings->UVLayerNamesList.Reset(); + int32 FoundIndex = -1; + for (int32 k = 0; k < BaseMesh.Attributes()->NumUVLayers(); ++k) + { + Settings->UVLayerNamesList.Add(FString::FromInt(k)); + if (Settings->UVLayer == Settings->UVLayerNamesList.Last()) + { + FoundIndex = k; + } + } + if (FoundIndex == -1) + { + Settings->UVLayer = Settings->UVLayerNamesList[0]; + } AddToolPropertySource(Settings); - OcclusionMapProps = NewObject(this); - OcclusionMapProps->RestoreProperties(this); - AddToolPropertySource(OcclusionMapProps); + Settings->WatchProperty(Settings->MapType, [this](EBakeMapType) { bResultValid = false; UpdateOnModeChange(); }); + Settings->WatchProperty(Settings->Resolution, [this](EBakeTextureResolution) { bResultValid = false; }); + Settings->WatchProperty(Settings->UVLayer, [this](FString) { bResultValid = false; }); + Settings->WatchProperty(Settings->bUseWorldSpace, [this](bool) { bDetailMeshValid = false; bResultValid = false; }); + NormalMapProps = NewObject(this); NormalMapProps->RestoreProperties(this); AddToolPropertySource(NormalMapProps); + SetToolPropertySourceEnabled(NormalMapProps, false); - Settings->WatchProperty(Settings->Resolution, [this](EBakeTextureResolution) { InvalidateNormals(); InvalidateOcclusion(); }); - Settings->WatchProperty(Settings->bNormalMap, [this](bool) { InvalidateNormals(); }); - Settings->WatchProperty(Settings->bAmbientOcclusionMap, [this](bool) { InvalidateOcclusion(); }); - OcclusionMapProps->WatchProperty(OcclusionMapProps->OcclusionRays, [this](int32) { InvalidateOcclusion(); }); - OcclusionMapProps->WatchProperty(OcclusionMapProps->MaxDistance, [this](float) { InvalidateOcclusion(); }); - OcclusionMapProps->WatchProperty(OcclusionMapProps->BlurRadius, [this](float) { InvalidateOcclusion(); }); - OcclusionMapProps->WatchProperty(OcclusionMapProps->bGaussianBlur, [this](float) { InvalidateOcclusion(); }); - OcclusionMapProps->WatchProperty(OcclusionMapProps->BiasAngle, [this](float) { InvalidateOcclusion(); }); + OcclusionMapProps = NewObject(this); + OcclusionMapProps->RestoreProperties(this); + AddToolPropertySource(OcclusionMapProps); + SetToolPropertySourceEnabled(OcclusionMapProps, false); + OcclusionMapProps->WatchProperty(OcclusionMapProps->OcclusionRays, [this](int32) { bResultValid = false; }); + OcclusionMapProps->WatchProperty(OcclusionMapProps->MaxDistance, [this](float) { bResultValid = false; }); + OcclusionMapProps->WatchProperty(OcclusionMapProps->BlurRadius, [this](float) { bResultValid = false; }); + OcclusionMapProps->WatchProperty(OcclusionMapProps->bGaussianBlur, [this](float) { bResultValid = false; }); + OcclusionMapProps->WatchProperty(OcclusionMapProps->BiasAngle, [this](float) { bResultValid = false; }); + + + CurvatureMapProps = NewObject(this); + CurvatureMapProps->RestoreProperties(this); + AddToolPropertySource(CurvatureMapProps); + SetToolPropertySourceEnabled(CurvatureMapProps, false); + CurvatureMapProps->WatchProperty(CurvatureMapProps->RangeMultiplier, [this](float) { bResultValid = false; }); + CurvatureMapProps->WatchProperty(CurvatureMapProps->MinRangeMultiplier, [this](float) { bResultValid = false; }); + CurvatureMapProps->WatchProperty(CurvatureMapProps->CurvatureType, [this](EBakedCurvatureTypeMode) { bResultValid = false; }); + CurvatureMapProps->WatchProperty(CurvatureMapProps->ColorMode, [this](EBakedCurvatureColorMode) { bResultValid = false; }); + CurvatureMapProps->WatchProperty(CurvatureMapProps->Clamping, [this](EBakedCurvatureClampMode) { bResultValid = false; }); + CurvatureMapProps->WatchProperty(CurvatureMapProps->BlurRadius, [this](float) { bResultValid = false; }); + CurvatureMapProps->WatchProperty(CurvatureMapProps->bGaussianBlur, [this](float) { bResultValid = false; }); + + + Texture2DProps = NewObject(this); + Texture2DProps->RestoreProperties(this); + AddToolPropertySource(Texture2DProps); + SetToolPropertySourceEnabled(Texture2DProps, false); + Texture2DProps->WatchProperty(Texture2DProps->UVLayer, [this](float) { bResultValid = false; }); + Texture2DProps->WatchProperty(Texture2DProps->SourceTexture, [this](UTexture2D*) { bResultValid = false; }); + VisualizationProps = NewObject(this); VisualizationProps->RestoreProperties(this); @@ -150,24 +200,34 @@ void UBakeMeshAttributeMapsTool::Setup() InitializeEmptyMaps(); bResultValid = false; + bDetailMeshValid = false; GetToolManager()->DisplayMessage( - LOCTEXT("OnStartTool", "Bake Normal and AO Maps. Select Bake Mesh (LowPoly) first, then Detail Mesh second. Texture Assets will be created on Accept. "), + LOCTEXT("OnStartTool", "Bake Maps. Select Bake Mesh (LowPoly) first, then Detail Mesh second. Texture Assets will be created on Accept. "), EToolMessageLevel::UserNotification); } + + +bool UBakeMeshAttributeMapsTool::CanAccept() const +{ + return Settings->Result != nullptr; +} + + void UBakeMeshAttributeMapsTool::Shutdown(EToolShutdownType ShutdownType) { Settings->SaveProperties(this); OcclusionMapProps->SaveProperties(this); NormalMapProps->SaveProperties(this); + CurvatureMapProps->SaveProperties(this); + Texture2DProps->SaveProperties(this); VisualizationProps->SaveProperties(this); if (DynamicMeshComponent != nullptr) { ComponentTargets[0]->SetOwnerVisibility(true); - ComponentTargets[1]->SetOwnerVisibility(true); if (ShutdownType == EToolShutdownType::Accept) { @@ -178,21 +238,56 @@ void UBakeMeshAttributeMapsTool::Shutdown(EToolShutdownType ShutdownType) if (AssetAPI != nullptr) { - if (Settings->bNormalMap && NormalMapProps->Result != nullptr) + bool bCreatedAssetOK = false; + switch (Settings->MapType) { - FTexture2DBuilder::CopyPlatformDataToSourceData(NormalMapProps->Result, FTexture2DBuilder::ETextureType::NormalMap); - bool bOK = AssetGenerationUtil::SaveGeneratedTexture2D(AssetAPI, NormalMapProps->Result, - FString::Printf(TEXT("%s_Normals"), *BaseName), StaticMeshAsset); - check(bOK); - } + default: + check(false); + break; - if (Settings->bAmbientOcclusionMap && OcclusionMapProps->Result != nullptr) - { - FTexture2DBuilder::CopyPlatformDataToSourceData(OcclusionMapProps->Result, FTexture2DBuilder::ETextureType::AmbientOcclusion); - bool bOK = AssetGenerationUtil::SaveGeneratedTexture2D(AssetAPI, OcclusionMapProps->Result, + case EBakeMapType::TangentSpaceNormalMap: + FTexture2DBuilder::CopyPlatformDataToSourceData(Settings->Result, FTexture2DBuilder::ETextureType::NormalMap); + bCreatedAssetOK = AssetGenerationUtil::SaveGeneratedTexture2D(AssetAPI, Settings->Result, + FString::Printf(TEXT("%s_Normals"), *BaseName), StaticMeshAsset); + break; + + case EBakeMapType::AmbientOcclusion: + FTexture2DBuilder::CopyPlatformDataToSourceData(Settings->Result, FTexture2DBuilder::ETextureType::AmbientOcclusion); + bCreatedAssetOK = AssetGenerationUtil::SaveGeneratedTexture2D(AssetAPI, Settings->Result, FString::Printf(TEXT("%s_Occlusion"), *BaseName), StaticMeshAsset); - check(bOK); + break; + + case EBakeMapType::Curvature: + FTexture2DBuilder::CopyPlatformDataToSourceData(Settings->Result, FTexture2DBuilder::ETextureType::Color); + bCreatedAssetOK = AssetGenerationUtil::SaveGeneratedTexture2D(AssetAPI, Settings->Result, + FString::Printf(TEXT("%s_Curvature"), *BaseName), StaticMeshAsset); + break; + + case EBakeMapType::NormalImage: + FTexture2DBuilder::CopyPlatformDataToSourceData(Settings->Result, FTexture2DBuilder::ETextureType::Color); + bCreatedAssetOK = AssetGenerationUtil::SaveGeneratedTexture2D(AssetAPI, Settings->Result, + FString::Printf(TEXT("%s_NormalImg"), *BaseName), StaticMeshAsset); + break; + + case EBakeMapType::FaceNormalImage: + FTexture2DBuilder::CopyPlatformDataToSourceData(Settings->Result, FTexture2DBuilder::ETextureType::Color); + bCreatedAssetOK = AssetGenerationUtil::SaveGeneratedTexture2D(AssetAPI, Settings->Result, + FString::Printf(TEXT("%s_FaceNormalImg"), *BaseName), StaticMeshAsset); + break; + + case EBakeMapType::PositionImage: + FTexture2DBuilder::CopyPlatformDataToSourceData(Settings->Result, FTexture2DBuilder::ETextureType::Color); + bCreatedAssetOK = AssetGenerationUtil::SaveGeneratedTexture2D(AssetAPI, Settings->Result, + FString::Printf(TEXT("%s_PositionImg"), *BaseName), StaticMeshAsset); + break; + + case EBakeMapType::Texture2DImage: + FTexture2DBuilder::CopyPlatformDataToSourceData(Settings->Result, FTexture2DBuilder::ETextureType::Color); + bCreatedAssetOK = AssetGenerationUtil::SaveGeneratedTexture2D(AssetAPI, Settings->Result, + FString::Printf(TEXT("%s_TextureImg"), *BaseName), StaticMeshAsset); + break; } + ensure(bCreatedAssetOK); } } @@ -220,15 +315,26 @@ void UBakeMeshAttributeMapsTool::Render(IToolsContextRenderAPI* RenderAPI) - -void UBakeMeshAttributeMapsTool::InvalidateOcclusion() -{ - bResultValid = false; -} - -void UBakeMeshAttributeMapsTool::InvalidateNormals() +void UBakeMeshAttributeMapsTool::UpdateDetailMesh() { + DetailMesh = MakeShared(); + FMeshDescriptionToDynamicMesh Converter; + TUniquePtr& UseDetailTarget = (bIsBakeToSelf) ? ComponentTargets[0] : ComponentTargets[1]; + Converter.Convert(UseDetailTarget->GetMesh(), *DetailMesh); + + if (Settings->bUseWorldSpace && bIsBakeToSelf == false) + { + FTransform3d DetailToWorld(UseDetailTarget->GetWorldTransform()); + MeshTransforms::ApplyTransform(*DetailMesh, DetailToWorld); + FTransform3d WorldToBase(ComponentTargets[0]->GetWorldTransform()); + MeshTransforms::ApplyTransform(*DetailMesh, WorldToBase.Inverse()); + } + + DetailSpatial = MakeShared(); + DetailSpatial->SetMesh(DetailMesh.Get(), true); + bResultValid = false; + DetailMeshTimestamp++; } @@ -236,365 +342,71 @@ void UBakeMeshAttributeMapsTool::InvalidateNormals() -/** - * Find point on Detail mesh that corresponds to point on Base mesh. - * If nearest point on Detail mesh is within DistanceThreshold, uses that point (cleanly handles coplanar/etc). - * Otherwise casts a ray in Normal direction. - * If Normal-direction ray misses, use reverse direction. - * If both miss, we return false, no correspondence found - */ -static bool GetDetailTrianglePoint( - const FDynamicMesh3& DetailMesh, - const FDynamicMeshAABBTree3& DetailSpatial, - const FVector3d& BasePoint, - const FVector3d& BaseNormal, - int32& DetailTriangleOut, - FVector3d& DetailTriBaryCoords, - double DistanceThreshold = FMathf::ZeroTolerance * 100.0f) -{ - // check if we are within on-surface tolerance, if so we use nearest point - IMeshSpatial::FQueryOptions OnSurfQueryOptions; - OnSurfQueryOptions.MaxDistance = DistanceThreshold; - double NearDistSqr = 0; - int32 NearestTriID = DetailSpatial.FindNearestTriangle(BasePoint, NearDistSqr, OnSurfQueryOptions); - if (DetailMesh.IsTriangle(NearestTriID)) - { - DetailTriangleOut = NearestTriID; - FDistPoint3Triangle3d DistQuery = TMeshQueries::TriangleDistance(DetailMesh, NearestTriID, BasePoint); - DetailTriBaryCoords = DistQuery.TriangleBaryCoords; - return true; - } - - // TODO: should we check normals here? inverse normal should probably not be considered valid - - // shoot rays forwards and backwards - FRay3d Ray(BasePoint, BaseNormal), BackwardsRay(BasePoint, -BaseNormal); - int32 HitTID = IndexConstants::InvalidID, BackwardHitTID = IndexConstants::InvalidID; - double HitDist, BackwardHitDist; - bool bHitForward = DetailSpatial.FindNearestHitTriangle(Ray, HitDist, HitTID); - bool bHitBackward = DetailSpatial.FindNearestHitTriangle(BackwardsRay, BackwardHitDist, BackwardHitTID); - - // use the backwards hit if it is closer than the forwards hit - if ( (bHitBackward && bHitForward == false) || (bHitForward && bHitBackward && BackwardHitDist < HitDist)) - { - Ray = BackwardsRay; - HitTID = BackwardHitTID; - HitDist = BackwardHitDist; - } - - // if we got a valid ray hit, use it - if (DetailMesh.IsTriangle(HitTID)) - { - DetailTriangleOut = HitTID; - FIntrRay3Triangle3d IntrQuery = TMeshQueries::TriangleIntersection(DetailMesh, HitTID, Ray); - DetailTriBaryCoords = IntrQuery.TriangleBaryCoords; - return true; - } - - // if we get this far, both rays missed, so use absolute nearest point regardless of distance - //NearestTriID = DetailSpatial.FindNearestTriangle(BasePoint, NearDistSqr); - //if (DetailMesh.IsTriangle(NearestTriID)) - //{ - // DetailTriangleOut = NearestTriID; - // FDistPoint3Triangle3d DistQuery = TMeshQueries::TriangleDistance(DetailMesh, NearestTriID, BasePoint); - // DetailTriBaryCoords = DistQuery.TriangleBaryCoords; - // return true; - //} - - return false; -} -// Information about Base/Detail point correspondence -struct FDetailPointSample -{ - FMeshUVSampleInfo BaseSample; - FVector3d BaseNormal; - - int32 DetailTriID; - FVector3d DetailBaryCoords; -}; - - void UBakeMeshAttributeMapsTool::UpdateResult() { - if (bResultValid) + + if (bDetailMeshValid == false) + { + UpdateDetailMesh(); + bDetailMeshValid = true; + } + + if (bResultValid) { return; } + // clear warning (ugh) + GetToolManager()->DisplayMessage(FText(), EToolMessageLevel::UserWarning); + int32 ImageSize = (int32)Settings->Resolution; FImageDimensions Dimensions(ImageSize, ImageSize); - FNormalMapSettings NormalMapSettings; - NormalMapSettings.Dimensions = Dimensions; - bool bBakeNormals = Settings->bNormalMap && ! (CachedNormalMapSettings == NormalMapSettings); + FBakeCacheSettings BakeCacheSettings; + BakeCacheSettings.Dimensions = Dimensions; + BakeCacheSettings.UVLayer = FCString::Atoi(*Settings->UVLayer); + BakeCacheSettings.DetailTimestamp = this->DetailMeshTimestamp; - - FOcclusionMapSettings OcclusionMapSettings; - OcclusionMapSettings.Dimensions = Dimensions; - OcclusionMapSettings.MaxDistance = (OcclusionMapProps->MaxDistance == 0) ? TNumericLimits::Max() : OcclusionMapProps->MaxDistance; - OcclusionMapSettings.OcclusionRays = OcclusionMapProps->OcclusionRays; - OcclusionMapSettings.BlurRadius = (OcclusionMapProps->bGaussianBlur) ? OcclusionMapProps->BlurRadius : 0.0; - OcclusionMapSettings.BiasAngle = OcclusionMapProps->BiasAngle; - bool bBakeOcclusion = Settings->bAmbientOcclusionMap && ! (CachedOcclusionMapSettings == OcclusionMapSettings); - double BiasDotThreshold = FMathd::Cos( FMathd::Clamp(90.0-OcclusionMapSettings.BiasAngle, 0.0, 90.0) * FMathd::DegToRad ); - - // if we have nothing to do, we can early-out - if (bBakeNormals == false && bBakeOcclusion == false) + // rebuild bake cache if we need to + if (!(CachedBakeCacheSettings == BakeCacheSettings)) { - UpdateVisualization(); - GetToolManager()->PostInvalidation(); - bResultValid = true; - return; + BakeCache = MakePimpl(); + BakeCache->SetDetailMesh(DetailMesh.Get(), DetailSpatial.Get()); + BakeCache->SetBakeTargetMesh(&BaseMesh); + BakeCache->SetDimensions(Dimensions); + BakeCache->SetUVLayer(BakeCacheSettings.UVLayer); + BakeCache->ValidateCache(); } - const FDynamicMesh3* Mesh = &BaseMesh; - const FDynamicMeshUVOverlay* UVOverlay = Mesh->Attributes()->GetUVLayer(0); - const FDynamicMeshNormalOverlay* NormalOverlay = Mesh->Attributes()->PrimaryNormals(); - - const FDynamicMeshNormalOverlay* DetailNormalOverlay = DetailMesh.Attributes()->GetNormalLayer(0); - check(DetailNormalOverlay); - - // this sampler finds the correspondence between base surface and detail surface - TMeshSurfaceUVSampler DetailMeshSampler; - DetailMeshSampler.Initialize(Mesh, UVOverlay, EMeshSurfaceSamplerQueryType::TriangleAndUV, FDetailPointSample(), - [Mesh, NormalOverlay, this](const FMeshUVSampleInfo& SampleInfo, FDetailPointSample& ValueOut) + // rebuild select map type + switch (Settings->MapType) { - //FVector3d BaseTriNormal = Mesh->GetTriNormal(SampleInfo.TriangleIndex); - NormalOverlay->GetTriBaryInterpolate(SampleInfo.TriangleIndex, &SampleInfo.BaryCoords[0], &ValueOut.BaseNormal[0]); - ValueOut.BaseNormal.Normalize(); - FVector3d RayDir = ValueOut.BaseNormal; + default: + case EBakeMapType::TangentSpaceNormalMap: + UpdateResult_Normal(); + break; + case EBakeMapType::AmbientOcclusion: + UpdateResult_Occlusion(); + break; + case EBakeMapType::Curvature: + UpdateResult_Curvature(); + break; - ValueOut.BaseSample = SampleInfo; + case EBakeMapType::NormalImage: + case EBakeMapType::FaceNormalImage: + case EBakeMapType::PositionImage: + UpdateResult_MeshProperty(); + break; - // find detail mesh triangle point - bool bFoundTri = GetDetailTrianglePoint(DetailMesh, DetailSpatial, SampleInfo.SurfacePoint, RayDir, - ValueOut.DetailTriID, ValueOut.DetailBaryCoords); - if (!bFoundTri) - { - ValueOut.DetailTriID = FDynamicMesh3::InvalidID; - } - }); - - - // sample normal of detail surface in tangent-space of base surface - auto NormalSampleFunction = [&](const FDetailPointSample& SampleData) - { - int32 DetailTriID = SampleData.DetailTriID; - if (DetailMesh.IsTriangle(DetailTriID)) - { - // get tangents on base mesh - FVector3d BaseTangentX, BaseTangentY; - BaseMeshTangents->GetInterpolatedTriangleTangent(SampleData.BaseSample.TriangleIndex, SampleData.BaseSample.BaryCoords, BaseTangentX, BaseTangentY); - - FVector3d DetailNormal; - DetailNormalOverlay->GetTriBaryInterpolate(DetailTriID, &SampleData.DetailBaryCoords[0], &DetailNormal.X); - DetailNormal.Normalize(); - double dx = DetailNormal.Dot(BaseTangentX); - double dy = DetailNormal.Dot(BaseTangentY); - double dz = DetailNormal.Dot(SampleData.BaseNormal); - return FVector3f((float)dx, (float)dy, (float)dz); - } - return FVector3f::UnitZ(); - }; - - - // precompute ray directions for AO - TSphericalFibonacci Points(2 * OcclusionMapSettings.OcclusionRays); - TArray RayDirections; - for (int32 k = 0; k < Points.Num(); ++k) - { - FVector3d P = Points[k]; - if (P.Z > 0) - { - RayDirections.Add(P.Normalized()); - } + case EBakeMapType::Texture2DImage: + UpdateResult_Texture2DImage(); + break; } - // - FRandomStream RotationGen(31337); - FCriticalSection RotationLock; - auto GetRandomRotation = [&RotationGen, &RotationLock]() { - RotationLock.Lock(); - double Angle = RotationGen.GetFraction() * FMathd::TwoPi; - RotationLock.Unlock(); - return Angle; - }; - - - auto OcclusionSampleFunction = [&](const FDetailPointSample& SampleData) - { - int32 DetailTriID = SampleData.DetailTriID; - if (DetailMesh.IsTriangle(DetailTriID)) - { - FIndex3i DetailTri = DetailMesh.GetTriangle(DetailTriID); - //FVector3d DetailTriNormal = DetailMesh.GetTriNormal(DetailTriID); - FVector3d DetailTriNormal; - DetailNormalOverlay->GetTriBaryInterpolate(DetailTriID, &SampleData.DetailBaryCoords[0], &DetailTriNormal.X); - DetailTriNormal.Normalize(); - - FVector3d DetailBaryCoords = SampleData.DetailBaryCoords; - FVector3d DetailPos = DetailMesh.GetTriBaryPoint(DetailTriID, DetailBaryCoords.X, DetailBaryCoords.Y, DetailBaryCoords.Z); - DetailPos += 10.0f * FMathf::ZeroTolerance * DetailTriNormal; - FFrame3d SurfaceFrame(DetailPos, DetailTriNormal); - - double RotationAngle = GetRandomRotation(); - SurfaceFrame.Rotate(FQuaterniond(SurfaceFrame.Z(), RotationAngle, false)); - - IMeshSpatial::FQueryOptions QueryOptions; - QueryOptions.MaxDistance = OcclusionMapSettings.MaxDistance; - - double AccumOcclusion = 0; - double TotalWeight = 0; - for (FVector3d SphereDir : RayDirections) - { - FRay3d OcclusionRay(DetailPos, SurfaceFrame.FromFrameVector(SphereDir)); - check(OcclusionRay.Direction.Dot(DetailTriNormal) > 0); - - // Have weight of point fall off as it becomes more coplanar with face. - // This reduces faceting artifacts that we would otherwise see because geometry does not vary smoothly - double PointWeight = 1.0; - double BiasDot = OcclusionRay.Direction.Dot(DetailTriNormal); - if (BiasDot < BiasDotThreshold) - { - PointWeight = FMathd::Lerp(0.0, 1.0, FMathd::Clamp(BiasDot/BiasDotThreshold,0.0,1.0)); - PointWeight *= PointWeight; - } - TotalWeight += PointWeight; - - if (DetailSpatial.TestAnyHitTriangle(OcclusionRay, QueryOptions)) - { - AccumOcclusion += PointWeight; - } - } - - AccumOcclusion = (TotalWeight > 0.0001) ? (AccumOcclusion / TotalWeight) : 0.0; - return AccumOcclusion; - } - return 0.0; - }; - - // make UV-space version of mesh - FDynamicMesh3 FlatMesh(EMeshComponents::FaceGroups); - for (int32 tid : Mesh->TriangleIndicesItr()) - { - if (UVOverlay->IsSetTriangle(tid)) - { - FVector2f A, B, C; - UVOverlay->GetTriElements(tid, A, B, C); - int32 VertA = FlatMesh.AppendVertex(FVector3d(A.X, A.Y, 0)); - int32 VertB = FlatMesh.AppendVertex(FVector3d(B.X, B.Y, 0)); - int32 VertC = FlatMesh.AppendVertex(FVector3d(C.X, C.Y, 0)); - int32 NewTriID = FlatMesh.AppendTriangle(VertA, VertB, VertC, tid); - } - } - - // calculate occupancy map - FImageOccupancyMap Occupancy; - Occupancy.Initialize(Dimensions); - Occupancy.ComputeFromUVSpaceMesh(FlatMesh, [&](int32 TriangleID) { return FlatMesh.GetTriangleGroup(TriangleID); } ); - - // initialize the textures - //FTexture2DBuilder ColorBuilder; - //ColorBuilder.Initialize(FTexture2DBuilder::ETextureType::Color, Dimensions); - FTexture2DBuilder OcclusionBuilder; - if (bBakeOcclusion) - { - OcclusionBuilder.Initialize(FTexture2DBuilder::ETextureType::AmbientOcclusion, Dimensions); - } - FTexture2DBuilder NormalsBuilder; - if (bBakeNormals) - { - NormalsBuilder.Initialize(FTexture2DBuilder::ETextureType::NormalMap, Dimensions); - } - - // calculate interior texels - ParallelFor(Dimensions.Num(), [&](int64 LinearIdx) - { - if (Occupancy.IsInterior(LinearIdx) == false) - { - return; - } - - FVector2d UVPosition = (FVector2d)Occupancy.TexelQueryUV[LinearIdx]; - int32 UVTriangleID = Occupancy.TexelQueryTriangle[LinearIdx]; - - FDetailPointSample DetailInfo; - DetailMeshSampler.SampleUV(UVTriangleID, UVPosition, DetailInfo); - - // calculate normal - if (bBakeNormals) - { - FVector3f RelativeDetailNormal = NormalSampleFunction(DetailInfo); - FVector3f MapNormal = (RelativeDetailNormal + FVector3f::One()) * 0.5; - NormalsBuilder.SetTexel(LinearIdx, ((FLinearColor)MapNormal).ToFColor(false)); - } - - // todo: calculate color? - //ColorBuilder.SetTexel(LinearIdx, FColor(128, 128, 128)); - //ColorBuilder.SetTexel(LinearIdx, FColor(192, 192, 192)); - - // calculate occlusion - if (bBakeOcclusion) - { - double Occlusion = OcclusionSampleFunction(DetailInfo); - FVector3d OcclusionColor = FMathd::Clamp(1.0 - Occlusion, 0.0, 1.0) * FVector3d::One(); - OcclusionBuilder.SetTexel(LinearIdx, ((FLinearColor)OcclusionColor).ToFColor(false)); - } - }); - - - // fill in the gutter texels - for (int64 k = 0; k < Occupancy.GutterTexels.Num(); k++) - { - TPair GutterTexel = Occupancy.GutterTexels[k]; - //ColorBuilder.CopyTexel(GutterTexel.Value, GutterTexel.Key); - if (bBakeNormals) - { - NormalsBuilder.CopyTexel(GutterTexel.Value, GutterTexel.Key); - //NormalsBuilder.ClearTexel(GutterTexel.Key); - } - if (bBakeOcclusion) - { - OcclusionBuilder.CopyTexel(GutterTexel.Value, GutterTexel.Key); - } - } - - // apply AO blur pass - if (bBakeOcclusion && OcclusionMapSettings.BlurRadius > 0.01) - { - TDiscreteKernel2f BlurKernel2d; - TGaussian2f::MakeKernelFromRadius(OcclusionMapSettings.BlurRadius, BlurKernel2d); - TArray AOBlurBuffer; - Occupancy.ParallelProcessingPass( - [&](int64 Index) { return 0.0f; }, - [&](int64 LinearIdx, float Weight, float& CurValue) { CurValue += Weight * (float)OcclusionBuilder.GetTexel(LinearIdx).R; }, - [&](int64 LinearIdx, float WeightSum, float& CurValue) { CurValue /= WeightSum; }, - [&](int64 LinearIdx, float& CurValue) { uint8 Val = FMath::Clamp(CurValue, 0.0f, 255.0f); OcclusionBuilder.SetTexel(LinearIdx, FColor(Val,Val,Val)); }, - [&](const FVector2i& TexelOffset) { return BlurKernel2d.EvaluateFromOffset(TexelOffset); }, - BlurKernel2d.IntRadius, - AOBlurBuffer); - } - - - // Unlock the textures and update them - //ColorBuilder.Commit(); - if (bBakeNormals) - { - NormalsBuilder.Commit(false); - CachedNormalMap = NormalsBuilder.GetTexture2D(); - CachedNormalMapSettings = NormalMapSettings; - } - - if (bBakeOcclusion) - { - OcclusionBuilder.Commit(false); - CachedOcclusionMap = OcclusionBuilder.GetTexture2D(); - CachedOcclusionMapSettings = OcclusionMapSettings; - } UpdateVisualization(); GetToolManager()->PostInvalidation(); @@ -604,64 +416,440 @@ void UBakeMeshAttributeMapsTool::UpdateResult() +void UBakeMeshAttributeMapsTool::UpdateResult_Normal() +{ + check(BakeCache->IsCacheValid()); + + int32 ImageSize = (int32)Settings->Resolution; + FImageDimensions Dimensions(ImageSize, ImageSize); + + FNormalMapSettings NormalMapSettings; + NormalMapSettings.Dimensions = Dimensions; + + if (!(CachedNormalMapSettings == NormalMapSettings)) + { + FMeshNormalMapBaker NormalBaker; + NormalBaker.SetCache(BakeCache.Get()); + NormalBaker.BaseMeshTangents = BaseMeshTangents.Get(); + NormalBaker.Bake(); + + FTexture2DBuilder TextureBuilder; + TextureBuilder.Initialize(FTexture2DBuilder::ETextureType::NormalMap, Dimensions); + TextureBuilder.Copy(*NormalBaker.GetResult()); + TextureBuilder.Commit(false); + CachedNormalMap = TextureBuilder.GetTexture2D(); + CachedNormalMapSettings = NormalMapSettings; + } +} + + +void UBakeMeshAttributeMapsTool::UpdateResult_Occlusion() +{ + check(BakeCache->IsCacheValid()); + + int32 ImageSize = (int32)Settings->Resolution; + FImageDimensions Dimensions(ImageSize, ImageSize); + + FOcclusionMapSettings OcclusionMapSettings; + OcclusionMapSettings.Dimensions = Dimensions; + OcclusionMapSettings.MaxDistance = (OcclusionMapProps->MaxDistance == 0) ? TNumericLimits::Max() : OcclusionMapProps->MaxDistance; + OcclusionMapSettings.OcclusionRays = OcclusionMapProps->OcclusionRays; + OcclusionMapSettings.BlurRadius = (OcclusionMapProps->bGaussianBlur) ? OcclusionMapProps->BlurRadius : 0.0; + OcclusionMapSettings.BiasAngle = OcclusionMapProps->BiasAngle; + + if ( !(CachedOcclusionMapSettings == OcclusionMapSettings) ) + { + FMeshOcclusionMapBaker OcclusionBaker; + OcclusionBaker.SetCache(BakeCache.Get()); + OcclusionBaker.NumOcclusionRays = OcclusionMapSettings.OcclusionRays; + OcclusionBaker.BlurRadius = OcclusionMapSettings.BlurRadius; + OcclusionBaker.BiasAngleDeg = OcclusionMapSettings.BiasAngle; + OcclusionBaker.MaxDistance = OcclusionMapSettings.MaxDistance; + OcclusionBaker.Bake(); + + FTexture2DBuilder TextureBuilder; + TextureBuilder.Initialize(FTexture2DBuilder::ETextureType::AmbientOcclusion, Dimensions); + TextureBuilder.Copy(*OcclusionBaker.GetResult()); + TextureBuilder.Commit(false); + CachedOcclusionMap = TextureBuilder.GetTexture2D(); + CachedOcclusionMapSettings = OcclusionMapSettings; + } +} + + + +void UBakeMeshAttributeMapsTool::UpdateResult_Curvature() +{ + check(BakeCache->IsCacheValid()); + + int32 ImageSize = (int32)Settings->Resolution; + FImageDimensions Dimensions(ImageSize, ImageSize); + + FCurvatureMapSettings CurvatureMapSettings; + CurvatureMapSettings.Dimensions = Dimensions; + CurvatureMapSettings.RangeMultiplier = CurvatureMapProps->RangeMultiplier; + CurvatureMapSettings.MinRangeMultiplier = CurvatureMapProps->MinRangeMultiplier; + switch (CurvatureMapProps->CurvatureType) + { + default: + case EBakedCurvatureTypeMode::MeanAverage: + CurvatureMapSettings.CurvatureType = (int32)FMeshCurvatureMapBaker::ECurvatureType::Mean; + break; + case EBakedCurvatureTypeMode::Gaussian: + CurvatureMapSettings.CurvatureType = (int32)FMeshCurvatureMapBaker::ECurvatureType::Gaussian; + break; + case EBakedCurvatureTypeMode::Max: + CurvatureMapSettings.CurvatureType = (int32)FMeshCurvatureMapBaker::ECurvatureType::MaxPrincipal; + break; + case EBakedCurvatureTypeMode::Min: + CurvatureMapSettings.CurvatureType = (int32)FMeshCurvatureMapBaker::ECurvatureType::MinPrincipal; + break; + } + switch (CurvatureMapProps->ColorMode) + { + default: + case EBakedCurvatureColorMode::Grayscale: + CurvatureMapSettings.ColorMode = (int32)FMeshCurvatureMapBaker::EColorMode::BlackGrayWhite; + break; + case EBakedCurvatureColorMode::RedBlue: + CurvatureMapSettings.ColorMode = (int32)FMeshCurvatureMapBaker::EColorMode::RedBlue; + break; + case EBakedCurvatureColorMode::RedGreenBlue: + CurvatureMapSettings.ColorMode = (int32)FMeshCurvatureMapBaker::EColorMode::RedGreenBlue; + break; + } + switch (CurvatureMapProps->Clamping) + { + default: + case EBakedCurvatureClampMode::None: + CurvatureMapSettings.ClampMode = (int32)FMeshCurvatureMapBaker::EClampMode::FullRange; + break; + case EBakedCurvatureClampMode::Positive: + CurvatureMapSettings.ClampMode = (int32)FMeshCurvatureMapBaker::EClampMode::Positive; + break; + case EBakedCurvatureClampMode::Negative: + CurvatureMapSettings.ClampMode = (int32)FMeshCurvatureMapBaker::EClampMode::Negative; + break; + } + CurvatureMapSettings.BlurRadius = (CurvatureMapProps->bGaussianBlur) ? CurvatureMapProps->BlurRadius : 0.0; + + if (!(CachedCurvatureMapSettings == CurvatureMapSettings)) + { + FMeshCurvatureMapBaker CurvatureBaker; + CurvatureBaker.SetCache(BakeCache.Get()); + CurvatureBaker.RangeScale = FMathd::Clamp(CurvatureMapSettings.RangeMultiplier, 0.0001, 1000.0); + CurvatureBaker.MinRangeScale = FMathd::Clamp(CurvatureMapSettings.MinRangeMultiplier, 0.0, 1.0); + CurvatureBaker.UseCurvatureType = (FMeshCurvatureMapBaker::ECurvatureType)CurvatureMapSettings.CurvatureType; + CurvatureBaker.UseColorMode = (FMeshCurvatureMapBaker::EColorMode)CurvatureMapSettings.ColorMode; + CurvatureBaker.UseClampMode = (FMeshCurvatureMapBaker::EClampMode)CurvatureMapSettings.ClampMode; + CurvatureBaker.BlurRadius = CurvatureMapSettings.BlurRadius; + CurvatureBaker.Bake(); + + FTexture2DBuilder TextureBuilder; + TextureBuilder.Initialize(FTexture2DBuilder::ETextureType::Color, Dimensions); + TextureBuilder.Copy(*CurvatureBaker.GetResult()); + TextureBuilder.Commit(false); + CachedCurvatureMap = TextureBuilder.GetTexture2D(); + CachedCurvatureMapSettings = CurvatureMapSettings; + } +} + + + +void UBakeMeshAttributeMapsTool::UpdateResult_MeshProperty() +{ + check(BakeCache->IsCacheValid()); + + int32 ImageSize = (int32)Settings->Resolution; + FImageDimensions Dimensions(ImageSize, ImageSize); + + FMeshPropertyMapSettings MeshPropertyMapSettings; + MeshPropertyMapSettings.Dimensions = Dimensions; + switch (Settings->MapType) + { + case EBakeMapType::NormalImage: + MeshPropertyMapSettings.PropertyTypeIndex = (int32)EMeshPropertyBakeType::Normal; + break; + case EBakeMapType::FaceNormalImage: + MeshPropertyMapSettings.PropertyTypeIndex = (int32)EMeshPropertyBakeType::FacetNormal; + break; + case EBakeMapType::PositionImage: + MeshPropertyMapSettings.PropertyTypeIndex = (int32)EMeshPropertyBakeType::Position; + break; + default: + check(false); // should not be possible! + } + //MeshPropertyMapSettings.BlurRadius = (CurvatureMapProps->bGaussianBlur) ? CurvatureMapProps->BlurRadius : 0.0; + + if (!(CachedMeshPropertyMapSettings == MeshPropertyMapSettings)) + { + FMeshPropertyMapBaker MeshPropertyBaker; + MeshPropertyBaker.SetCache(BakeCache.Get()); + MeshPropertyBaker.Property = (EMeshPropertyBakeType)MeshPropertyMapSettings.PropertyTypeIndex; + //MeshPropertyBaker.BlurRadius = CurvatureMapSettings.BlurRadius; + MeshPropertyBaker.Bake(); + + FTexture2DBuilder TextureBuilder; + TextureBuilder.Initialize(FTexture2DBuilder::ETextureType::Color, Dimensions); + TextureBuilder.Copy(*MeshPropertyBaker.GetResult()); + TextureBuilder.Commit(false); + CachedMeshPropertyMap = TextureBuilder.GetTexture2D(); + CachedMeshPropertyMapSettings = MeshPropertyMapSettings; + } +} + + + + + + + + +class FTempTextureAccess +{ +public: + FTempTextureAccess(UTexture2D* DisplacementMap) + : DisplacementMap(DisplacementMap) + { + check(DisplacementMap); + OldCompressionSettings = DisplacementMap->CompressionSettings; + bOldSRGB = DisplacementMap->SRGB; +#if WITH_EDITOR + OldMipGenSettings = DisplacementMap->MipGenSettings; +#endif + DisplacementMap->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap; + DisplacementMap->SRGB = false; +#if WITH_EDITOR + DisplacementMap->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; +#endif + DisplacementMap->UpdateResource(); + + FormattedImageData = reinterpret_cast(DisplacementMap->PlatformData->Mips[0].BulkData.LockReadOnly()); + } + FTempTextureAccess(const FTempTextureAccess&) = delete; + FTempTextureAccess(FTempTextureAccess&&) = delete; + void operator=(const FTempTextureAccess&) = delete; + void operator=(FTempTextureAccess&&) = delete; + + ~FTempTextureAccess() + { + DisplacementMap->PlatformData->Mips[0].BulkData.Unlock(); + + DisplacementMap->CompressionSettings = OldCompressionSettings; + DisplacementMap->SRGB = bOldSRGB; +#if WITH_EDITOR + DisplacementMap->MipGenSettings = OldMipGenSettings; +#endif + + DisplacementMap->UpdateResource(); + } + + bool HasData() const + { + return FormattedImageData != nullptr; + } + const FColor* GetData() const + { + return FormattedImageData; + } + + FImageDimensions GetDimensions() const + { + int32 Width = DisplacementMap->PlatformData->Mips[0].SizeX; + int32 Height = DisplacementMap->PlatformData->Mips[0].SizeY; + return FImageDimensions(Width, Height); + } + + + bool CopyTo(TImageBuilder& DestImage) const + { + if (!HasData()) return false; + + FImageDimensions TextureDimensions = GetDimensions(); + if (ensure(DestImage.GetDimensions() == TextureDimensions) == false) + { + return false; + } + + int64 Num = TextureDimensions.Num(); + for (int32 i = 0; i < Num; ++i) + { + FColor ByteColor = FormattedImageData[i]; + FLinearColor FloatColor(ByteColor); + DestImage.SetPixel(i, FVector4f(FloatColor)); + } + return true; + } + +private: + UTexture2D* DisplacementMap{ nullptr }; + TextureCompressionSettings OldCompressionSettings{}; + TextureMipGenSettings OldMipGenSettings{}; + bool bOldSRGB{ false }; + const FColor* FormattedImageData{ nullptr }; +}; + + + + + + + +void UBakeMeshAttributeMapsTool::UpdateResult_Texture2DImage() +{ + check(BakeCache->IsCacheValid()); + + int32 ImageSize = (int32)Settings->Resolution; + FImageDimensions Dimensions(ImageSize, ImageSize); + + FTexture2DImageSettings NewSettings; + NewSettings.Dimensions = Dimensions; + NewSettings.UVLayer = 0; + + const FDynamicMeshUVOverlay* UVOverlay = DetailMesh->Attributes()->GetUVLayer(NewSettings.UVLayer); + if (UVOverlay == nullptr) + { + GetToolManager()->DisplayMessage(LOCTEXT("InvalidUVWarning", "The Source Mesh does not have the selected UV layer"), EToolMessageLevel::UserWarning); + return; + } + + if (Texture2DProps->SourceTexture == nullptr) + { + GetToolManager()->DisplayMessage(LOCTEXT("InvalidTextureWarning", "The Source Texture is not valid"), EToolMessageLevel::UserWarning); + return; + } + + + TImageBuilder TextureImage; + { + FTempTextureAccess TextureAccess(Texture2DProps->SourceTexture); + TextureImage.SetDimensions(TextureAccess.GetDimensions()); + if (!TextureAccess.CopyTo(TextureImage)) + { + GetToolManager()->DisplayMessage(LOCTEXT("CannotReadTextureWarning", "Cannot read from the source texture"), EToolMessageLevel::UserWarning); + return; + } + } + + if (!(CachedTexture2DImageSettings == NewSettings)) + { + FMeshResampleImageBaker Baker; + Baker.SetCache(BakeCache.Get()); + Baker.DetailUVOverlay = UVOverlay; + Baker.SampleFunction = [&](FVector2d UVCoord) { + return TextureImage.BilinearSampleUV(UVCoord, FVector4f(0,0,0,1)); + }; + Baker.Bake(); + + FTexture2DBuilder TextureBuilder; + TextureBuilder.Initialize(FTexture2DBuilder::ETextureType::Color, Dimensions); + TextureBuilder.Copy(*Baker.GetResult(), true); + TextureBuilder.Commit(false); + CachedTexture2DImageMap = TextureBuilder.GetTexture2D(); + CachedTexture2DImageSettings = NewSettings; + } +} + + + + + void UBakeMeshAttributeMapsTool::UpdateVisualization() { - //if (BakeColor != nullptr) - //{ - // PreviewMaterial->SetTextureParameterValue(TEXT("ColorMap"), BakeColor); - //} - - if (Settings->bNormalMap) + switch (Settings->MapType) { - NormalMapProps->Result = CachedNormalMap; - PreviewMaterial->SetTextureParameterValue(TEXT("NormalMap"), CachedNormalMap); - } - else - { - NormalMapProps->Result = nullptr; + default: + Settings->Result = nullptr; + PreviewMaterial->SetTextureParameterValue(TEXT("NormalMap"), EmptyNormalMap); + PreviewMaterial->SetTextureParameterValue(TEXT("OcclusionMap"), EmptyColorMapWhite); + PreviewMaterial->SetTextureParameterValue(TEXT("ColorMap"), EmptyColorMapWhite); + break; + case EBakeMapType::TangentSpaceNormalMap: + Settings->Result = CachedNormalMap; + PreviewMaterial->SetTextureParameterValue(TEXT("NormalMap"), CachedNormalMap); + PreviewMaterial->SetTextureParameterValue(TEXT("OcclusionMap"), EmptyColorMapWhite); + PreviewMaterial->SetTextureParameterValue(TEXT("ColorMap"), EmptyColorMapWhite); + break; + case EBakeMapType::AmbientOcclusion: + Settings->Result = CachedOcclusionMap; PreviewMaterial->SetTextureParameterValue(TEXT("NormalMap"), EmptyNormalMap); - } - - if (Settings->bAmbientOcclusionMap) - { - OcclusionMapProps->Result = CachedOcclusionMap; PreviewMaterial->SetTextureParameterValue(TEXT("OcclusionMap"), CachedOcclusionMap); - PreviewMaterial->SetTextureParameterValue(TEXT("ColorMap"), CachedOcclusionMap); + PreviewMaterial->SetTextureParameterValue(TEXT("ColorMap"), EmptyColorMapWhite); + break; + case EBakeMapType::Curvature: + Settings->Result = CachedCurvatureMap; + PreviewMaterial->SetTextureParameterValue(TEXT("NormalMap"), EmptyNormalMap); + PreviewMaterial->SetTextureParameterValue(TEXT("OcclusionMap"), EmptyColorMapWhite); + PreviewMaterial->SetTextureParameterValue(TEXT("ColorMap"), CachedCurvatureMap); + break; + + case EBakeMapType::NormalImage: + case EBakeMapType::FaceNormalImage: + case EBakeMapType::PositionImage: + Settings->Result = CachedMeshPropertyMap; + PreviewMaterial->SetTextureParameterValue(TEXT("NormalMap"), EmptyNormalMap); + PreviewMaterial->SetTextureParameterValue(TEXT("OcclusionMap"), EmptyColorMapWhite); + PreviewMaterial->SetTextureParameterValue(TEXT("ColorMap"), CachedMeshPropertyMap); + break; + + case EBakeMapType::Texture2DImage: + Settings->Result = CachedTexture2DImageMap; + PreviewMaterial->SetTextureParameterValue(TEXT("NormalMap"), EmptyNormalMap); + PreviewMaterial->SetTextureParameterValue(TEXT("OcclusionMap"), EmptyColorMapWhite); + PreviewMaterial->SetTextureParameterValue(TEXT("ColorMap"), CachedTexture2DImageMap); + break; } - else +} + + + +void UBakeMeshAttributeMapsTool::UpdateOnModeChange() +{ + SetToolPropertySourceEnabled(NormalMapProps, false); + SetToolPropertySourceEnabled(OcclusionMapProps, false); + SetToolPropertySourceEnabled(CurvatureMapProps, false); + SetToolPropertySourceEnabled(Texture2DProps, false); + + switch (Settings->MapType) { - OcclusionMapProps->Result = nullptr; - PreviewMaterial->SetTextureParameterValue(TEXT("OcclusionMap"), EmptyOcclusionMap); - PreviewMaterial->SetTextureParameterValue(TEXT("ColorMap"), EmptyOcclusionMap); + case EBakeMapType::TangentSpaceNormalMap: + SetToolPropertySourceEnabled(NormalMapProps, true); + break; + case EBakeMapType::AmbientOcclusion: + SetToolPropertySourceEnabled(OcclusionMapProps, true); + break; + case EBakeMapType::Curvature: + SetToolPropertySourceEnabled(CurvatureMapProps, true); + break; + case EBakeMapType::Texture2DImage: + SetToolPropertySourceEnabled(Texture2DProps, true); + break; } + } -bool UBakeMeshAttributeMapsTool::HasAccept() const -{ - return true; -} - -bool UBakeMeshAttributeMapsTool::CanAccept() const -{ - return true; -} - - void UBakeMeshAttributeMapsTool::InitializeEmptyMaps() { - FTexture2DBuilder OcclusionBuilder; - OcclusionBuilder.Initialize(FTexture2DBuilder::ETextureType::AmbientOcclusion, FImageDimensions(16,16)); - OcclusionBuilder.Commit(false); - EmptyOcclusionMap = OcclusionBuilder.GetTexture2D(); - FTexture2DBuilder NormalsBuilder; NormalsBuilder.Initialize(FTexture2DBuilder::ETextureType::NormalMap, FImageDimensions(16, 16)); NormalsBuilder.Commit(false); EmptyNormalMap = NormalsBuilder.GetTexture2D(); + + FTexture2DBuilder ColorBuilderBlack; + ColorBuilderBlack.Initialize(FTexture2DBuilder::ETextureType::Color, FImageDimensions(16, 16)); + ColorBuilderBlack.Clear(FColor(0,0,0)); + ColorBuilderBlack.Commit(false); + EmptyColorMapBlack = ColorBuilderBlack.GetTexture2D(); + + FTexture2DBuilder ColorBuilderWhite; + ColorBuilderWhite.Initialize(FTexture2DBuilder::ETextureType::Color, FImageDimensions(16, 16)); + ColorBuilderWhite.Clear(FColor::White); + ColorBuilderWhite.Commit(false); + EmptyColorMapWhite = ColorBuilderWhite.GetTexture2D(); } diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/DeformMeshPolygonsTool.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/DeformMeshPolygonsTool.cpp index 807956527f2e..7f0b369ff0a0 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/DeformMeshPolygonsTool.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/DeformMeshPolygonsTool.cpp @@ -1036,11 +1036,10 @@ bool UDeformMeshPolygonsTool::HitTest(const FRay& WorldRay, FHitResult& OutHit) Transform.InverseTransformVector(WorldRay.Direction)); LocalRay.Direction.Normalize(); - TopoSelector.UpdateEnableFlags(TransformProps->bSelectFaces, TransformProps->bSelectEdges, - TransformProps->bSelectVertices); FGroupTopologySelection Selection; FVector3d LocalPosition, LocalNormal; - if (TopoSelector.FindSelectedElement(LocalRay, Selection, LocalPosition, LocalNormal) == false) + FGroupTopologySelector::FSelectionSettings TopoSelectorSettings = GetTopoSelectorSettings(); + if (TopoSelector.FindSelectedElement(TopoSelectorSettings, LocalRay, Selection, LocalPosition, LocalNormal) == false) { return false; } @@ -1085,11 +1084,10 @@ void UDeformMeshPolygonsTool::OnBeginDrag(const FRay& WorldRay) HilightSelection.Clear(); - TopoSelector.UpdateEnableFlags(TransformProps->bSelectFaces, TransformProps->bSelectEdges, - TransformProps->bSelectVertices); FGroupTopologySelection Selection; FVector3d LocalPosition, LocalNormal; - bool bHit = TopoSelector.FindSelectedElement(LocalRay, Selection, LocalPosition, LocalNormal); + FGroupTopologySelector::FSelectionSettings TopoSelectorSettings = GetTopoSelectorSettings(); + bool bHit = TopoSelector.FindSelectedElement(TopoSelectorSettings, LocalRay, Selection, LocalPosition, LocalNormal); if (bHit == false) { @@ -1300,10 +1298,9 @@ bool UDeformMeshPolygonsTool::OnUpdateHover(const FInputDeviceRay& DevicePos) LocalRay.Direction.Normalize(); HilightSelection.Clear(); - TopoSelector.UpdateEnableFlags(TransformProps->bSelectFaces, TransformProps->bSelectEdges, - TransformProps->bSelectVertices); FVector3d LocalPosition, LocalNormal; - bool bHit = TopoSelector.FindSelectedElement(LocalRay, HilightSelection, LocalPosition, LocalNormal); + FGroupTopologySelector::FSelectionSettings TopoSelectorSettings = GetTopoSelectorSettings(); + bool bHit = TopoSelector.FindSelectedElement(TopoSelectorSettings, LocalRay, HilightSelection, LocalPosition, LocalNormal); if (bHit) { @@ -1634,6 +1631,15 @@ void UDeformMeshPolygonsTool::Render(IToolsContextRenderAPI* RenderAPI) void UDeformMeshPolygonsTool::OnPropertyModified(UObject* PropertySet, FProperty* Property) {} +FGroupTopologySelector::FSelectionSettings UDeformMeshPolygonsTool::GetTopoSelectorSettings() +{ + FGroupTopologySelector::FSelectionSettings TopoSelectorSettings; + TopoSelectorSettings.bEnableFaceHits = TransformProps->bSelectFaces; + TopoSelectorSettings.bEnableEdgeHits = TransformProps->bSelectEdges; + TopoSelectorSettings.bEnableCornerHits = TransformProps->bSelectVertices; + + return TopoSelectorSettings; +} // // Change Tracking diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/DrawAndRevolveTool.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/DrawAndRevolveTool.cpp index 997db00c1580..cf1f545bf64c 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/DrawAndRevolveTool.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/DrawAndRevolveTool.cpp @@ -9,8 +9,8 @@ #include "CompositionOps/CurveSweepOp.h" #include "Generators/SweepGenerator.h" #include "InteractiveToolManager.h" // To use SceneState.ToolManager -#include "Mechanics/CollectSurfacePathMechanic.h" #include "Mechanics/ConstructionPlaneMechanic.h" +#include "Mechanics/CurveControlPointsMechanic.h" #include "Properties/MeshMaterialProperties.h" #include "Selection/ToolSelectionUtil.h" #include "ToolSceneQueriesUtil.h" @@ -18,11 +18,20 @@ #define LOCTEXT_NAMESPACE "UDrawAndRevolveTool" + +const FText InitializationModeMessage = LOCTEXT("CurveInitialization", "Draw and a profile curve and it will be revolved around the purple draw plane axis. " + "Ctrl+click repositions draw plane and axis. The curve is ended by clicking the end again or connecting to its start. Holding shift toggles snapping to " + "be opposite the EnableSnapping setting."); +const FText EditModeMessage = LOCTEXT("CurveEditing", "Click points to select them, Shift+click to add/remove points to selection. Ctrl+click a segment " + "to add a point, or select an endpoint and Ctrl+click somewhere on the plane to add to the ends. Backspace deletes selected points. Holding Shift " + "toggles snapping to be opposite the EnableSnapping setting."); + + // Tool builder bool UDrawAndRevolveToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const { - return (this->AssetAPI != nullptr);; + return (this->AssetAPI != nullptr); } UInteractiveTool* UDrawAndRevolveToolBuilder::BuildTool(const FToolBuilderState& SceneState) const @@ -42,25 +51,22 @@ TUniquePtr URevolveOperatorFactory::MakeNewOperator() TUniquePtr CurveSweepOp = MakeUnique(); // Assemble profile curve - CurveSweepOp->ProfileCurve.Reserve(RevolveTool->DrawProfileCurveMechanic->HitPath.Num() + 2); // extra space for top/bottom caps - for (FFrame3d Frame : RevolveTool->DrawProfileCurveMechanic->HitPath) - { - CurveSweepOp->ProfileCurve.Add(Frame.Origin); - } - CurveSweepOp->bProfileCurveIsClosed = RevolveTool->DrawProfileCurveMechanic->LoopWasClosed(); + CurveSweepOp->ProfileCurve.Reserve(RevolveTool->ControlPointsMechanic->GetNumPoints() + 2); // extra space for top/bottom caps + RevolveTool->ControlPointsMechanic->ExtractPointPositions(CurveSweepOp->ProfileCurve); + CurveSweepOp->bProfileCurveIsClosed = RevolveTool->ControlPointsMechanic->GetIsLoop(); // If we are capping the top and bottom, we just add a couple extra vertices and mark the curve as being closed if (!CurveSweepOp->bProfileCurveIsClosed && RevolveTool->Settings->bConnectOpenProfileToAxis) { // Project first and last points onto the revolution axis. - double DistanceAlongAxis = RevolveTool->RevolutionAxisDirection.Dot( - RevolveTool->DrawProfileCurveMechanic->HitPath.Last().Origin - RevolveTool->RevolutionAxisOrigin); + FVector3d FirstPoint = CurveSweepOp->ProfileCurve[0]; + FVector3d LastPoint = CurveSweepOp->ProfileCurve.Last(); + double DistanceAlongAxis = RevolveTool->RevolutionAxisDirection.Dot(LastPoint - RevolveTool->RevolutionAxisOrigin); FVector3d ProjectedPoint = RevolveTool->RevolutionAxisOrigin + (RevolveTool->RevolutionAxisDirection * DistanceAlongAxis); CurveSweepOp->ProfileCurve.Add(ProjectedPoint); - DistanceAlongAxis = RevolveTool->RevolutionAxisDirection.Dot( - RevolveTool->DrawProfileCurveMechanic->HitPath[0].Origin - RevolveTool->RevolutionAxisOrigin); + DistanceAlongAxis = RevolveTool->RevolutionAxisDirection.Dot(FirstPoint - RevolveTool->RevolutionAxisOrigin); ProjectedPoint = RevolveTool->RevolutionAxisOrigin + (RevolveTool->RevolutionAxisDirection * DistanceAlongAxis); CurveSweepOp->ProfileCurve.Add(ProjectedPoint); @@ -73,8 +79,29 @@ TUniquePtr URevolveOperatorFactory::MakeNewOperator() return CurveSweepOp; } + // Tool itself +void UDrawAndRevolveTool::RegisterActions(FInteractiveToolActionSet& ActionSet) +{ + ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 1, + TEXT("DeletePoint"), + LOCTEXT("DeletePointUIName", "Delete Point"), + LOCTEXT("DeletePointTooltip", "Delete currently selected point(s)"), + EModifierKey::None, EKeys::BackSpace, + [this]() { OnBackspacePress(); }); +} + +void UDrawAndRevolveTool::OnBackspacePress() +{ + // TODO: Someday we'd like the mechanic to register the action itself and not have to catch and forward + // it in the tool. + if (ControlPointsMechanic) + { + ControlPointsMechanic->DeleteSelectedPoints(); + } +} + bool UDrawAndRevolveTool::CanAccept() const { return Preview != nullptr && Preview->HaveValidResult(); @@ -84,67 +111,82 @@ void UDrawAndRevolveTool::Setup() { UInteractiveTool::Setup(); - GetToolManager()->DisplayMessage( - LOCTEXT("OnStartRevolveTool", "Draw and a profile curve and it will be revolved around the purple draw plane axis. " - "Ctrl+click repositions draw plane and axis. The curve is ended by clicking the end again or connecting to its start."), - EToolMessageLevel::UserNotification); + GetToolManager()->DisplayMessage(InitializationModeMessage, EToolMessageLevel::UserNotification); Settings = NewObject(this, TEXT("Revolve Tool Settings")); Settings->RestoreProperties(this); - Settings->AllowedToEditDrawPlane = 1; + Settings->bAllowedToEditDrawPlane = true; AddToolPropertySource(Settings); MaterialProperties = NewObject(this); AddToolPropertySource(MaterialProperties); MaterialProperties->RestoreProperties(this); - DrawProfileCurveMechanic = NewObject(this); - DrawProfileCurveMechanic->Setup(this); + ControlPointsMechanic = NewObject(this); + ControlPointsMechanic->Setup(this); + ControlPointsMechanic->SetWorld(TargetWorld); + ControlPointsMechanic->OnPointsChanged.AddLambda([this]() { + if (Preview) + { + Preview->InvalidateResult(); + } + Settings->bAllowedToEditDrawPlane = (ControlPointsMechanic->GetNumPoints() == 0); + }); + // This gets called when we enter/leave curve initialization mode + ControlPointsMechanic->OnModeChanged.AddLambda([this]() { + if (ControlPointsMechanic->IsInInteractiveIntialization()) + { + // We're back to initializing so hide the preview + if (Preview) + { + Preview->Cancel(); + Preview = nullptr; + } + GetToolManager()->DisplayMessage(InitializationModeMessage, EToolMessageLevel::UserNotification); + } + else + { + StartPreview(); + GetToolManager()->DisplayMessage(EditModeMessage, EToolMessageLevel::UserNotification); + } + }); + ControlPointsMechanic->SetSnappingEnabled(Settings->bEnableSnapping); UpdateRevolutionAxis(Settings->DrawPlaneAndAxis); - double SnapTolerance = ToolSceneQueriesUtil::GetDefaultVisualAngleSnapThreshD(); - DrawProfileCurveMechanic->SpatialSnapPointsFunc = [this, SnapTolerance](FVector3d Position1, FVector3d Position2) - { - //TODO: optimize for ortho - return ToolSceneQueriesUtil::PointSnapQuery(this->CameraState, Position1, Position2, SnapTolerance); - }; - DrawProfileCurveMechanic->SetDoubleClickOrCloseLoopMode(); - FFrame3d ProfileDrawPlane(Settings->DrawPlaneAndAxis); - DrawProfileCurveMechanic->InitializePlaneSurface(ProfileDrawPlane); - - // The click behavior forwards clicks to the DrawProfileCurveMechanic - USingleClickInputBehavior* ClickBehavior = NewObject(this); - ClickBehavior->Initialize(this); - AddInputBehavior(ClickBehavior); - - // The hover behavior forwards hover to DrawProfileCurveMechanic (for the preview point) - UMouseHoverBehavior* HoverBehavior = NewObject(this); - HoverBehavior->Initialize(this); - AddInputBehavior(HoverBehavior); - // The plane mechanic lets us update the plane in which we draw the profile curve, as long as we haven't // started adding points to it already. + FFrame3d ProfileDrawPlane(Settings->DrawPlaneAndAxis); PlaneMechanic = NewObject(this); PlaneMechanic->Setup(this); PlaneMechanic->Initialize(TargetWorld, ProfileDrawPlane); - PlaneMechanic->UpdateClickPriority(ClickBehavior->GetPriority().MakeHigher()); + PlaneMechanic->UpdateClickPriority(ControlPointsMechanic->ClickBehavior->GetPriority().MakeHigher()); PlaneMechanic->CanUpdatePlaneFunc = [this]() { - return DrawProfileCurveMechanic->HitPath.Num() == 0; + return ControlPointsMechanic->GetNumPoints() == 0; }; PlaneMechanic->OnPlaneChanged.AddLambda([this]() { Settings->DrawPlaneAndAxis = PlaneMechanic->Plane.ToFTransform(); - DrawProfileCurveMechanic->InitializePlaneSurface(PlaneMechanic->Plane); + if (ControlPointsMechanic) + { + ControlPointsMechanic->SetPlane(PlaneMechanic->Plane); + } UpdateRevolutionAxis(Settings->DrawPlaneAndAxis); }); PlaneMechanic->SetEnableGridSnaping(Settings->bSnapToWorldGrid); + + ControlPointsMechanic->SetPlane(PlaneMechanic->Plane); + ControlPointsMechanic->SetInteractiveInitialization(true); } void UDrawAndRevolveTool::UpdateRevolutionAxis(const FTransform& PlaneTransform) { RevolutionAxisOrigin = PlaneTransform.GetLocation(); RevolutionAxisDirection = PlaneTransform.GetRotation().GetAxisX(); + + const int32 AXIS_SNAP_TARGET_ID = 1; + ControlPointsMechanic->RemoveSnapLine(AXIS_SNAP_TARGET_ID); + ControlPointsMechanic->AddSnapLine(AXIS_SNAP_TARGET_ID, FLine3d(RevolutionAxisOrigin, RevolutionAxisDirection)); } void UDrawAndRevolveTool::Shutdown(EToolShutdownType ShutdownType) @@ -153,7 +195,7 @@ void UDrawAndRevolveTool::Shutdown(EToolShutdownType ShutdownType) MaterialProperties->SaveProperties(this); PlaneMechanic->Shutdown(); - DrawProfileCurveMechanic->Shutdown(); + ControlPointsMechanic->Shutdown(); if (Preview) { @@ -183,22 +225,6 @@ void UDrawAndRevolveTool::GenerateAsset(const FDynamicMeshOpResult& Result) GetToolManager()->EndUndoTransaction(); } -void UDrawAndRevolveTool::OnClicked(const FInputDeviceRay& ClickPos) -{ - if (!bProfileCurveComplete && DrawProfileCurveMechanic->TryAddPointFromRay(ClickPos.WorldRay)) - { - GetToolManager()->EmitObjectChange(this, MakeUnique(), LOCTEXT("ProfileCurvePoint", "Profile Curve Change")); - - Settings->AllowedToEditDrawPlane = (DrawProfileCurveMechanic->HitPath.Num() == 0); - - if (DrawProfileCurveMechanic->IsDone()) - { - bProfileCurveComplete = true; - StartPreview(); - } - } -} - void UDrawAndRevolveTool::StartPreview() { URevolveOperatorFactory* RevolveOpCreator = NewObject(); @@ -219,56 +245,18 @@ void UDrawAndRevolveTool::StartPreview() Preview->InvalidateResult(); } -FInputRayHit UDrawAndRevolveTool::IsHitByClick(const FInputDeviceRay& ClickPos) -{ - if (!bProfileCurveComplete) - { - FFrame3d HitPoint; - if (DrawProfileCurveMechanic->IsHitByRay(ClickPos.WorldRay, HitPoint)) - { - return FInputRayHit(FRay3d(ClickPos.WorldRay).Project(HitPoint.Origin)); - } - } - - // background capture, if nothing else is hit - return FInputRayHit(TNumericLimits::Max()); -} - -FInputRayHit UDrawAndRevolveTool::BeginHoverSequenceHitTest(const FInputDeviceRay& DevicePos) -{ - if (!bProfileCurveComplete) - { - FFrame3d HitPoint; - if (DrawProfileCurveMechanic->IsHitByRay(DevicePos.WorldRay, HitPoint)) - { - return FInputRayHit(FRay3d(DevicePos.WorldRay).Project(HitPoint.Origin)); - } - } - - // background capture, if nothing else is hit - return FInputRayHit(TNumericLimits::Max()); -} - -bool UDrawAndRevolveTool::OnUpdateHover(const FInputDeviceRay& DevicePos) -{ - if (!bProfileCurveComplete) - { - DrawProfileCurveMechanic->UpdatePreviewPoint(DevicePos.WorldRay); - } - return true; -} - void UDrawAndRevolveTool::OnPropertyModified(UObject* PropertySet, FProperty* Property) { if (Property && (Property->GetFName() == GET_MEMBER_NAME_CHECKED(URevolveToolProperties, DrawPlaneAndAxis))) { FFrame3d ProfileDrawPlane(Settings->DrawPlaneAndAxis); // Casting to FFrame3d - DrawProfileCurveMechanic->InitializePlaneSurface(ProfileDrawPlane); + ControlPointsMechanic->SetPlane(PlaneMechanic->Plane); PlaneMechanic->SetPlaneWithoutBroadcast(ProfileDrawPlane); UpdateRevolutionAxis(Settings->DrawPlaneAndAxis); } PlaneMechanic->SetEnableGridSnaping(Settings->bSnapToWorldGrid); + ControlPointsMechanic->SetSnappingEnabled(Settings->bEnableSnapping); if (Preview) { @@ -321,46 +309,10 @@ void UDrawAndRevolveTool::Render(IToolsContextRenderAPI* RenderAPI) AxisThickness, 0.0f, true); } - if (DrawProfileCurveMechanic != nullptr) + if (ControlPointsMechanic != nullptr) { - DrawProfileCurveMechanic->Render(RenderAPI); + ControlPointsMechanic->Render(RenderAPI); } } -// Undo support - -void UDrawAndRevolveTool::UndoCurrentOperation() -{ - if (bProfileCurveComplete) - { - // Curve is no longer complete - bProfileCurveComplete = false; - - // Cancel and destroy the preview mesh - if (Preview) - { - Preview->Cancel(); - Preview = nullptr; - } - } - - DrawProfileCurveMechanic->PopLastPoint(); - - Settings->AllowedToEditDrawPlane = (DrawProfileCurveMechanic->HitPath.Num() == 0); -} - -void FRevolveToolStateChange::Revert(UObject* Object) -{ - Cast(Object)->UndoCurrentOperation(); - bHaveDoneUndo = true; -} -bool FRevolveToolStateChange::HasExpired(UObject* Object) const -{ - return bHaveDoneUndo; -} -FString FRevolveToolStateChange::ToString() const -{ - return TEXT("FRevolveToolStateChange"); -} - #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/DrawPolygonTool.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/DrawPolygonTool.cpp index 17dff9bdc8dd..1e6c3aecb4d0 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/DrawPolygonTool.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/DrawPolygonTool.cpp @@ -478,8 +478,7 @@ bool UDrawPolygonTool::FindDrawPlaneHitPoint(const FInputDeviceRay& ClickPos, FV // if we found a scene snap point, add to snap set if (bIgnoreSnappingToggle || SnapProperties->bEnableSnapping == false) { - SnapEngine.ResetActiveSnap(); - SnapEngine.UpdatePointHistory(TArray()); + SnapEngine.Reset(); } else { diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/EdgeLoopInsertionTool.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/EdgeLoopInsertionTool.cpp new file mode 100644 index 000000000000..35fa0db9572b --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/EdgeLoopInsertionTool.cpp @@ -0,0 +1,522 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "EdgeLoopInsertionTool.h" + +#include "BaseBehaviors/SingleClickBehavior.h" +#include "BaseBehaviors/MouseHoverBehavior.h" +#include "CuttingOps/EdgeLoopInsertionOp.h" +#include "DynamicMeshToMeshDescription.h" +#include "InteractiveToolManager.h" +#include "MeshDescriptionToDynamicMesh.h" +#include "Operations/GroupEdgeInserter.h" +#include "ToolBuilderUtil.h" +#include "ToolSceneQueriesUtil.h" +#include "ToolSetupUtil.h" + +#define LOCTEXT_NAMESPACE "UEdgeLoopInsertionTool" + +bool UEdgeLoopInsertionToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const +{ + return AssetAPI != nullptr && ToolBuilderUtil::CountComponents(SceneState, CanMakeComponentTarget) == 1; +} + +UInteractiveTool* UEdgeLoopInsertionToolBuilder::BuildTool(const FToolBuilderState& SceneState) const +{ + UEdgeLoopInsertionTool* NewTool = NewObject(SceneState.ToolManager); + + UActorComponent* ActorComponent = ToolBuilderUtil::FindFirstComponent(SceneState, CanMakeComponentTarget); + UPrimitiveComponent* MeshComponent = Cast(ActorComponent); + check(MeshComponent != nullptr); + NewTool->SetSelection(MakeComponentTarget(MeshComponent)); + + NewTool->SetWorld(SceneState.World); + NewTool->SetAssetAPI(AssetAPI); + + return NewTool; +} + +TUniquePtr UEdgeLoopInsertionOperatorFactory::MakeNewOperator() +{ + TUniquePtr Op = MakeUnique(); + + Op->OriginalMesh = Tool->CurrentMesh; + Op->OriginalTopology = Tool->CurrentTopology; + Op->SetTransform(Tool->ComponentTarget->GetWorldTransform()); + + if (Tool->bShowingBaseMesh) + { + // Return op with no input lengths so that we get the original mesh back. + return Op; + } + + if (Tool->Settings->InsertionMode == EEdgeLoopInsertionMode::PlaneCut) + { + Op->Mode = FGroupEdgeInserter::EInsertionMode::PlaneCut; + } + else + { + Op->Mode = FGroupEdgeInserter::EInsertionMode::Retriangulate; + } + + Op->VertexTolerance = Tool->Settings->VertexTolerance; + + Op->GroupEdgeID = Tool->InputGroupEdgeID; + + Op->StartCornerID = Tool->Settings->bFlipOffsetDirection ? + Op->OriginalTopology->Edges[Op->GroupEdgeID].EndpointCorners.B + : Op->OriginalTopology->Edges[Op->GroupEdgeID].EndpointCorners.A; + + // Set up the inputs + if (Tool->Settings->PositionMode == EEdgeLoopPositioningMode::Even) + { + int32 NumLoops = Tool->Settings->NumLoops; + for (int32 i = 0; i < NumLoops; ++i) + { + Op->InputLengths.Add((i + 1.0) / (NumLoops + 1.0)); + } + } + else if (Tool->Settings->bInteractive) + { + Op->InputLengths.Add(Tool->InteractiveInputLength); + } + else if (Tool->Settings->PositionMode == EEdgeLoopPositioningMode::ProportionOffset) + { + Op->InputLengths.Add(Tool->Settings->ProportionOffset); + } + else + { + Op->InputLengths.Add(Tool->Settings->DistanceOffset); + } + + Op->bInputsAreProportions = (Tool->Settings->PositionMode == EEdgeLoopPositioningMode::Even + || Tool->Settings->PositionMode == EEdgeLoopPositioningMode::ProportionOffset); + + return Op; +} + +void UEdgeLoopInsertionTool::Setup() +{ + USingleSelectionTool::Setup(); + + if (!ComponentTarget) + { + return; + } + + GetToolManager()->DisplayMessage( + LOCTEXT("EdgeLoopInsertionToolDescription", + "Click an edge to insert an edge loop passing across that edge. " + "Edge loops follow a sequence of quad-like polygroups."), + EToolMessageLevel::UserNotification); + + // Initialize the mesh that we'll be operating on + CurrentMesh = MakeShared(); + FMeshDescriptionToDynamicMesh Converter; + Converter.Convert(ComponentTarget->GetMesh(), *CurrentMesh); + CurrentTopology = MakeShared(CurrentMesh.Get(), true); + MeshSpatial.SetMesh(CurrentMesh.Get(), true); + + // Set up properties + Settings = NewObject(this); + Settings->RestoreProperties(this); + AddToolPropertySource(Settings); + + // Register ourselves to receive clicks and hover + USingleClickInputBehavior* ClickBehavior = NewObject(); + ClickBehavior->Initialize(this); + AddInputBehavior(ClickBehavior); + UMouseHoverBehavior* HoverBehavior = NewObject(); + HoverBehavior->Initialize(this); + AddInputBehavior(HoverBehavior); + + SetupPreview(); + + // These draw the group edges and the loops to be inserted + ExistingEdgesRenderer.LineColor = FLinearColor::Red; + ExistingEdgesRenderer.LineThickness = 2.0; + PreviewEdgeRenderer.LineColor = FLinearColor::Green; + PreviewEdgeRenderer.LineThickness = 2.0; + + // Set up the topology selector, which we use to select the edges where we insert the loops + TopologySelector.Initialize(CurrentMesh.Get(), CurrentTopology.Get()); + TopologySelector.SetSpatialSource([this]() {return &MeshSpatial; }); + TopologySelector.PointsWithinToleranceTest = [this](const FVector3d& Position1, const FVector3d& Position2) { + FTransform3d Transform(ComponentTarget->GetWorldTransform()); + return ToolSceneQueriesUtil::PointSnapQuery(CameraState, + Transform.TransformPosition(Position1), Transform.TransformPosition(Position2)); + + }; + TopologySelectorSettings.bEnableEdgeHits = true; + TopologySelectorSettings.bEnableFaceHits = false; + TopologySelectorSettings.bEnableCornerHits = false; +} + +void UEdgeLoopInsertionTool::SetupPreview() +{ + UEdgeLoopInsertionOperatorFactory* OpFactory = NewObject(); + OpFactory->Tool = this; + + Preview = NewObject(OpFactory); + Preview->Setup(TargetWorld, OpFactory); + Preview->PreviewMesh->SetTangentsMode(EDynamicMeshTangentCalcType::AutoCalculated); + + FComponentMaterialSet MaterialSet; + ComponentTarget->GetMaterialSet(MaterialSet); + Preview->ConfigureMaterials(MaterialSet.Materials, ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager())); + + // Whenever we get a new result from the op, we need to extract the preview edges so that + // we can draw them if we want to. + Preview->OnOpCompleted.AddLambda([this](const FDynamicMeshOperator* UncastOp) { + const FEdgeLoopInsertionOp* Op = static_cast(UncastOp); + + bLastComputeSucceeded = Op->bSucceeded; + LatestOpTopologyResult.Reset(); + PreviewEdges.Reset(); + + if (bLastComputeSucceeded) + { + Op->GetLoopEdgeLocations(PreviewEdges); + LatestOpTopologyResult = Op->ResultTopology; + } + else + { + // Don't show the broken preview, since we wouldn't accept it on click. + Preview->PreviewMesh->UpdatePreview(CurrentMesh.Get()); + } + }); + + // Set initial preview to unprocessed mesh, so that things don't disappear initially + Preview->PreviewMesh->UpdatePreview(CurrentMesh.Get()); + Preview->PreviewMesh->SetTransform(ComponentTarget->GetWorldTransform()); + Preview->PreviewMesh->EnableWireframe(Settings->bWireframe); + Preview->SetVisibility(true); + ClearPreview(); + + ComponentTarget->SetOwnerVisibility(false); +} + +void UEdgeLoopInsertionTool::Shutdown(EToolShutdownType ShutdownType) +{ + Settings->SaveProperties(this); + Preview->Shutdown(); + ComponentTarget->SetOwnerVisibility(true); + CurrentMesh.Reset(); + ExpireChanges(); +} + +void UEdgeLoopInsertionTool::OnTick(float DeltaTime) +{ + if (Preview) + { + Preview->Tick(DeltaTime); + + if (bWaitingForInsertionCompletion && Preview->HaveValidResult()) + { + if (bLastComputeSucceeded) + { + // Apply the insertion + GetToolManager()->BeginUndoTransaction(LOCTEXT("EdgeLoopInsertionTransactionName", "Edge Loop Insertion")); + + GetToolManager()->EmitObjectChange(this, MakeUnique(CurrentChangeStamp, true), LOCTEXT("EdgeLoopInsertion", "Edge Loop Insertion")); + ComponentTarget->CommitMesh([this](const FPrimitiveComponentTarget::FCommitParams& CommitParams) + { + FDynamicMeshToMeshDescription Converter; + Converter.Convert(Preview->PreviewMesh->GetMesh(), *CommitParams.MeshDescription); + }); + GetToolManager()->EmitObjectChange(this, MakeUnique(CurrentChangeStamp, false), LOCTEXT("EdgeLoopInsertion", "Edge Loop Insertion")); + + GetToolManager()->EndUndoTransaction(); + + // Update current mesh and topology + CurrentMesh->Copy(*Preview->PreviewMesh->GetMesh(), true, true, true, true); + *CurrentTopology = *LatestOpTopologyResult; + CurrentTopology->RetargetOnClonedMesh(CurrentMesh.Get()); + MeshSpatial.Build(); + TopologySelector.Invalidate(true, true); + } + + PreviewEdges.Reset(); + bWaitingForInsertionCompletion = false; + } + } +} + +void UEdgeLoopInsertionTool::Render(IToolsContextRenderAPI* RenderAPI) +{ + GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CameraState); + + // Draw the existing group edges + FViewCameraState RenderCameraState = RenderAPI->GetCameraState(); + ExistingEdgesRenderer.BeginFrame(RenderAPI, RenderCameraState); + ExistingEdgesRenderer.SetTransform(Preview->PreviewMesh->GetTransform()); + + for (const FGroupTopology::FGroupEdge& Edge : CurrentTopology->Edges) + { + FVector3d A, B; + for (int32 eid : Edge.Span.Edges) + { + CurrentMesh->GetEdgeV(eid, A, B); + ExistingEdgesRenderer.DrawLine(A, B); + } + } + ExistingEdgesRenderer.EndFrame(); + + // Draw the preview edges + PreviewEdgeRenderer.BeginFrame(RenderAPI, RenderCameraState); + PreviewEdgeRenderer.SetTransform(Preview->PreviewMesh->GetTransform()); // TODO: Is this right? + for (TPair& EdgeVerts : PreviewEdges) + { + PreviewEdgeRenderer.DrawLine(EdgeVerts.Key, EdgeVerts.Value); + } + PreviewEdgeRenderer.EndFrame(); +} + +bool UEdgeLoopInsertionTool::CanAccept() const +{ + return !bWaitingForInsertionCompletion; +} + +void UEdgeLoopInsertionTool::OnPropertyModified(UObject* PropertySet, FProperty* Property) +{ + PreviewEdges.Reset(); + Preview->PreviewMesh->EnableWireframe(Settings->bWireframe); + Preview->InvalidateResult(); +} + +FInputRayHit UEdgeLoopInsertionTool::HitTest(const FRay& WorldRay) +{ + FInputRayHit Hit; + + // See if we hit an edge + FTransform LocalToWorld = ComponentTarget->GetWorldTransform(); + FRay3d LocalRay(LocalToWorld.InverseTransformPosition(WorldRay.Origin), + LocalToWorld.InverseTransformVector(WorldRay.Direction), false); + FGroupTopologySelection Selection; + FVector3d Position, Normal; + if (TopologySelector.FindSelectedElement( + TopologySelectorSettings, LocalRay, Selection, Position, Normal)) + { + // TODO: We could check here that the edge has some quad-like neighbor. For now we + // just check that the edge isn't a loop unto itself (in which case the neighbor groups + // are definitely not quad-like). + int32 GroupEdgeID = Selection.SelectedEdgeIDs[0]; + const FGroupTopology::FGroupEdge& GroupEdge = CurrentTopology->Edges[GroupEdgeID]; + if (GroupEdge.EndpointCorners.A != FDynamicMesh3::InvalidID) + { + Hit = FInputRayHit(LocalRay.Project(Position)); + } + } + + return Hit; +} + +bool UEdgeLoopInsertionTool::UpdateHoveredItem(const FRay& WorldRay) +{ + // Check that we hit an edge + FTransform LocalToWorld = ComponentTarget->GetWorldTransform(); + FRay3d LocalRay(LocalToWorld.InverseTransformPosition(WorldRay.Origin), + LocalToWorld.InverseTransformVector(WorldRay.Direction), false); + + FGroupTopologySelection Selection; + FVector3d Position, Normal; + int32 EdgeSegmentID; + if (!TopologySelector.FindSelectedElement( + TopologySelectorSettings, LocalRay, Selection, Position, Normal, &EdgeSegmentID)) + { + ClearPreview(); + return false; // Didn't hit anything + } + + // Check that the edge has endpoints + int32 GroupEdgeID = Selection.SelectedEdgeIDs[0]; + FGroupTopology::FGroupEdge GroupEdge = CurrentTopology->Edges[GroupEdgeID]; + if (GroupEdge.EndpointCorners.A == FDynamicMesh3::InvalidID) + { + ClearPreview(); + return false; // Edge definitely doesn't have quad-like neighbors + } + + if (Settings->PositionMode == EEdgeLoopPositioningMode::Even) + { + // In even mode and non-interactive mode, all that matters is the group edge + // that we're hovering, not where our pointer is exactly. + ConditionallyUpdatePreview(GroupEdgeID); + return true; + } + if (!Settings->bInteractive) + { + // Don't try to insert a loop when our inputs don't make sense. + double TotalLength = CurrentTopology->GetEdgeArcLength(GroupEdgeID); + if (Settings->PositionMode == EEdgeLoopPositioningMode::DistanceOffset) + { + if (Settings->DistanceOffset > TotalLength || Settings->DistanceOffset <= Settings->VertexTolerance) + { + ClearPreview(); + return false; + } + } + else if (Settings->PositionMode == EEdgeLoopPositioningMode::ProportionOffset) + { + if (abs(Settings->ProportionOffset * TotalLength - TotalLength) <= Settings->VertexTolerance) + { + ClearPreview(); + return false; + } + } + + ConditionallyUpdatePreview(GroupEdgeID); + return true; + } + + // Otherwise, we need to figure out where along the edge we are hovering. + double NewInputLength = 0; + int32 StartVid = GroupEdge.Span.Vertices[EdgeSegmentID]; + int32 EndVid = GroupEdge.Span.Vertices[EdgeSegmentID + 1]; + FVector3d StartVert = CurrentMesh->GetVertex(StartVid); + FVector3d EndVert = CurrentMesh->GetVertex(EndVid); + + FRay EdgeRay((FVector)StartVert, (FVector)(EndVert - StartVert), false); + + double DistDownEdge = EdgeRay.GetParameter((FVector)Position); + + TArray PerVertexLengths; + double TotalLength = CurrentTopology->GetEdgeArcLength(GroupEdgeID, &PerVertexLengths); + + NewInputLength = PerVertexLengths[EdgeSegmentID] + DistDownEdge; + if (Settings->bFlipOffsetDirection) + { + // If we flipped start corner, we should be measuring from the opposite direction + NewInputLength = TotalLength - NewInputLength; + } + // We avoid trying to insert loops that are guaranteed to follow existing group edges. + // Distance offset with total length may work if the group widens on the other side. + // Though it's worth noting that this filter as a whole is assuming straight group edges... + if (NewInputLength <= Settings->VertexTolerance || + (Settings->PositionMode == EEdgeLoopPositioningMode::ProportionOffset + && abs(NewInputLength - TotalLength) <= Settings->VertexTolerance)) + { + ClearPreview(); + return false; + } + if (Settings->PositionMode == EEdgeLoopPositioningMode::ProportionOffset) + { + NewInputLength /= TotalLength; + } + ConditionallyUpdatePreview(GroupEdgeID, &NewInputLength); + return true; +} + +void UEdgeLoopInsertionTool::ClearPreview() +{ + // We don't seem to have a way to cancel the background op on a mesh without shutting down + // the entire preview, hence us clearing the preview this way. When we know that the op is + // not running, we can instead use UpdatePreview() to reset the mesh to the original mesh. + bShowingBaseMesh = true; + PreviewEdges.Reset(); + Preview->InvalidateResult(); +} + +void UEdgeLoopInsertionTool::ConditionallyUpdatePreview(int32 NewGroupID, double *NewInteractiveInputLength) +{ + if (bShowingBaseMesh || InputGroupEdgeID != NewGroupID + || (NewInteractiveInputLength && Settings->PositionMode != EEdgeLoopPositioningMode::Even + && *NewInteractiveInputLength != InteractiveInputLength)) + { + InputGroupEdgeID = NewGroupID; + if (NewInteractiveInputLength) + { + InteractiveInputLength = *NewInteractiveInputLength; + } + bShowingBaseMesh = false; + PreviewEdges.Reset(); + Preview->InvalidateResult(); + } +} + +FInputRayHit UEdgeLoopInsertionTool::BeginHoverSequenceHitTest(const FInputDeviceRay& PressPos) +{ + if (bWaitingForInsertionCompletion) + { + return FInputRayHit(); + } + + return HitTest(PressPos.WorldRay); +} + +bool UEdgeLoopInsertionTool::OnUpdateHover(const FInputDeviceRay& DevicePos) +{ + if (bWaitingForInsertionCompletion) + { + return false; + } + + return UpdateHoveredItem(DevicePos.WorldRay); +} + +void UEdgeLoopInsertionTool::OnEndHover() +{ + if (!bWaitingForInsertionCompletion) + { + ClearPreview(); + } +} + +FInputRayHit UEdgeLoopInsertionTool::IsHitByClick(const FInputDeviceRay& ClickPos) +{ + FInputRayHit Hit; + if (bWaitingForInsertionCompletion) + { + return Hit; + } + + return HitTest(ClickPos.WorldRay); +} + +void UEdgeLoopInsertionTool::OnClicked(const FInputDeviceRay& ClickPos) +{ + if (bWaitingForInsertionCompletion) + { + return; + } + + if (UpdateHoveredItem(ClickPos.WorldRay)) + { + bWaitingForInsertionCompletion = true; + } + +} + + +// Undo/redo support + +void FEdgeLoopInsertionChangeBookend::Apply(UObject* Object) +{ + if (!bBeforeChange) + { + UEdgeLoopInsertionTool* Tool = Cast(Object); + Tool->CurrentMesh.Reset(); + FMeshDescriptionToDynamicMesh Converter; + Converter.Convert(Tool->ComponentTarget->GetMesh(), *Tool->CurrentMesh); + Tool->CurrentTopology->RebuildTopology(); + Tool->MeshSpatial.Build(); + Tool->TopologySelector.Invalidate(true, true); + Tool->Preview->PreviewMesh->UpdatePreview(Tool->CurrentMesh.Get()); + } +} + +void FEdgeLoopInsertionChangeBookend::Revert(UObject* Object) +{ + if (bBeforeChange) + { + UEdgeLoopInsertionTool* Tool = Cast(Object); + Tool->CurrentMesh->Clear(); + FMeshDescriptionToDynamicMesh Converter; + Converter.Convert(Tool->ComponentTarget->GetMesh(), *Tool->CurrentMesh); + Tool->CurrentTopology->RebuildTopology(); + Tool->MeshSpatial.Build(); + Tool->TopologySelector.Invalidate(true, true); + Tool->Preview->PreviewMesh->UpdatePreview(Tool->CurrentMesh.Get()); + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/EditMeshPolygonsTool.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/EditMeshPolygonsTool.cpp index 905348ebb758..e9e5378286e1 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/EditMeshPolygonsTool.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/EditMeshPolygonsTool.cpp @@ -124,11 +124,11 @@ void UEditMeshPolygonsTool::Setup() // add properties - TransformProps = NewObject(this); - AddToolPropertySource(TransformProps); - TransformProps->WatchProperty(TransformProps->LocalFrameMode, + CommonProps = NewObject(this); + AddToolPropertySource(CommonProps); + CommonProps->WatchProperty(CommonProps->LocalFrameMode, [this](ELocalFrameMode) { UpdateMultiTransformerFrame(); }); - TransformProps->WatchProperty(TransformProps->bLockRotation, + CommonProps->WatchProperty(CommonProps->bLockRotation, [this](bool) { LockedTransfomerFrame = LastTransformerFrame; UpdateMultiTransformerFrame(); @@ -166,7 +166,7 @@ void UEditMeshPolygonsTool::Setup() MultiTransformer->OnTransformUpdated.AddUObject(this, &UEditMeshPolygonsTool::OnMultiTransformerTransformUpdate); MultiTransformer->OnTransformCompleted.AddUObject(this, &UEditMeshPolygonsTool::OnMultiTransformerTransformEnd); MultiTransformer->SetSnapToWorldGridSourceFunc([this]() { - return TransformProps->bSnapToWorldGrid + return CommonProps->bSnapToWorldGrid && GetToolManager()->GetContextQueriesAPI()->GetCurrentCoordinateSystem() == EToolContextCoordinateSystem::World; }); MultiTransformer->SetGizmoVisibility(false); @@ -203,6 +203,16 @@ void UEditMeshPolygonsTool::Setup() ExtrudeProperties->WatchProperty(ExtrudeProperties->Direction, [this](EPolyEditExtrudeDirection){ RestartExtrude(); }); + InsetProperties = NewObject(); + InsetProperties->RestoreProperties(this); + AddToolPropertySource(InsetProperties); + SetToolPropertySourceEnabled(InsetProperties, false); + + OutsetProperties = NewObject(); + OutsetProperties->RestoreProperties(this); + AddToolPropertySource(OutsetProperties); + SetToolPropertySourceEnabled(OutsetProperties, false); + CutProperties = NewObject(); CutProperties->RestoreProperties(this); AddToolPropertySource(CutProperties); @@ -226,6 +236,7 @@ void UEditMeshPolygonsTool::Setup() void UEditMeshPolygonsTool::Shutdown(EToolShutdownType ShutdownType) { ExtrudeProperties->SaveProperties(this); + InsetProperties->SaveProperties(this); CutProperties->SaveProperties(this); SetUVProperties->SaveProperties(this); @@ -279,7 +290,7 @@ void UEditMeshPolygonsTool::RegisterActions(FInteractiveToolActionSet& ActionSet LOCTEXT("ToggleLockRotationUIName", "Lock Rotation"), LOCTEXT("ToggleLockRotationTooltip", "Toggle Frame Rotation Lock on and off"), EModifierKey::None, EKeys::Q, - [this]() { TransformProps->bLockRotation = !TransformProps->bLockRotation; }); + [this]() { CommonProps->bLockRotation = !CommonProps->bLockRotation; }); } @@ -401,7 +412,7 @@ void UEditMeshPolygonsTool::UpdateMultiTransformerFrame(const FFrame3d* UseFrame FFrame3d SetFrame = LastTransformerFrame; if (UseFrame == nullptr) { - if (TransformProps->LocalFrameMode == ELocalFrameMode::FromGeometry) + if (CommonProps->LocalFrameMode == ELocalFrameMode::FromGeometry) { SetFrame = LastGeometryFrame; } @@ -415,7 +426,7 @@ void UEditMeshPolygonsTool::UpdateMultiTransformerFrame(const FFrame3d* UseFrame SetFrame = *UseFrame; } - if (TransformProps->bLockRotation) + if (CommonProps->bLockRotation) { SetFrame.Rotation = LockedTransfomerFrame.Rotation; } @@ -751,8 +762,13 @@ void UEditMeshPolygonsTool::OnTick(float DeltaTime) } else if (CurrentToolMode == ECurrentToolMode::InsetSelection || CurrentToolMode == ECurrentToolMode::OutsetSelection) { - double Sign = (CurrentToolMode == ECurrentToolMode::OutsetSelection) ? -1.0 : 1.0; - EditPreview->UpdateInsetType(Sign * CurveDistMechanic->CurrentDistance); + bool bOutset = (CurrentToolMode == ECurrentToolMode::OutsetSelection); + double Sign = bOutset ? -1.0 : 1.0; + bool bReproject = (bOutset) ? false : InsetProperties->bReproject; + double Softness = (bOutset) ? OutsetProperties->Softness : InsetProperties->Softness; + bool bBoundaryOnly = (bOutset) ? OutsetProperties->bBoundaryOnly : InsetProperties->bBoundaryOnly; + double AreaCorrection = (bOutset) ? OutsetProperties->AreaScale : InsetProperties->AreaScale; + EditPreview->UpdateInsetType(Sign* CurveDistMechanic->CurrentDistance, bReproject, Softness, AreaCorrection, bBoundaryOnly); } else if (CurrentToolMode == ECurrentToolMode::SetUVs) { @@ -774,6 +790,7 @@ void UEditMeshPolygonsTool::PrecomputeTopology() [this]() { return &GetSpatial(); }, [this]() { return GetShiftToggle(); } ); + SelectionMechanic->SetShouldSelectEdgeLoopsFunc([this]() { return CommonProps->bSelectEdgeLoops; }); LinearDeformer.Initialize(Mesh, Topology.Get()); } @@ -784,7 +801,7 @@ void UEditMeshPolygonsTool::PrecomputeTopology() void UEditMeshPolygonsTool::Render(IToolsContextRenderAPI* RenderAPI) { GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CameraState); - DynamicMeshComponent->bExplicitShowWireframe = TransformProps->bShowWireframe; + DynamicMeshComponent->bExplicitShowWireframe = CommonProps->bShowWireframe; SelectionMechanic->Render(RenderAPI); @@ -929,7 +946,7 @@ void UEditMeshPolygonsTool::BeginExtrude(bool bIsNormalOffset) }; ExtrudeHeightMechanic->WorldPointSnapFunc = [this](const FVector3d& WorldPos, FVector3d& SnapPos) { - return TransformProps->bSnapToWorldGrid && ToolSceneQueriesUtil::FindWorldGridSnapPoint(this, WorldPos, SnapPos); + return CommonProps->bSnapToWorldGrid && ToolSceneQueriesUtil::FindWorldGridSnapPoint(this, WorldPos, SnapPos); }; ExtrudeHeightMechanic->CurrentHeight = 1.0f; // initialize to something non-zero...prob should be based on polygon bounds maybe? @@ -1032,7 +1049,7 @@ void UEditMeshPolygonsTool::BeginInset(bool bOutset) CurveDistMechanic->Setup(this); CurveDistMechanic->WorldPointSnapFunc = [this](const FVector3d& WorldPos, FVector3d& SnapPos) { - return TransformProps->bSnapToWorldGrid && ToolSceneQueriesUtil::FindWorldGridSnapPoint(this, WorldPos, SnapPos); + return CommonProps->bSnapToWorldGrid && ToolSceneQueriesUtil::FindWorldGridSnapPoint(this, WorldPos, SnapPos); }; CurveDistMechanic->CurrentDistance = 1.0f; // initialize to something non-zero...prob should be based on polygon bounds maybe? @@ -1041,6 +1058,9 @@ void UEditMeshPolygonsTool::BeginInset(bool bOutset) Loops.Loops[0].GetVertices(LoopVertices); CurveDistMechanic->InitializePolyLoop(LoopVertices, FTransform3d::Identity()); CurrentToolMode = (bOutset) ? ECurrentToolMode::OutsetSelection : ECurrentToolMode::InsetSelection; + + SetToolPropertySourceEnabled((bOutset) ? + (UInteractiveToolPropertySet*)OutsetProperties : (UInteractiveToolPropertySet*)InsetProperties, true); } @@ -1055,6 +1075,11 @@ void UEditMeshPolygonsTool::ApplyInset(bool bOutset) Inset.UVScaleFactor = UVScaleFactor; Inset.Triangles = ActiveTriangleSelection; Inset.InsetDistance = (bOutset) ? -CurveDistMechanic->CurrentDistance : CurveDistMechanic->CurrentDistance; + Inset.bReproject = (bOutset) ? false : InsetProperties->bReproject; + Inset.Softness = (bOutset) ? OutsetProperties->Softness : InsetProperties->Softness; + Inset.bSolveRegionInteriors = (bOutset) ? (!OutsetProperties->bBoundaryOnly) : (!InsetProperties->bBoundaryOnly); + Inset.AreaCorrection = (bOutset) ? OutsetProperties->AreaScale : InsetProperties->AreaScale; + Inset.ChangeTracker = MakeUnique(Mesh); Inset.ChangeTracker->BeginChange(); Inset.Apply(); @@ -1068,6 +1093,9 @@ void UEditMeshPolygonsTool::ApplyInset(bool bOutset) CurveDistMechanic = nullptr; CurrentToolMode = ECurrentToolMode::TransformSelection; + + SetToolPropertySourceEnabled((bOutset) ? + (UInteractiveToolPropertySet*)OutsetProperties : (UInteractiveToolPropertySet*)InsetProperties, false); } @@ -1983,6 +2011,8 @@ void UEditMeshPolygonsTool::CancelMeshEditChange() // hide properties that might be visible SetToolPropertySourceEnabled(ExtrudeProperties, false); + SetToolPropertySourceEnabled(InsetProperties, false); + SetToolPropertySourceEnabled(OutsetProperties, false); SetToolPropertySourceEnabled(CutProperties, false); SetToolPropertySourceEnabled(SetUVProperties, false); diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/MeshInspectorTool.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/MeshInspectorTool.cpp index 07f73fc08546..b92d43990311 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/MeshInspectorTool.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/MeshInspectorTool.cpp @@ -233,18 +233,18 @@ void UMeshInspectorTool::UpdateVisualization() } FColor BoundaryEdgeColor(240, 15, 15); - float BoundaryEdgeThickness = LineWidthMultiplier * 2.0; + float BoundaryEdgeThickness = LineWidthMultiplier * 4.0; FColor UVSeamColor(15, 240, 15); - float UVSeamThickness = LineWidthMultiplier * 1.0; + float UVSeamThickness = LineWidthMultiplier * 2.0; FColor NormalSeamColor(15, 240, 240); - float NormalSeamThickness = LineWidthMultiplier * 1.0; + float NormalSeamThickness = LineWidthMultiplier * 2.0; FColor PolygonBorderColor(240, 15, 240); - float PolygonBorderThickness = LineWidthMultiplier * 1.0; + float PolygonBorderThickness = LineWidthMultiplier * 2.0; FColor NormalColor(15, 15, 240); - float NormalThickness = LineWidthMultiplier * 1.0f; + float NormalThickness = LineWidthMultiplier * 2.0f; FColor TangentColor(240, 15, 15); FColor BinormalColor(15, 240, 15); - float TangentThickness = LineWidthMultiplier * 1.0f; + float TangentThickness = LineWidthMultiplier * 2.0f; float BoundaryEdgeDepthBias = 2.0f; float UVSeamDepthBias = 3.0f; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/MeshVertexSculptTool.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/MeshVertexSculptTool.cpp index 268126862a0b..4c6c57df9f79 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/MeshVertexSculptTool.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/MeshVertexSculptTool.cpp @@ -13,7 +13,7 @@ #include "Changes/MeshVertexChange.h" -//#include "Sculpting/KelvinletBrushOp.h" +#include "Sculpting/KelvinletBrushOp.h" #include "Sculpting/MeshSmoothingBrushOps.h" #include "Sculpting/MeshInflateBrushOps.h" #include "Sculpting/MeshMoveBrushOps.h" @@ -171,6 +171,21 @@ void UMeshVertexSculptTool::Setup() MakeUnique>(), NewObject(this)); + RegisterBrushType((int32)EMeshVertexSculptBrushType::ScaleKelvin , + MakeUnique>(), + NewObject(this)); + + RegisterBrushType((int32)EMeshVertexSculptBrushType::PullKelvin, + MakeUnique>(), + NewObject(this)); + + RegisterBrushType((int32)EMeshVertexSculptBrushType::PullSharpKelvin, + MakeUnique>(), + NewObject(this)); + + RegisterBrushType((int32)EMeshVertexSculptBrushType::TwistKelvin, + MakeUnique>(), + NewObject(this)); // secondary brushes RegisterSecondaryBrushType((int32)EMeshVertexSculptBrushType::Smooth, diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/CollisionGeometryVisualization.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/CollisionGeometryVisualization.cpp new file mode 100644 index 000000000000..d4afd3da66d9 --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/CollisionGeometryVisualization.cpp @@ -0,0 +1,118 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Physics/CollisionGeometryVisualization.h" +#include "Generators/LineSegmentGenerators.h" + +void UE::PhysicsTools::InitializePreviewGeometryLines(const FPhysicsDataCollection& PhysicsData, UPreviewGeometry* PreviewGeom, + const FColor& LineColor, float LineThickness, float DepthBias, int32 CircleStepResolution) +{ + int32 CircleSteps = FMath::Max(4, CircleStepResolution); + FColor SphereColor = LineColor; + FColor BoxColor = LineColor; + FColor ConvexColor = LineColor; + FColor CapsuleColor = LineColor; + + const FKAggregateGeom& AggGeom = PhysicsData.AggGeom; + + // spheres are draw as 3 orthogonal circles + PreviewGeom->CreateOrUpdateLineSet(TEXT("Spheres"), AggGeom.SphereElems.Num(), [&](int32 Index, TArray& LinesOut) + { + const FKSphereElem& Sphere = AggGeom.SphereElems[Index]; + FTransform ElemTransform = Sphere.GetTransform(); + ElemTransform.ScaleTranslation(PhysicsData.ExternalScale3D); + FTransform3f ElemTransformf(ElemTransform); + float Radius = PhysicsData.ExternalScale3D.GetAbsMin() * Sphere.Radius; + UE::Geometry::GenerateCircleSegments(CircleSteps, Radius, FVector3f::Zero(), FVector3f::UnitX(), FVector3f::UnitY(), ElemTransformf, + [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, SphereColor, LineThickness, DepthBias)); }); + UE::Geometry::GenerateCircleSegments(CircleSteps, Radius, FVector3f::Zero(), FVector3f::UnitX(), FVector3f::UnitZ(), ElemTransformf, + [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, SphereColor, LineThickness, DepthBias)); }); + UE::Geometry::GenerateCircleSegments(CircleSteps, Radius, FVector3f::Zero(), FVector3f::UnitY(), FVector3f::UnitZ(), ElemTransformf, + [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, SphereColor, LineThickness, DepthBias)); }); + }); + + + // boxes are drawn as boxes + PreviewGeom->CreateOrUpdateLineSet(TEXT("Boxes"), AggGeom.BoxElems.Num(), [&](int32 Index, TArray& LinesOut) + { + const FKBoxElem& Box = AggGeom.BoxElems[Index]; + FTransform ElemTransform = Box.GetTransform(); + ElemTransform.ScaleTranslation(PhysicsData.ExternalScale3D); + FTransform3f ElemTransformf(ElemTransform); + FVector3f HalfDimensions( + PhysicsData.ExternalScale3D.X * Box.X * 0.5f, + PhysicsData.ExternalScale3D.Y * Box.Y * 0.5f, + PhysicsData.ExternalScale3D.Z * Box.Z * 0.5f); + UE::Geometry::GenerateBoxSegments(HalfDimensions, FVector3f::Zero(), FVector3f::UnitX(), FVector3f::UnitY(), FVector3f::UnitZ(), ElemTransformf, + [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, BoxColor, LineThickness, DepthBias)); }); + }); + + + // capsules are draw as two hemispheres (with 3 intersecting arcs/circles) and connecting lines + PreviewGeom->CreateOrUpdateLineSet(TEXT("Capsules"), AggGeom.SphylElems.Num(), [&](int32 Index, TArray& LinesOut) + { + const FKSphylElem& Capsule = AggGeom.SphylElems[Index]; + FTransform ElemTransform = Capsule.GetTransform(); + ElemTransform.ScaleTranslation(PhysicsData.ExternalScale3D); + FTransform3f ElemTransformf(ElemTransform); + const float HalfLength = Capsule.GetScaledCylinderLength(PhysicsData.ExternalScale3D) * .5f; + const float Radius = Capsule.GetScaledRadius(PhysicsData.ExternalScale3D); + FVector3f Top(0, 0, HalfLength), Bottom(0, 0, -HalfLength); + + // top and bottom circles + UE::Geometry::GenerateCircleSegments(CircleSteps, Radius, Top, FVector3f::UnitX(), FVector3f::UnitY(), ElemTransformf, + [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, CapsuleColor, LineThickness, DepthBias)); }); + UE::Geometry::GenerateCircleSegments(CircleSteps, Radius, Bottom, FVector3f::UnitX(), FVector3f::UnitY(), ElemTransformf, + [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, CapsuleColor, LineThickness, DepthBias)); }); + + // top dome + UE::Geometry::GenerateArcSegments(CircleSteps, Radius, 0.0, PI, Top, FVector3f::UnitY(), FVector3f::UnitZ(), ElemTransformf, + [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, CapsuleColor, LineThickness, DepthBias)); }); + UE::Geometry::GenerateArcSegments(CircleSteps, Radius, 0.0, PI, Top, FVector3f::UnitX(), FVector3f::UnitZ(), ElemTransformf, + [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, CapsuleColor, LineThickness, DepthBias)); }); + + // bottom dome + UE::Geometry::GenerateArcSegments(CircleSteps, Radius, 0.0, -PI, Bottom, FVector3f::UnitY(), FVector3f::UnitZ(), ElemTransformf, + [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, CapsuleColor, LineThickness, DepthBias)); }); + UE::Geometry::GenerateArcSegments(CircleSteps, Radius, 0.0, -PI, Bottom, FVector3f::UnitX(), FVector3f::UnitZ(), ElemTransformf, + [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, CapsuleColor, LineThickness, DepthBias)); }); + + // connecting lines + for (int k = 0; k < 2; ++k) + { + FVector DX = (k < 1) ? FVector(-Radius, 0, 0) : FVector(Radius, 0, 0); + LinesOut.Add(FRenderableLine( + ElemTransform.TransformPosition((FVector)Top + DX), + ElemTransform.TransformPosition((FVector)Bottom + DX), CapsuleColor, LineThickness, DepthBias)); + FVector DY = (k < 1) ? FVector(0, -Radius, 0) : FVector(0, Radius, 0); + LinesOut.Add(FRenderableLine( + ElemTransform.TransformPosition((FVector)Top + DY), + ElemTransform.TransformPosition((FVector)Bottom + DY), CapsuleColor, LineThickness, DepthBias)); + } + }); + + + + // convexes are drawn as mesh edges + PreviewGeom->CreateOrUpdateLineSet(TEXT("Convexes"), AggGeom.ConvexElems.Num(), [&](int32 Index, TArray& LinesOut) + { + const FKConvexElem& Convex = AggGeom.ConvexElems[Index]; + FTransform ElemTransform = Convex.GetTransform(); + ElemTransform.ScaleTranslation(PhysicsData.ExternalScale3D); + ElemTransform.SetScale3D(PhysicsData.ExternalScale3D); + int32 NumTriangles = Convex.IndexData.Num() / 3; + for (int32 k = 0; k < NumTriangles; ++k) + { + FVector A = ElemTransform.TransformPosition(Convex.VertexData[Convex.IndexData[3 * k]]); + FVector B = ElemTransform.TransformPosition(Convex.VertexData[Convex.IndexData[3 * k + 1]]); + FVector C = ElemTransform.TransformPosition(Convex.VertexData[Convex.IndexData[3 * k + 2]]); + LinesOut.Add(FRenderableLine(A, B, ConvexColor, LineThickness, DepthBias)); + LinesOut.Add(FRenderableLine(B, C, ConvexColor, LineThickness, DepthBias)); + LinesOut.Add(FRenderableLine(C, A, ConvexColor, LineThickness, DepthBias)); + } + }); + + + // Unclear whether we actually use these in the Engine, for UBodySetup? Does not appear to be supported by UxX import system, + // and online documentation suggests they may only be supported for cloth? + ensure(AggGeom.TaperedCapsuleElems.Num() == 0); +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/CollisionPropertySets.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/CollisionPropertySets.cpp new file mode 100644 index 000000000000..6f7c9deab2a6 --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/CollisionPropertySets.cpp @@ -0,0 +1,71 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Physics/CollisionPropertySets.h" +#include "Physics/PhysicsDataCollection.h" + +#include "Engine/Classes/PhysicsEngine/BodySetup.h" + +void UE::PhysicsTools::InitializePhysicsToolObjectPropertySet(const FPhysicsDataCollection* PhysicsData, UPhysicsObjectToolPropertySet* PropSet) +{ + check(PhysicsData); + check(PropSet); + + PropSet->ObjectName = FString::Printf(TEXT("%s - %s"), + *PhysicsData->SourceComponent->GetOwner()->GetName(), + *PhysicsData->SourceComponent->GetName()); + + PropSet->CollisionType = (ECollisionGeometryMode)(int32)PhysicsData->BodySetup->GetCollisionTraceFlag(); + + // change object name to above + //PropSet->Rename(*PropSet->ObjectName, nullptr, REN_NonTransactional | REN_ForceNoResetLoaders | REN_DoNotDirty); + + const FKAggregateGeom& AggGeom = PhysicsData->AggGeom; + + for (const FKSphereElem& Sphere : AggGeom.SphereElems) + { + FPhysicsSphereData SphereData; + SphereData.Radius = Sphere.Radius; + SphereData.Transform = Sphere.GetTransform(); + SphereData.Element = Sphere; + PropSet->Spheres.Add(SphereData); + } + + for (const FKBoxElem& Box : AggGeom.BoxElems) + { + FPhysicsBoxData BoxData; + BoxData.Dimensions = FVector(Box.X, Box.Y, Box.Z); + BoxData.Transform = Box.GetTransform(); + BoxData.Element = Box; + PropSet->Boxes.Add(BoxData); + } + + for (const FKSphylElem& Capsule : AggGeom.SphylElems) + { + FPhysicsCapsuleData CapsuleData; + CapsuleData.Radius = Capsule.Radius; + CapsuleData.Length = Capsule.Length; + CapsuleData.Transform = Capsule.GetTransform(); + CapsuleData.Element = Capsule; + PropSet->Capsules.Add(CapsuleData); + } + + for (const FKConvexElem& Convex : AggGeom.ConvexElems) + { + FPhysicsConvexData ConvexData; + ConvexData.NumVertices = Convex.VertexData.Num(); + ConvexData.NumFaces = Convex.IndexData.Num() / 3; + ConvexData.Element = Convex; + PropSet->Convexes.Add(ConvexData); + } +} + + + +void UPhysicsObjectToolPropertySet::Reset() +{ + ObjectName = TEXT(""); + Spheres.Reset(); + Boxes.Reset(); + Capsules.Reset(); + Convexes.Reset(); +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/ExtractCollisionGeometryTool.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/ExtractCollisionGeometryTool.cpp new file mode 100644 index 000000000000..d944ac5bf241 --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/ExtractCollisionGeometryTool.cpp @@ -0,0 +1,321 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Physics/ExtractCollisionGeometryTool.h" +#include "InteractiveToolManager.h" +#include "ToolBuilderUtil.h" +#include "ToolSetupUtil.h" +#include "AssetGenerationUtil.h" +#include "Selection/ToolSelectionUtil.h" +#include "Drawing/PreviewGeometryActor.h" +#include "Util/ColorConstants.h" +#include "PreviewMesh.h" + +#include "DynamicMeshEditor.h" +#include "MeshNormals.h" +#include "Generators/SphereGenerator.h" +#include "Generators/MinimalBoxMeshGenerator.h" +#include "Generators/CapsuleGenerator.h" +#include "MeshTransforms.h" +#include "Parameterization/DynamicMeshUVEditor.h" + +#include "Physics/PhysicsDataCollection.h" +#include "Physics/CollisionGeometryVisualization.h" + +// physics data +#include "Engine/Classes/Engine/StaticMesh.h" +#include "Engine/Classes/Components/StaticMeshComponent.h" +#include "Engine/Classes/PhysicsEngine/BodySetup.h" +#include "Engine/Classes/PhysicsEngine/AggregateGeom.h" + + +#define LOCTEXT_NAMESPACE "UExtractCollisionGeometryTool" + + + +bool UExtractCollisionGeometryToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const +{ + int32 NumStaticMeshes = ToolBuilderUtil::CountComponents(SceneState, [&](UActorComponent* Comp) { return Cast(Comp) != nullptr; }); + int32 NumComponentTargets = ToolBuilderUtil::CountComponents(SceneState, CanMakeComponentTarget); + return (NumStaticMeshes == 1 && NumStaticMeshes == NumComponentTargets); +} + + +UInteractiveTool* UExtractCollisionGeometryToolBuilder::BuildTool(const FToolBuilderState& SceneState) const +{ + UExtractCollisionGeometryTool* NewTool = NewObject(SceneState.ToolManager); + NewTool->SetWorld(SceneState.World); + check(AssetAPI); + NewTool->SetAssetAPI(AssetAPI); + + TArray ValidComponents = ToolBuilderUtil::FindAllComponents(SceneState, + [&](UActorComponent* Comp) { return Cast(Comp) != nullptr; }); + check(ValidComponents.Num() == 1); + + TUniquePtr ComponentTargets; + UStaticMeshComponent* MeshComponent = Cast(ValidComponents[0]); + NewTool->SetSelection(MakeComponentTarget(MeshComponent)); + return NewTool; +} + + +void UExtractCollisionGeometryTool::Setup() +{ + UInteractiveTool::Setup(); + + // create preview mesh + PreviewMesh = NewObject(this); + PreviewMesh->bBuildSpatialDataStructure = false; + PreviewMesh->CreateInWorld(TargetWorld, FTransform::Identity); + PreviewMesh->SetTransform(ComponentTarget->GetWorldTransform()); + PreviewMesh->SetMaterial(ToolSetupUtil::GetDefaultSculptMaterial(GetToolManager())); + PreviewMesh->SetOverrideRenderMaterial(ToolSetupUtil::GetSelectionMaterial(GetToolManager())); + PreviewMesh->SetTriangleColorFunction([this](const FDynamicMesh3* Mesh, int TriangleID) + { + return LinearColors::SelectFColor(Mesh->GetTriangleGroup(TriangleID)); + }); + + VizSettings = NewObject(this); + VizSettings->RestoreProperties(this); + AddToolPropertySource(VizSettings); + VizSettings->WatchProperty(VizSettings->LineThickness, [this](float NewValue) { bVisualizationDirty = true; }); + VizSettings->WatchProperty(VizSettings->Color, [this](FColor NewValue) { bVisualizationDirty = true; }); + + + const UStaticMeshComponent* Component = CastChecked(ComponentTarget->GetOwnerComponent()); + const UStaticMesh* StaticMesh = Component->GetStaticMesh(); + if (ensure(StaticMesh && StaticMesh->BodySetup)) + { + PhysicsInfo = MakeShared(); + PhysicsInfo->InitializeFromComponent(Component, true); + + PreviewElements = NewObject(this); + FTransform TargetTransform = ComponentTarget->GetWorldTransform(); + //PhysicsInfo->ExternalScale3D = TargetTransform.GetScale3D(); + //TargetTransform.SetScale3D(FVector::OneVector); + PreviewElements->CreateInWorld(ComponentTarget->GetOwnerActor()->GetWorld(), TargetTransform); + + UE::PhysicsTools::InitializePreviewGeometryLines(*PhysicsInfo, PreviewElements, + VizSettings->Color, VizSettings->LineThickness, 0.0f, 16); + + ObjectProps = NewObject(this); + UE::PhysicsTools::InitializePhysicsToolObjectPropertySet(PhysicsInfo.Get(), ObjectProps); + AddToolPropertySource(ObjectProps); + } + + + GetToolManager()->DisplayMessage( + LOCTEXT("OnStartTool", "Convert Collision geometry to Static Mesh"), + EToolMessageLevel::UserNotification); +} + + +void UExtractCollisionGeometryTool::Shutdown(EToolShutdownType ShutdownType) +{ + VizSettings->SaveProperties(this); + + FTransform3d Transform(PreviewMesh->GetTransform()); + + PreviewElements->Disconnect(); + PreviewMesh->SetVisible(false); + PreviewMesh->Disconnect(); + PreviewMesh = nullptr; + + if (ShutdownType == EToolShutdownType::Accept) + { + UMaterialInterface* UseMaterial = UMaterial::GetDefaultMaterial(MD_Surface); + + FString NewName = ComponentTarget->IsValid() ? + FString::Printf(TEXT("%s_Collision"), *ComponentTarget->GetOwnerComponent()->GetName()) : TEXT("CollisionGeo"); + + GetToolManager()->BeginUndoTransaction(LOCTEXT("CreateCollisionMesh", "Collision To Mesh")); + + AActor* NewActor = AssetGenerationUtil::GenerateStaticMeshActor( + AssetAPI, TargetWorld, + &CurrentMesh, Transform, NewName, UseMaterial); + if (NewActor != nullptr) + { + ToolSelectionUtil::SetNewActorSelection(GetToolManager(), NewActor); + } + + GetToolManager()->EndUndoTransaction(); + } +} + + +bool UExtractCollisionGeometryTool::CanAccept() const +{ + return CurrentMesh.TriangleCount() > 0; +} + + + + +void UExtractCollisionGeometryTool::OnTick(float DeltaTime) +{ + if (bResultValid == false) + { + RecalculateMesh(); + } + + if (bVisualizationDirty) + { + UpdateVisualization(); + bVisualizationDirty = false; + } +} + + + +void UExtractCollisionGeometryTool::UpdateVisualization() +{ + float UseThickness = VizSettings->LineThickness / 10.0f; + FColor UseColor = VizSettings->Color; + PreviewElements->UpdateAllLineSets([&](ULineSetComponent* LineSet) + { + LineSet->SetAllLinesThickness(UseThickness); + LineSet->SetAllLinesColor(UseColor); + }); +} + + + +void UExtractCollisionGeometryTool::RecalculateMesh() +{ + int32 SphereResolution = 16; + + CurrentMesh = FDynamicMesh3(EMeshComponents::FaceGroups); + CurrentMesh.EnableAttributes(); + + FDynamicMeshEditor Editor(&CurrentMesh); + + const FKAggregateGeom& AggGeom = PhysicsInfo->AggGeom; + + for (const FKSphereElem& Sphere : AggGeom.SphereElems) + { + FSphereGenerator SphereGen; + SphereGen.Radius = Sphere.Radius; + SphereGen.NumPhi = SphereGen.NumTheta = SphereResolution; + SphereGen.bPolygroupPerQuad = false; + SphereGen.Generate(); + FDynamicMesh3 SphereMesh(&SphereGen); + + MeshTransforms::Translate(SphereMesh, FVector3d(Sphere.Center)); + + FMeshIndexMappings Mappings; + Editor.AppendMesh(&SphereMesh, Mappings); + } + + + for (const FKBoxElem& Box : AggGeom.BoxElems) + { + FMinimalBoxMeshGenerator BoxGen; + BoxGen.Box = FOrientedBox3d( + FFrame3d(FVector3d(Box.Center), FQuaterniond(Box.Rotation.Quaternion())), + 0.5*FVector3d(Box.X, Box.Y, Box.Z)); + BoxGen.Generate(); + FDynamicMesh3 BoxMesh(&BoxGen); + + // transform not applied because it is just the Center/Rotation + + FMeshIndexMappings Mappings; + Editor.AppendMesh(&BoxMesh, Mappings); + } + + + for (const FKSphylElem& Capsule: AggGeom.SphylElems) + { + FCapsuleGenerator CapsuleGen; + CapsuleGen.Radius = Capsule.Radius; + CapsuleGen.SegmentLength = Capsule.Length; + CapsuleGen.NumHemisphereArcSteps = SphereResolution/4+1; + CapsuleGen.NumCircleSteps = SphereResolution; + CapsuleGen.bPolygroupPerQuad = false; + CapsuleGen.Generate(); + FDynamicMesh3 CapsuleMesh(&CapsuleGen); + + MeshTransforms::Translate(CapsuleMesh, FVector3d(0,0,-0.5*Capsule.Length) ); + + FTransform3d Transform(Capsule.GetTransform()); + MeshTransforms::ApplyTransform(CapsuleMesh, Transform); + + FMeshIndexMappings Mappings; + Editor.AppendMesh(&CapsuleMesh, Mappings); + } + + + for (const FKConvexElem& Convex : AggGeom.ConvexElems) + { + FTransform3d ElemTransform(Convex.GetTransform()); + FDynamicMesh3 ConvexMesh(EMeshComponents::None); + int32 NumVertices = Convex.VertexData.Num(); + for (int32 k = 0; k < NumVertices; ++k) + { + ConvexMesh.AppendVertex( FVector3d(Convex.VertexData[k]) ); + } + int32 NumTriangles = Convex.IndexData.Num() / 3; + for (int32 k = 0; k < NumTriangles; ++k) + { + ConvexMesh.AppendTriangle(Convex.IndexData[3*k], Convex.IndexData[3*k+1], Convex.IndexData[3*k+2]); + } + + ConvexMesh.ReverseOrientation(); + ConvexMesh.EnableTriangleGroups(0); + ConvexMesh.EnableAttributes(); + FDynamicMeshUVEditor UVEditor(&ConvexMesh, 0, true); + UVEditor.SetPerTriangleUVs(); + FMeshIndexMappings Mappings; + Editor.AppendMesh(&ConvexMesh, Mappings); + } + + FMeshNormals::InitializeMeshToPerTriangleNormals(&CurrentMesh); + PreviewMesh->UpdatePreview(&CurrentMesh); + + + //PreviewGeom->CreateOrUpdateLineSet(TEXT("Capsules"), AggGeom.SphylElems.Num(), [&](int32 Index, TArray& LinesOut) + //{ + // const FKSphylElem& Capsule = AggGeom.SphylElems[Index]; + // FTransform ElemTransform = Capsule.GetTransform(); + // ElemTransform.ScaleTranslation(PhysicsData.ExternalScale3D); + // FTransform3f ElemTransformf(ElemTransform); + // const float HalfLength = Capsule.GetScaledCylinderLength(PhysicsData.ExternalScale3D) * .5f; + // const float Radius = Capsule.GetScaledRadius(PhysicsData.ExternalScale3D); + // FVector3f Top(0, 0, HalfLength), Bottom(0, 0, -HalfLength); + + // // top and bottom circles + // UE::Geometry::GenerateCircleSegments(CircleSteps, Radius, Top, FVector3f::UnitX(), FVector3f::UnitY(), ElemTransformf, + // [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, CapsuleColor, LineThickness, DepthBias)); }); + // UE::Geometry::GenerateCircleSegments(CircleSteps, Radius, Bottom, FVector3f::UnitX(), FVector3f::UnitY(), ElemTransformf, + // [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, CapsuleColor, LineThickness, DepthBias)); }); + + // // top dome + // UE::Geometry::GenerateArcSegments(CircleSteps, Radius, 0.0, PI, Top, FVector3f::UnitY(), FVector3f::UnitZ(), ElemTransformf, + // [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, CapsuleColor, LineThickness, DepthBias)); }); + // UE::Geometry::GenerateArcSegments(CircleSteps, Radius, 0.0, PI, Top, FVector3f::UnitX(), FVector3f::UnitZ(), ElemTransformf, + // [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, CapsuleColor, LineThickness, DepthBias)); }); + + // // bottom dome + // UE::Geometry::GenerateArcSegments(CircleSteps, Radius, 0.0, -PI, Bottom, FVector3f::UnitY(), FVector3f::UnitZ(), ElemTransformf, + // [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, CapsuleColor, LineThickness, DepthBias)); }); + // UE::Geometry::GenerateArcSegments(CircleSteps, Radius, 0.0, -PI, Bottom, FVector3f::UnitX(), FVector3f::UnitZ(), ElemTransformf, + // [&](const FVector3f& A, const FVector3f& B) { LinesOut.Add(FRenderableLine((FVector)A, (FVector)B, CapsuleColor, LineThickness, DepthBias)); }); + + // // connecting lines + // for (int k = 0; k < 2; ++k) + // { + // FVector DX = (k < 1) ? FVector(-Radius, 0, 0) : FVector(Radius, 0, 0); + // LinesOut.Add(FRenderableLine( + // ElemTransform.TransformPosition((FVector)Top + DX), + // ElemTransform.TransformPosition((FVector)Bottom + DX), CapsuleColor, LineThickness, DepthBias)); + // FVector DY = (k < 1) ? FVector(0, -Radius, 0) : FVector(0, Radius, 0); + // LinesOut.Add(FRenderableLine( + // ElemTransform.TransformPosition((FVector)Top + DY), + // ElemTransform.TransformPosition((FVector)Bottom + DY), CapsuleColor, LineThickness, DepthBias)); + // } + //}); +} + + + + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/PhysicsInspectorTool.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/PhysicsInspectorTool.cpp new file mode 100644 index 000000000000..4e378f6e4b2c --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/PhysicsInspectorTool.cpp @@ -0,0 +1,155 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Physics/PhysicsInspectorTool.h" +#include "InteractiveToolManager.h" +#include "ToolBuilderUtil.h" +#include "ToolSetupUtil.h" +#include "Drawing/PreviewGeometryActor.h" + +#include "Physics/PhysicsDataCollection.h" +#include "Physics/CollisionGeometryVisualization.h" + +// physics data +#include "Engine/Classes/Engine/StaticMesh.h" +#include "Engine/Classes/Components/StaticMeshComponent.h" +#include "Engine/Classes/PhysicsEngine/BodySetup.h" +#include "Engine/Classes/PhysicsEngine/AggregateGeom.h" + + +#define LOCTEXT_NAMESPACE "UPhysicsInspectorTool" + + + +bool UPhysicsInspectorToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const +{ + int32 NumStaticMeshes = ToolBuilderUtil::CountComponents(SceneState, [&](UActorComponent* Comp) { return Cast(Comp) != nullptr; }); + int32 NumComponentTargets = ToolBuilderUtil::CountComponents(SceneState, CanMakeComponentTarget); + return (NumStaticMeshes > 0 && NumStaticMeshes == NumComponentTargets); +} + + +UInteractiveTool* UPhysicsInspectorToolBuilder::BuildTool(const FToolBuilderState& SceneState) const +{ + UPhysicsInspectorTool* NewTool = NewObject(SceneState.ToolManager); + + TArray ValidComponents = ToolBuilderUtil::FindAllComponents(SceneState, + [&](UActorComponent* Comp) { return Cast(Comp) != nullptr; }); + check(ValidComponents.Num() > 0); + + TArray> ComponentTargets; + for (UActorComponent* ActorComponent : ValidComponents) + { + UStaticMeshComponent* MeshComponent = Cast(ActorComponent); + if (MeshComponent) + { + ComponentTargets.Add(MakeComponentTarget(MeshComponent)); + } + } + + NewTool->SetSelection(MoveTemp(ComponentTargets)); + return NewTool; +} + + +void UPhysicsInspectorTool::Setup() +{ + UInteractiveTool::Setup(); + + VizSettings = NewObject(this); + VizSettings->RestoreProperties(this); + AddToolPropertySource(VizSettings); + VizSettings->WatchProperty(VizSettings->LineThickness, [this](float NewValue) { bVisualizationDirty = true; }); + VizSettings->WatchProperty(VizSettings->Color, [this](FColor NewValue) { bVisualizationDirty = true; }); + + for (TUniquePtr& ComponentTarget : ComponentTargets) + { + const UStaticMeshComponent* Component = CastChecked(ComponentTarget->GetOwnerComponent()); + const UStaticMesh* StaticMesh = Component->GetStaticMesh(); + if (ensure(StaticMesh && StaticMesh->BodySetup)) + { + TSharedPtr PhysicsData = MakeShared(); + PhysicsData->SourceComponent = Component; + PhysicsData->BodySetup = StaticMesh->BodySetup; + PhysicsData->AggGeom = StaticMesh->BodySetup->AggGeom; + + PhysicsInfos.Add(PhysicsData); + + UPreviewGeometry* PreviewGeom = NewObject(this); + FTransform TargetTransform = ComponentTarget->GetWorldTransform(); + PhysicsData->ExternalScale3D = TargetTransform.GetScale3D(); + TargetTransform.SetScale3D(FVector::OneVector); + PreviewGeom->CreateInWorld(ComponentTarget->GetOwnerActor()->GetWorld(), TargetTransform); + PreviewElements.Add(PreviewGeom); + + InitializeGeometry(*PhysicsData, PreviewGeom); + + UPhysicsObjectToolPropertySet* ObjectProps = NewObject(this); + InitializeObjectProperties(*PhysicsData, ObjectProps); + AddToolPropertySource(ObjectProps); + } + } + + + GetToolManager()->DisplayMessage( + LOCTEXT("OnStartTool", "Inspect Physics data for the seleced Static Meshes"), + EToolMessageLevel::UserNotification); +} + + +void UPhysicsInspectorTool::Shutdown(EToolShutdownType ShutdownType) +{ + VizSettings->SaveProperties(this); + + for (UPreviewGeometry* Preview : PreviewElements) + { + Preview->Disconnect(); + } +} + + + + + +void UPhysicsInspectorTool::OnTick(float DeltaTime) +{ + if (bVisualizationDirty) + { + UpdateVisualization(); + bVisualizationDirty = false; + } +} + + + +void UPhysicsInspectorTool::UpdateVisualization() +{ + float UseThickness = VizSettings->LineThickness; + FColor UseColor = VizSettings->Color; + for (UPreviewGeometry* Preview : PreviewElements) + { + Preview->UpdateAllLineSets([&](ULineSetComponent* LineSet) + { + LineSet->SetAllLinesThickness(UseThickness); + LineSet->SetAllLinesColor(UseColor); + }); + } +} + + + +void UPhysicsInspectorTool::InitializeObjectProperties(const FPhysicsDataCollection& PhysicsData, UPhysicsObjectToolPropertySet* PropSet) +{ + UE::PhysicsTools::InitializePhysicsToolObjectPropertySet(&PhysicsData, PropSet); +} + + + +void UPhysicsInspectorTool::InitializeGeometry(const FPhysicsDataCollection& PhysicsData, UPreviewGeometry* PreviewGeom) +{ + UE::PhysicsTools::InitializePreviewGeometryLines(PhysicsData, PreviewGeom, + VizSettings->Color, VizSettings->LineThickness, 0.0f, 16); +} + + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/SetCollisionGeometryTool.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/SetCollisionGeometryTool.cpp new file mode 100644 index 000000000000..5fdf538532ab --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Physics/SetCollisionGeometryTool.cpp @@ -0,0 +1,461 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Physics/SetCollisionGeometryTool.h" +#include "InteractiveToolManager.h" +#include "ToolBuilderUtil.h" +#include "ToolSetupUtil.h" +#include "Drawing/PreviewGeometryActor.h" +#include "MeshDescriptionToDynamicMesh.h" +#include "MeshTransforms.h" +#include "DynamicMeshEditor.h" +#include "Selections/MeshConnectedComponents.h" +#include "DynamicSubmesh3.h" + +#include "ShapeApproximation/ShapeDetection3.h" +#include "ShapeApproximation/MeshSimpleShapeApproximation.h" + +#include "Physics/PhysicsDataCollection.h" +#include "Physics/CollisionGeometryVisualization.h" + +// physics data +#include "Engine/Classes/Engine/StaticMesh.h" +#include "Engine/Classes/Components/StaticMeshComponent.h" +#include "Engine/Classes/PhysicsEngine/BodySetup.h" + +#include "Async/ParallelFor.h" + +#define LOCTEXT_NAMESPACE "USetCollisionGeometryTool" + + + +bool USetCollisionGeometryToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const +{ + TArray Components = ToolBuilderUtil::FindAllComponents(SceneState, CanMakeComponentTarget); + return (Components.Num() > 0 && Cast(Components[Components.Num() - 1]) != nullptr); +} + + +UInteractiveTool* USetCollisionGeometryToolBuilder::BuildTool(const FToolBuilderState& SceneState) const +{ + USetCollisionGeometryTool* NewTool = NewObject(SceneState.ToolManager); + TArray Components = ToolBuilderUtil::FindAllComponents(SceneState, CanMakeComponentTarget); + TArray> ComponentTargets; + for (UActorComponent* ActorComponent : Components) + { + auto* MeshComponent = Cast(ActorComponent); + if (MeshComponent) + { + ComponentTargets.Add(MakeComponentTarget(MeshComponent)); + } + } + NewTool->SetSelection(MoveTemp(ComponentTargets)); + return NewTool; +} + + +void USetCollisionGeometryTool::Setup() +{ + UInteractiveTool::Setup(); + + // if we have one selection, use it as the source, otherwise use all but hte last selected mesh + bSourcesHidden = (ComponentTargets.Num() > 1); + if (ComponentTargets.Num() == 1) + { + SourceObjectIndices.Add(0); + } + else + { + for (int32 k = 0; k < ComponentTargets.Num() -1; ++k) + { + SourceObjectIndices.Add(k); + ComponentTargets[k]->SetOwnerVisibility(false); + } + } + + bInputMeshesValid = true; + + TUniquePtr& CollisionTarget = ComponentTargets[ComponentTargets.Num() - 1]; + PreviewGeom = NewObject(this); + FTransform PreviewTransform = CollisionTarget->GetWorldTransform(); + OrigTargetTransform = PreviewTransform; + TargetScale3D = PreviewTransform.GetScale3D(); + PreviewTransform.SetScale3D(FVector::OneVector); + PreviewGeom->CreateInWorld(CollisionTarget->GetOwnerActor()->GetWorld(), PreviewTransform); + + // initialize initial collision object + InitialCollision = MakeShared(); + InitialCollision->InitializeFromComponent(CollisionTarget->GetOwnerComponent(), true); + InitialCollision->ExternalScale3D = TargetScale3D; + + // create tool options + Settings = NewObject(this); + Settings->RestoreProperties(this); + AddToolPropertySource(Settings); + Settings->bUseWorldSpace = (SourceObjectIndices.Num() > 1); + Settings->WatchProperty(Settings->InputMode, [this](ESetCollisionGeometryInputMode) { bResultValid = false; }); + Settings->WatchProperty(Settings->GeometryType, [this](ECollisionGeometryType) { bResultValid = false; }); + Settings->WatchProperty(Settings->bUseWorldSpace, [this](bool) { bInputMeshesValid = false; }); + Settings->WatchProperty(Settings->bAppendToExisting, [this](bool) { bResultValid = false; }); + Settings->WatchProperty(Settings->bRemoveContained, [this](bool) { bResultValid = false; }); + Settings->WatchProperty(Settings->bEnableMaxCount, [this](bool) { bResultValid = false; }); + Settings->WatchProperty(Settings->MaxCount, [this](int32) { bResultValid = false; }); + Settings->WatchProperty(Settings->MinThickness, [this](float) { bResultValid = false; }); + Settings->WatchProperty(Settings->bDetectBoxes, [this](int32) { bResultValid = false; }); + Settings->WatchProperty(Settings->bDetectSpheres, [this](int32) { bResultValid = false; }); + Settings->WatchProperty(Settings->bDetectCapsules, [this](int32) { bResultValid = false; }); + Settings->WatchProperty(Settings->bSimplifyHulls, [this](bool) { bResultValid = false; }); + Settings->WatchProperty(Settings->HullTargetFaceCount, [this](int32) { bResultValid = false; }); + Settings->WatchProperty(Settings->bSimplifyPolygons, [this](bool) { bResultValid = false; }); + Settings->WatchProperty(Settings->HullTolerance, [this](float) { bResultValid = false; }); + Settings->WatchProperty(Settings->SweepAxis, [this](EProjectedHullAxis) { bResultValid = false; }); + + bResultValid = false; + + VizSettings = NewObject(this); + VizSettings->RestoreProperties(this); + AddToolPropertySource(VizSettings); + Settings->WatchProperty(VizSettings->LineThickness, [this](float NewValue) { bVisualizationDirty = true; }); + Settings->WatchProperty(VizSettings->Color, [this](FColor NewValue) { bVisualizationDirty = true; }); + + // add option for collision properties + CollisionProps = NewObject(this); + AddToolPropertySource(CollisionProps); + + GetToolManager()->DisplayMessage( + LOCTEXT("OnStartTool", "Initialize Simple Collision geometry for a Mesh from one or more input Meshes (including itself)."), + EToolMessageLevel::UserNotification); +} + + +void USetCollisionGeometryTool::Shutdown(EToolShutdownType ShutdownType) +{ + VizSettings->SaveProperties(this); + Settings->SaveProperties(this); + PreviewGeom->Disconnect(); + + // show hidden sources + if (bSourcesHidden) + { + for (int32 k : SourceObjectIndices) + { + ComponentTargets[k]->SetOwnerVisibility(true); + } + } + + if (ShutdownType == EToolShutdownType::Accept) + { + GetToolManager()->BeginUndoTransaction(LOCTEXT("UpdateCollision", "Update Collision")); + + // code below derived from FStaticMeshEditor::DuplicateSelectedPrims() + + TUniquePtr& CollisionTarget = ComponentTargets[ComponentTargets.Num() - 1]; + UStaticMeshComponent* StaticMeshComponent = CastChecked(CollisionTarget->GetOwnerComponent()); + UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh(); + UBodySetup* BodySetup = StaticMesh->BodySetup; + + // mark the BodySetup for modification. Do we need to modify the UStaticMesh?? + BodySetup->Modify(); + + //Clear the cache (PIE may have created some data), create new GUID (comment from StaticMeshEditor) + BodySetup->InvalidatePhysicsData(); + + BodySetup->RemoveSimpleCollision(); + BodySetup->AggGeom = GeneratedCollision->AggGeom; + // update collision type + BodySetup->CollisionTraceFlag = (ECollisionTraceFlag)(int32)Settings->SetCollisionType; + // rebuild physics data + BodySetup->InvalidatePhysicsData(); + BodySetup->CreatePhysicsMeshes(); + + // do we need to do a post edit change here?? + + // is this necessary? + StaticMesh->CreateNavCollision(/*bIsUpdate=*/true); + + // post the undo transaction + GetToolManager()->EndUndoTransaction(); + + // mark static mesh as dirty so it gets resaved? + StaticMesh->MarkPackageDirty(); + } + +} + + + + +void USetCollisionGeometryTool::OnTick(float DeltaTime) +{ + if (bInputMeshesValid == false) + { + PrecomputeInputMeshes(); + bInputMeshesValid = true; + bResultValid = false; + } + + if (bResultValid == false) + { + UpdateGeneratedCollision(); + bResultValid = true; + } + + if (bVisualizationDirty) + { + UpdateVisualization(); + bVisualizationDirty = false; + } +} + + +void USetCollisionGeometryTool::UpdateVisualization() +{ + float UseThickness = VizSettings->LineThickness; + FColor UseColor = VizSettings->Color; + PreviewGeom->UpdateAllLineSets([&](ULineSetComponent* LineSet) + { + LineSet->SetAllLinesThickness(UseThickness); + LineSet->SetAllLinesColor(UseColor); + }); +} + + +void USetCollisionGeometryTool::UpdateGeneratedCollision() +{ + // calculate new collision + ECollisionGeometryType ComputeType = Settings->GeometryType; + + + TSharedPtr NewCollision = MakeShared(); + NewCollision->InitializeFromExisting(*InitialCollision); + if (Settings->bAppendToExisting || ComputeType == ECollisionGeometryType::KeepExisting) + { + NewCollision->CopyGeometryFromExisting(*InitialCollision); + } + + TSharedPtr UseShapeGenerator = GetApproximator(Settings->InputMode); + + UseShapeGenerator->bDetectSpheres = Settings->bDetectSpheres; + UseShapeGenerator->bDetectBoxes = Settings->bDetectBoxes; + UseShapeGenerator->bDetectCapsules = Settings->bDetectCapsules; + //UseShapeGenerator->bDetectConvexes = Settings->bDetectConvexes; + + UseShapeGenerator->MinDimension = Settings->MinThickness; + + switch (ComputeType) + { + case ECollisionGeometryType::KeepExisting: + case ECollisionGeometryType::None: + break; + case ECollisionGeometryType::AlignedBoxes: + UseShapeGenerator->Generate_AlignedBoxes(NewCollision->Geometry); + break; + case ECollisionGeometryType::OrientedBoxes: + UseShapeGenerator->Generate_OrientedBoxes(NewCollision->Geometry); + break; + case ECollisionGeometryType::MinimalSpheres: + UseShapeGenerator->Generate_MinimalSpheres(NewCollision->Geometry); + break; + case ECollisionGeometryType::Capsules: + UseShapeGenerator->Generate_Capsules(NewCollision->Geometry); + break; + case ECollisionGeometryType::ConvexHulls: + UseShapeGenerator->bSimplifyHulls = Settings->bSimplifyHulls; + UseShapeGenerator->HullTargetFaceCount = Settings->HullTargetFaceCount; + UseShapeGenerator->Generate_ConvexHulls(NewCollision->Geometry); + break; + case ECollisionGeometryType::SweptHulls: + UseShapeGenerator->bSimplifyHulls = Settings->bSimplifyPolygons; + UseShapeGenerator->HullSimplifyTolerance = Settings->HullTolerance; + UseShapeGenerator->Generate_ProjectedHulls(NewCollision->Geometry, + (FMeshSimpleShapeApproximation::EProjectedHullAxisMode)(int32)Settings->SweepAxis); + break; + case ECollisionGeometryType::MinVolume: + UseShapeGenerator->Generate_MinVolume(NewCollision->Geometry); + break; + } + + + if (!NewCollision) + { + ensure(false); + return; + } + GeneratedCollision = NewCollision; + + if (Settings->bRemoveContained) + { + GeneratedCollision->Geometry.RemoveContainedGeometry(); + } + + bool bUseMaxCount = (Settings->bEnableMaxCount); + if (bUseMaxCount) + { + GeneratedCollision->Geometry.FilterByVolume(Settings->MaxCount); + } + + GeneratedCollision->CopyGeometryToAggregate(); + + // update visualization + PreviewGeom->RemoveAllLineSets(); + UE::PhysicsTools::InitializePreviewGeometryLines(*GeneratedCollision, PreviewGeom, + VizSettings->Color, VizSettings->LineThickness, 0.0f, 16); + + // update property set + CollisionProps->Reset(); + UE::PhysicsTools::InitializePhysicsToolObjectPropertySet(GeneratedCollision.Get(), CollisionProps); +} + + + + + +void USetCollisionGeometryTool::InitializeDerivedMeshSet( + const TArray>& FromInputMeshes, + TArray>& ToMeshes, + TFunctionRef TrisConnectedPredicate) +{ + // find connected-components on input meshes, under given connectivity predicate + TArray> ComponentSets; + ComponentSets.SetNum(FromInputMeshes.Num()); + ParallelFor(FromInputMeshes.Num(), [&](int32 k) + { + const FDynamicMesh3* Mesh = FromInputMeshes[k].Get(); + ComponentSets[k] = MakeUnique(Mesh); + ComponentSets[k]->FindConnectedTriangles( + [Mesh, &TrisConnectedPredicate](int32 Tri0, int32 Tri1) + { + return TrisConnectedPredicate(Mesh, Tri0, Tri1); + } + ); + }); + + // Assemble a list of all the submeshes we want to compute, so we can do them all in parallel + struct FSubmeshSource + { + const FDynamicMesh3* SourceMesh; + FIndex2i ComponentIdx; + }; + TArray AllSubmeshes; + for (int32 k = 0; k < FromInputMeshes.Num(); ++k) + { + const FDynamicMesh3* Mesh = FromInputMeshes[k].Get(); + int32 NumComponents = ComponentSets[k]->Num(); + for ( int32 j = 0; j < NumComponents; ++j ) + { + const FMeshConnectedComponents::FComponent& Component = ComponentSets[k]->GetComponent(j); + if (Component.Indices.Num() > 1) // ignore single triangles + { + AllSubmeshes.Add(FSubmeshSource{ Mesh, FIndex2i(k,j) }); + } + } + } + + + // compute all the submeshes + ToMeshes.Reset(); + ToMeshes.SetNum(AllSubmeshes.Num()); + ParallelFor(AllSubmeshes.Num(), [&](int32 k) + { + const FSubmeshSource& Source = AllSubmeshes[k]; + const FMeshConnectedComponents::FComponent& Component = ComponentSets[Source.ComponentIdx.A]->GetComponent(Source.ComponentIdx.B); + FDynamicSubmesh3 Submesh(Source.SourceMesh, Component.Indices, (int32)EMeshComponents::None, false); + ToMeshes[k] = MakeShared( MoveTemp(Submesh.GetSubmesh()) ); + }); +} + + +template +TArray MakeRawPointerList(const TArray>& InputList) +{ + TArray Result; + Result.Reserve(InputList.Num()); + for (const TSharedPtr& Ptr : InputList) + { + Result.Add(Ptr.Get()); + } + return MoveTemp(Result); +} + + +void USetCollisionGeometryTool::PrecomputeInputMeshes() +{ + TUniquePtr& CollisionTarget = ComponentTargets[ComponentTargets.Num()-1]; + FTransform3d TargetTransform(CollisionTarget->GetWorldTransform()); + FTransform3d TargetTransformInv = TargetTransform.Inverse(); + + InputMeshes.Reset(); + InputMeshes.SetNum(SourceObjectIndices.Num()); + ParallelFor(SourceObjectIndices.Num(), [&](int32 k) + { + FMeshDescriptionToDynamicMesh Converter; + Converter.bCalculateMaps = false; + Converter.bDisableAttributes = true; + FDynamicMesh3 SourceMesh; + Converter.Convert(ComponentTargets[k]->GetMesh(), SourceMesh); + if (Settings->bUseWorldSpace) + { + FTransform3d ToWorld(ComponentTargets[k]->GetWorldTransform()); + MeshTransforms::ApplyTransform(SourceMesh, ToWorld); + MeshTransforms::ApplyTransform(SourceMesh, TargetTransformInv); + } + SourceMesh.DiscardAttributes(); + InputMeshes[k] = MakeShared(MoveTemp(SourceMesh)); + }); + InputMeshesApproximator = MakeShared(); + InputMeshesApproximator->InitializeSourceMeshes(MakeRawPointerList(InputMeshes)); + + + // build combined input + CombinedInputMeshes.Reset(); + FDynamicMesh3 CombinedMesh; + CombinedMesh.EnableTriangleGroups(); + FDynamicMeshEditor Appender(&CombinedMesh); + FMeshIndexMappings TmpMappings; + for (const TSharedPtr& InputMesh : InputMeshes) + { + TmpMappings.Reset(); + Appender.AppendMesh(InputMesh.Get(), TmpMappings); + } + CombinedInputMeshes.Add( MakeShared(MoveTemp(CombinedMesh)) ); + CombinedInputMeshesApproximator = MakeShared(); + CombinedInputMeshesApproximator->InitializeSourceMeshes(MakeRawPointerList(CombinedInputMeshes)); + + // build separated input meshes + SeparatedInputMeshes.Reset(); + InitializeDerivedMeshSet(InputMeshes, SeparatedInputMeshes, + [&](const FDynamicMesh3* Mesh, int32 Tri0, int32 Tri1) { return true; }); + SeparatedMeshesApproximator = MakeShared(); + SeparatedMeshesApproximator->InitializeSourceMeshes(MakeRawPointerList(SeparatedInputMeshes)); + + // build per-group input meshes + PerGroupInputMeshes.Reset(); + InitializeDerivedMeshSet(InputMeshes, PerGroupInputMeshes, + [&](const FDynamicMesh3* Mesh, int32 Tri0, int32 Tri1) { return Mesh->GetTriangleGroup(Tri0) == Mesh->GetTriangleGroup(Tri1); }); + PerGroupMeshesApproximator = MakeShared(); + PerGroupMeshesApproximator->InitializeSourceMeshes(MakeRawPointerList(PerGroupInputMeshes)); + +} + + +TSharedPtr& USetCollisionGeometryTool::GetApproximator(ESetCollisionGeometryInputMode MeshSetMode) +{ + if (MeshSetMode == ESetCollisionGeometryInputMode::CombineAll) + { + return CombinedInputMeshesApproximator; + } + else if ( MeshSetMode == ESetCollisionGeometryInputMode::PerMeshComponent) + { + return SeparatedMeshesApproximator; + } + else if (MeshSetMode == ESetCollisionGeometryInputMode::PerMeshGroup) + { + return PerGroupMeshesApproximator; + } + else + { + return InputMeshesApproximator; + } +} + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Sculpting/KelvinletBrushOp.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Sculpting/KelvinletBrushOp.h index 34d7fccd39b7..d8743c502922 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Sculpting/KelvinletBrushOp.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/Sculpting/KelvinletBrushOp.h @@ -5,8 +5,455 @@ #include "MatrixTypes.h" #include "DynamicMesh3.h" #include "Deformers/Kelvinlets.h" +#include "Sculpting/MeshSculptToolBase.h" +#include "Sculpting/MeshBrushOpBase.h" +#include "Async/ParallelFor.h" +#include "KelvinletBrushOp.generated.h" + +UCLASS() +class MESHMODELINGTOOLS_API UBaseKelvinletBrushOpProps : public UMeshSculptBrushOpProps +{ + GENERATED_BODY() + +public: + + /** How much the mesh resists shear */ + UPROPERTY() + float Stiffness = 1.f; + + /** How compressible the spatial region is: 1 - 2 x Poisson ratio */ + UPROPERTY() + float Incompressiblity = 1.f; + + /** Integration steps*/ + UPROPERTY() + int32 BrushSteps = 3; + + virtual float GetStiffness() { return Stiffness; } + virtual float GetIncompressiblity() { return Incompressiblity; } + virtual int32 GetNumSteps() { return BrushSteps; } + +}; + + + + + +class FBaseKelvinletBrushOp : public FMeshSculptBrushOp +{ +public: + + void SetBaseProperties(const FDynamicMesh3* SrcMesh, const FSculptBrushStamp& Stamp) + { + // pointer to mesh + Mesh = SrcMesh; + UBaseKelvinletBrushOpProps* BaseKelvinletBrushOpProps = GetPropertySetAs(); + + float Stiffness = BaseKelvinletBrushOpProps->GetStiffness(); + float Incompressibility = BaseKelvinletBrushOpProps->GetIncompressiblity(); + int NumIntSteps = BaseKelvinletBrushOpProps->GetNumSteps(); + + Mu = FMath::Max(Stiffness, 0.f); + Nu = FMath::Clamp(0.5f * (1.f - 2.f * Incompressibility), 0.f, 0.5f); + + Size = FMath::Max(Stamp.Radius, 0.01); + + NumSteps = NumIntSteps; + } + + + template + void ApplyKelvinlet(const KelvinletType& Kelvinlet, const FFrame3d& LocalFrame, const TArray& Vertices, TArray& NewPositionsOut) const + { + + if (NumSteps == 0) + { + DisplaceKelvinlet(Kelvinlet, LocalFrame, Vertices, NewPositionsOut); + } + else + { + IntegrateKelvinlet(Kelvinlet, LocalFrame, Vertices, NewPositionsOut, IntegrationTime, NumSteps); + } + } + + + + virtual void ApplyStamp(const FDynamicMesh3*SrcMesh, const FSculptBrushStamp& Stamp, const TArray& Vertices, TArray& NewPositionsOut) override {} + +protected: + + void ApplyFalloff(const FSculptBrushStamp& Stamp, const TArray& Vertices, TArray& NewPositionsOut) const + { + int32 NumV = Vertices.Num(); + bool bParallel = true; + ParallelFor(NumV, [&Stamp, &Vertices, &NewPositionsOut, this](int32 k) + { + int32 VertIdx = Vertices[k]; + FVector3d OrigPos = Mesh->GetVertex(VertIdx); + + double Falloff = GetFalloff().Evaluate(Stamp, OrigPos); + + double Alpha = FMath::Clamp(Falloff, 0., 1.); + + NewPositionsOut[k] = Alpha * NewPositionsOut[k] + (1. - Alpha) * OrigPos; + }, bParallel); + } + + + // NB: this just moves the verts, but doesn't update the normal. The kelvinlets will have to be extended if we want + // to do the Jacobian Transpose operation on the normals - but for now, we should just rebuild the normals after the brush + template + void DisplaceKelvinlet(const KelvinletType& Kelvinlet, const FFrame3d& LocalFrame, const TArray& Vertices, TArray& NewPositionsOut) const + { + int32 NumV = Vertices.Num(); + NewPositionsOut.SetNum(NumV, false); + + const bool bForceSingleThread = false; + + ParallelFor(NumV, [&Kelvinlet, &Vertices, &NewPositionsOut, &LocalFrame, this](int32 k) + { + int VertIdx = Vertices[k]; + FVector3d Pos = Mesh->GetVertex(VertIdx); + Pos = LocalFrame.ToFramePoint(Pos); + + Pos = Kelvinlet.Evaluate(Pos) + Pos; + + // Update the position in the ROI Array + NewPositionsOut[k] = LocalFrame.FromFramePoint(Pos); + } + , bForceSingleThread); + + } + + template + void IntegrateKelvinlet(const KelvinletType& Kelvinlet, const FFrame3d& LocalFrame, const TArray& Vertices, TArray& NewPositionsOut, const double Dt, const int32 Steps) const + { + int NumV = Vertices.Num(); + NewPositionsOut.SetNum(NumV, false); + + const bool bForceSingleThread = false; + + ParallelFor(NumV, [&Kelvinlet, &Vertices, &NewPositionsOut, &LocalFrame, Dt, Steps, this](int32 k) + { + int VertIdx = Vertices[k]; + FVector3d Pos = LocalFrame.ToFramePoint(Mesh->GetVertex(VertIdx)); + + double TimeScale = 1. / (Steps); + // move with several time steps + for (int i = 0; i < Steps; ++i) + { + // the position after deformation + Pos = Kelvinlet.IntegrateRK3(Pos, Dt * TimeScale); + } + // Update the position in the ROI Array + NewPositionsOut[k] = LocalFrame.FromFramePoint(Pos); + }, bForceSingleThread); + + + } + +protected: + + + // physical properties. + float Mu; + float Nu; + // model regularization parameter + float Size; + + // integration parameters + int32 NumSteps; + double IntegrationTime = 1.; + + const FDynamicMesh3* Mesh; +}; + + + + + +UCLASS() +class MESHMODELINGTOOLS_API UScaleKelvinletBrushOpProps : public UBaseKelvinletBrushOpProps +{ + GENERATED_BODY() + +public: + /** Strength of the Brush */ + UPROPERTY(EditAnywhere, Category = KelvinScaleBrush, meta = (DisplayName = "Strength", UIMin = "0.0", UIMax = "10.", ClampMin = "0.0", ClampMax = "10.")) + float Strength = 0.5; + + /** Amount of falloff to apply */ + UPROPERTY(EditAnywhere, Category = KelvinScaleBrush, meta = (DisplayName = "Falloff", UIMin = "0.0", UIMax = "1.0", ClampMin = "0.0", ClampMax = "1.0")) + float Falloff = 0.5; + + + virtual float GetStrength() override { return Strength; } + virtual float GetFalloff() override { return Falloff; } +}; + +class FScaleKelvinletBrushOp : public FBaseKelvinletBrushOp +{ + +public: + + virtual void ApplyStamp(const FDynamicMesh3* SrcMesh, const FSculptBrushStamp& Stamp, const TArray& Vertices, TArray& NewPositionsOut) override + { + SetBaseProperties(SrcMesh, Stamp); + + float Strength = GetPropertySetAs()->GetStrength(); + + float Speed = Strength * 0.025 * FMath::Sqrt(Stamp.Radius) * Stamp.Direction; + FScaleKelvinlet ScaleKelvinlet(Speed, 0.35 * Size, Mu, Nu); + + ApplyKelvinlet(ScaleKelvinlet, Stamp.LocalFrame, Vertices, NewPositionsOut); + + ApplyFalloff(Stamp, Vertices, NewPositionsOut); + } + +}; + +UCLASS() +class MESHMODELINGTOOLS_API UPullKelvinletBrushOpProps : public UBaseKelvinletBrushOpProps +{ + GENERATED_BODY() +public: + + /** Amount of falloff to apply */ + UPROPERTY(EditAnywhere, Category = KelvinGrabBrush, meta = (DisplayName = "Falloff", UIMin = "0.0", UIMax = "1.0", ClampMin = "0.0", ClampMax = "1.0")) + float Falloff = 0.1; + + /** Depth of Brush into surface along view ray */ + UPROPERTY(EditAnywhere, Category = KelvinGrabBrush, meta = (UIMin = "-0.5", UIMax = "0.5", ClampMin = "-1.0", ClampMax = "1.0")) + float Depth = 0; + + // these get routed into the Stamp + virtual float GetFalloff() override { return Falloff; } + virtual float GetDepth() override { return Depth; } +}; + +class FPullKelvinletBrushOp : public FBaseKelvinletBrushOp +{ + +public: + + virtual void ApplyStamp(const FDynamicMesh3* SrcMesh, const FSculptBrushStamp& Stamp, const TArray& Vertices, TArray& NewPositionsOut) override + { + SetBaseProperties(SrcMesh, Stamp); + + // compute the displacement vector in the local frame of the stamp + FVector3d Force = Stamp.LocalFrame.ToFrameVector(Stamp.LocalFrame.Origin - Stamp.PrevLocalFrame.Origin); + Size *= 0.6; + + FLaplacianPullKelvinlet LaplacianPullKelvinlet(Force, Size, Mu, Nu); + FBiLaplacianPullKelvinlet BiLaplacianPullKelvinlet(Force, Size, Mu, Nu); + + const double Alpha = Stamp.Falloff; + // Lerp between a broad and a narrow kelvinlet based on the fall-off + FBlendPullKelvinlet BlendPullKelvinlet(BiLaplacianPullKelvinlet, LaplacianPullKelvinlet, Alpha); + ApplyKelvinlet(BlendPullKelvinlet, Stamp.LocalFrame, Vertices, NewPositionsOut); + + ApplyFalloff(Stamp, Vertices, NewPositionsOut); + } + + virtual ESculptBrushOpTargetType GetBrushTargetType() const override + { + return ESculptBrushOpTargetType::ActivePlane; + } + + virtual bool IgnoreZeroMovements() const override + { + return true; + } +}; + +UCLASS() +class MESHMODELINGTOOLS_API USharpPullKelvinletBrushOpProps : public UBaseKelvinletBrushOpProps +{ + GENERATED_BODY() +public: + + /** Amount of falloff to apply */ + UPROPERTY(EditAnywhere, Category = KelvinSharpGrabBrush, meta = (DisplayName = "Falloff", UIMin = "0.0", UIMax = "1.0", ClampMin = "0.0", ClampMax = "1.0")) + float Falloff = 0.5; + + /** Depth of Brush into surface along view ray */ + UPROPERTY(EditAnywhere, Category = KelvinSharpGrabBrush, meta = (UIMin = "-0.5", UIMax = "0.5", ClampMin = "-1.0", ClampMax = "1.0")) + float Depth = 0; + + // these get routed into the Stamp + virtual float GetFalloff() override { return Falloff; } + virtual float GetDepth() override { return Depth; } +}; + +class FSharpPullKelvinletBrushOp : public FBaseKelvinletBrushOp +{ + +public: + + virtual void ApplyStamp(const FDynamicMesh3* SrcMesh, const FSculptBrushStamp& Stamp, const TArray& Vertices, TArray& NewPositionsOut) override + { + SetBaseProperties(SrcMesh, Stamp); + + // compute the displacement vector in the local frame of the stamp + FVector3d Force = Stamp.LocalFrame.ToFrameVector(Stamp.LocalFrame.Origin - Stamp.PrevLocalFrame.Origin); + + FSharpLaplacianPullKelvinlet SharpLaplacianPullKelvinlet(Force, Size, Mu, Nu); + FSharpBiLaplacianPullKelvinlet SharpBiLaplacianPullKelvinlet(Force, Size, Mu, Nu); + + const double Alpha = Stamp.Falloff; + // Lerp between a broad and a narrow Kelvinlet based on the fall-off + FBlendPullSharpKelvinlet BlendPullSharpKelvinlet(SharpBiLaplacianPullKelvinlet, SharpLaplacianPullKelvinlet, Alpha); + + ApplyKelvinlet(BlendPullSharpKelvinlet, Stamp.LocalFrame, Vertices, NewPositionsOut); + + ApplyFalloff(Stamp, Vertices, NewPositionsOut); + } + + virtual ESculptBrushOpTargetType GetBrushTargetType() const override + { + return ESculptBrushOpTargetType::ActivePlane; + } + + virtual bool IgnoreZeroMovements() const override + { + return true; + } + +}; + +class FLaplacianPullKelvinletBrushOp : public FBaseKelvinletBrushOp +{ +public: + + virtual void ApplyStamp(const FDynamicMesh3* SrcMesh, const FSculptBrushStamp& Stamp, const TArray& Vertices, TArray& NewPositionsOut) override + { + SetBaseProperties(SrcMesh, Stamp); + + // compute the displacement vector in the local frame of the stamp + FVector3d Force = Stamp.LocalFrame.ToFrameVector(Stamp.LocalFrame.Origin - Stamp.PrevLocalFrame.Origin); + + FLaplacianPullKelvinlet PullKelvinlet(Force, Size, Mu, Nu); + ApplyKelvinlet(PullKelvinlet, Stamp.LocalFrame, Vertices, NewPositionsOut); + + ApplyFalloff(Stamp, Vertices, NewPositionsOut); + } + + virtual ESculptBrushOpTargetType GetBrushTargetType() const override + { + return ESculptBrushOpTargetType::ActivePlane; + } + + virtual bool IgnoreZeroMovements() const override + { + return true; + } +}; + +class FBiLaplacianPullKelvinletBrushOp : public FBaseKelvinletBrushOp +{ +public: + + virtual void ApplyStamp(const FDynamicMesh3* SrcMesh, const FSculptBrushStamp& Stamp, const TArray& Vertices, TArray& NewPositionsOut) override + { + SetBaseProperties(SrcMesh, Stamp); + + // compute the displacement vector in the local frame of the stamp + FVector3d Force = Stamp.LocalFrame.ToFrameVector(Stamp.LocalFrame.Origin - Stamp.PrevLocalFrame.Origin); + + FBiLaplacianPullKelvinlet PullKelvinlet(Force, Size, Mu, Nu); + ApplyKelvinlet(PullKelvinlet, Stamp.LocalFrame, Vertices, NewPositionsOut); + + ApplyFalloff(Stamp, Vertices, NewPositionsOut); + } + + virtual ESculptBrushOpTargetType GetBrushTargetType() const override + { + return ESculptBrushOpTargetType::ActivePlane; + } + + virtual bool IgnoreZeroMovements() const override + { + return true; + } + +}; + +UCLASS() +class MESHMODELINGTOOLS_API UTwistKelvinletBrushOpProps : public UBaseKelvinletBrushOpProps +{ + GENERATED_BODY() + +public: + /** Twisting strength of the Brush */ + UPROPERTY(EditAnywhere, Category = KelvinTwistBrush, meta = (DisplayName = "Strength", UIMin = "0.0", UIMax = "10.", ClampMin = "0.0", ClampMax = "10.")) + float Strength = 0.5; + + /** Amount of falloff to apply */ + UPROPERTY(EditAnywhere, Category = KelvinTwistBrush, meta = (DisplayName = "Falloff", UIMin = "0.0", UIMax = "1.0", ClampMin = "0.0", ClampMax = "1.0")) + float Falloff = 0.5; + + + virtual float GetStrength() override { return Strength; } + virtual float GetFalloff() override { return Falloff; } +}; + +class FTwistKelvinletBrushOp : public FBaseKelvinletBrushOp +{ +public: + + + virtual void ApplyStamp(const FDynamicMesh3* SrcMesh, const FSculptBrushStamp& Stamp, const TArray& Vertices, TArray& NewPositionsOut) override + { + SetBaseProperties(SrcMesh, Stamp); + + float Strength = GetPropertySetAs()->GetStrength(); + + float Speed = Strength * Stamp.Direction; + + // In the local frame, twist axis is in "z" direction + FVector3d LocalTwistAxis = FVector3d(0., 0., Speed); + + FTwistKelvinlet TwistKelvinlet(LocalTwistAxis, Size, Mu, Nu); + ApplyKelvinlet(TwistKelvinlet, Stamp.LocalFrame, Vertices, NewPositionsOut); + + ApplyFalloff(Stamp, Vertices, NewPositionsOut); + } + + virtual ESculptBrushOpTargetType GetBrushTargetType() const override + { + return ESculptBrushOpTargetType::ActivePlane; + } +}; + +class FPinchKelvinletBrushOp : public FBaseKelvinletBrushOp +{ +public: + virtual void ApplyStamp(const FDynamicMesh3* SrcMesh, const FSculptBrushStamp& Stamp, const TArray& Vertices, TArray& NewPositionsOut) override + { + // is this really the vector we want? + // compute the displacement vector in the local frame of the stamp + FVector3d Dir = Stamp.LocalFrame.ToFrameVector(Stamp.LocalFrame.Origin - Stamp.PrevLocalFrame.Origin); + + FMatrix3d ForceMatrix = CrossProductMatrix(Dir); + // make symmetric + ForceMatrix.Row0[1] = -ForceMatrix.Row0[1]; + ForceMatrix.Row0[2] = -ForceMatrix.Row0[2]; + ForceMatrix.Row1[2] = -ForceMatrix.Row1[2]; + FPinchKelvinlet PinchKelvinlet(ForceMatrix, Size, Mu, Nu); + ApplyKelvinlet(PinchKelvinlet, Stamp.LocalFrame, Vertices, NewPositionsOut); + + ApplyFalloff(Stamp, Vertices, NewPositionsOut); + } +}; + + +/**--------------------- Glue Layer Currently Used in the DynamicMeshSculptTool: -------------------------- +* +* We should be able to remove these once the dynamic mesh sculpt tool starts using the new brush infrastructure ( used by the MeshVertexSculptTool) +*/ + enum class EKelvinletBrushMode { ScaleKelvinlet, @@ -27,9 +474,9 @@ public: struct FKelvinletBrushOpProperties { - FKelvinletBrushOpProperties(const EKelvinletBrushMode& BrushMode, const UKelvinBrushProperties& Properties, const UBrushBaseProperties& Brush) : - Mode(BrushMode), - Direction(1.0, 0.0, 0.0) + FKelvinletBrushOpProperties(const EKelvinletBrushMode& BrushMode, const UKelvinBrushProperties& Properties, const UBrushBaseProperties& Brush) + : Mode(BrushMode) + , Direction(1.0, 0.0, 0.0) { Speed = 0.; FallOff = Brush.BrushFalloffAmount; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/AddPrimitiveTool.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/AddPrimitiveTool.h index 543bf032e916..35977ded5071 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/AddPrimitiveTool.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/AddPrimitiveTool.h @@ -143,11 +143,11 @@ public: float Depth = 100.f; /** Number of Subdivisions Along the Width */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Width", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "4000", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Width", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "500", ProceduralShapeSetting)) int WidthSubdivisions = 1; /** Number of Subdivisions Along the Depth*/ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Depth", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "4000", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Depth", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "500", ProceduralShapeSetting)) int DepthSubdivisions = 1; }; @@ -163,7 +163,7 @@ public: float Height = 100.f; /** Number of Subdivisions Along the Height */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Height", UIMin = "0", UIMax = "100", ClampMin = "0", ClampMax = "4000", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Height", UIMin = "0", UIMax = "100", ClampMin = "0", ClampMax = "500", ProceduralShapeSetting)) int HeightSubdivisions = 1; }; @@ -177,7 +177,7 @@ public: float CornerRadius = 25.f; /** Number of Angular Slices in Each Corner*/ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Corner", UIMin = "3", UIMax = "128", ClampMin = "3", ClampMax = "999", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Corner", UIMin = "3", UIMax = "128", ClampMin = "3", ClampMax = "500", ProceduralShapeSetting)) int CornerSlices = 16; }; @@ -191,11 +191,11 @@ public: float Radius = 50.f; /** Number of Angular Slices around the Disc */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Radial", UIMin = "3", UIMax = "128", ClampMin = "3", ClampMax = "999", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Radial", UIMin = "3", UIMax = "128", ClampMin = "3", ClampMax = "500", ProceduralShapeSetting)) int RadialSlices = 16; /** Number of Radial Subdivisions in the Disc */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Radial", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "4000", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Radial", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "500", ProceduralShapeSetting)) int RadialSubdivisions = 1; }; @@ -223,11 +223,11 @@ public: float MinorRadius = 25.f; /** Number of Angular Slices Along the Torus Tube */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Major", UIMin = "3", UIMax = "128", ClampMin = "3", ClampMax = "999", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Major", UIMin = "3", UIMax = "128", ClampMin = "3", ClampMax = "500", ProceduralShapeSetting)) int TubeSlices = 16; /** Number of Angular Slices Around the Tube of the Torus */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Minor", UIMin = "3", UIMax = "128", ClampMin = "3", ClampMax = "999", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Minor", UIMin = "3", UIMax = "128", ClampMin = "3", ClampMax = "500", ProceduralShapeSetting)) int CrossSectionSlices = 16; }; @@ -246,11 +246,11 @@ public: float Height = 200.f; /** Number of Slices on the Cylinder Caps */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Radial", UIMin = "3", UIMax = "128", ClampMin = "3", ClampMax = "999", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Radial", UIMin = "3", UIMax = "128", ClampMin = "3", ClampMax = "500", ProceduralShapeSetting)) int RadialSlices = 16; /** Number of Vertical Subdivisions Along the Height of the Cylidner */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Height", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "4000", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Height", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "500", ProceduralShapeSetting)) int HeightSubdivisions = 1; }; @@ -268,11 +268,11 @@ public: float Height = 200.f; /** Number of Slices on the Cone Base */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Radial", UIMin = "3", UIMax = "128", ClampMin = "3", ClampMax = "999", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Radial", UIMin = "3", UIMax = "128", ClampMin = "3", ClampMax = "500", ProceduralShapeSetting)) int RadialSlices = 16; /** Number of Vertical Subdivisions Along the Hight of the Cone */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Height", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "4000", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Height", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "500", ProceduralShapeSetting)) int HeightSubdivisions = 1; }; @@ -298,11 +298,11 @@ public: float HeadHeight = 120.f; /** Number of Angular Slices Around the Arrow */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Radial Slices", UIMin = "3", UIMax = "100", ClampMin = "3", ClampMax = "4000", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Radial Slices", UIMin = "3", UIMax = "100", ClampMin = "3", ClampMax = "500", ProceduralShapeSetting)) int RadialSlices = 16; /** Number of Vertical Subdivisions Along in the Arrow */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Total", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "4000", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Total", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "500", ProceduralShapeSetting)) int TotalSubdivisions = 1; }; @@ -316,11 +316,11 @@ public: float Radius = 50.f; /** Number of Latitudinal Slices of the Sphere */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Latitude Slices", UIMin = "3", UIMax = "100", ClampMin = "4", ClampMax = "4000", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Latitude Slices", UIMin = "3", UIMax = "100", ClampMin = "4", ClampMax = "500", ProceduralShapeSetting)) int LatitudeSlices = 16; /** Number of Longitudinal Slices around the Sphere */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Longitude Slices", UIMin = "3", UIMax = "100", ClampMin = "4", ClampMax = "4000", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Slices", meta = (DisplayName = "Longitude Slices", UIMin = "3", UIMax = "100", ClampMin = "4", ClampMax = "500", ProceduralShapeSetting)) int LongitudeSlices = 16; }; @@ -334,7 +334,7 @@ public: float Radius = 50.f; /** Number of Subdivisions of each Side of the Sphere */ - UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Side", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "4000", ProceduralShapeSetting)) + UPROPERTY(EditAnywhere, Category = "ShapeSettings|Subdivisions", meta = (DisplayName = "Side", UIMin = "1", UIMax = "100", ClampMin = "1", ClampMax = "500", ProceduralShapeSetting)) int Subdivisions = 16; }; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/BakeMeshAttributeMapsTool.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/BakeMeshAttributeMapsTool.h index 590d88b8266f..7435de76cecb 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/BakeMeshAttributeMapsTool.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/BakeMeshAttributeMapsTool.h @@ -3,12 +3,11 @@ #pragma once #include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" +#include "Templates/PimplPtr.h" #include "MultiSelectionTool.h" #include "InteractiveToolBuilder.h" #include "DynamicMesh3.h" #include "DynamicMeshAABBTree3.h" -#include "Sampling/MeshSurfaceSampler.h" #include "Image/ImageDimensions.h" #include "BakeMeshAttributeMapsTool.generated.h" @@ -19,7 +18,7 @@ class USimpleDynamicMeshComponent; class UMaterialInstanceDynamic; class UTexture2D; template class TMeshTangents; - +class FMeshImageBakingCache; /** * @@ -37,6 +36,20 @@ public: }; + +UENUM() +enum class EBakeMapType +{ + TangentSpaceNormalMap, + AmbientOcclusion, + Curvature, + Texture2DImage, + NormalImage, + FaceNormalImage, + PositionImage +}; + + UENUM() enum class EBakeTextureResolution { @@ -60,16 +73,26 @@ class MESHMODELINGTOOLS_API UBakeMeshAttributeMapsToolProperties : public UInter public: - /** Control whether to compute/show Normal Map */ UPROPERTY(EditAnywhere, Category = MapSettings) - bool bNormalMap = true; - - /** Control whether to compute/show Ambient Occlusion Map */ - UPROPERTY(EditAnywhere, Category = MapSettings) - bool bAmbientOcclusionMap = true; + EBakeMapType MapType = EBakeMapType::TangentSpaceNormalMap; UPROPERTY(EditAnywhere, Category = MapSettings, meta = (TransientToolProperty)) EBakeTextureResolution Resolution = EBakeTextureResolution::Resolution256; + + UPROPERTY(EditAnywhere, Category = MapSettings, meta = (GetOptions = GetUVLayerNamesFunc)) + FString UVLayer; + + UFUNCTION() + TArray GetUVLayerNamesFunc(); + UPROPERTY(meta = (TransientToolProperty)) + TArray UVLayerNamesList; + + UPROPERTY(EditAnywhere, Category = MapSettings) + bool bUseWorldSpace = false; + + UPROPERTY(VisibleAnywhere, Category = MapSettings, meta = (TransientToolProperty)) + UTexture2D* Result; + }; @@ -79,8 +102,6 @@ class MESHMODELINGTOOLS_API UBakedNormalMapToolProperties : public UInteractiveT { GENERATED_BODY() public: - UPROPERTY(VisibleAnywhere, Category = NormalMap, meta = (TransientToolProperty)) - UTexture2D* Result; }; @@ -92,7 +113,7 @@ class MESHMODELINGTOOLS_API UBakedOcclusionMapToolProperties : public UInteracti public: /** Number of AO rays */ UPROPERTY(EditAnywhere, Category = OcclusionMap, meta = (UIMin = "1", UIMax = "1024", ClampMin = "0", ClampMax = "50000")) - int32 OcclusionRays = 128; + int32 OcclusionRays = 16; /** Maximum AO distance (0 = infinity) */ UPROPERTY(EditAnywhere, Category = OcclusionMap, meta = (UIMin = "0.0", UIMax = "1000.0", ClampMin = "0.0", ClampMax = "99999999.0")) @@ -109,10 +130,6 @@ public: /** Contribution of AO rays that are within this angle (degrees) from horizontal are attenuated. This reduces faceting artifacts. */ UPROPERTY(EditAnywhere, Category = OcclusionMap, meta = (UIMin = "0", UIMax = "45.0", ClampMin = "0", ClampMax = "89.9")) float BiasAngle = 15.0; - - UPROPERTY(VisibleAnywhere, Category = OcclusionMap, meta = (TransientToolProperty)) - UTexture2D* Result; - }; @@ -123,7 +140,7 @@ class MESHMODELINGTOOLS_API UBakedOcclusionMapVisualizationProperties : public U GENERATED_BODY() public: UPROPERTY(EditAnywhere, Category = Visualization, meta = (UIMin = "0.0", UIMax = "1.0")) - float BaseGrayLevel = 0.7; + float BaseGrayLevel = 1.0; /** AO Multiplier in visualization (does not affect output) */ UPROPERTY(EditAnywhere, Category = Visualization, meta = (UIMin = "0.0", UIMax = "1.0")) @@ -132,6 +149,79 @@ public: +UENUM() +enum class EBakedCurvatureTypeMode +{ + MeanAverage, + Gaussian, + Max, + Min +}; + +UENUM() +enum class EBakedCurvatureColorMode +{ + Grayscale, + RedBlue, + RedGreenBlue +}; + +UENUM() +enum class EBakedCurvatureClampMode +{ + None, + Positive, + Negative +}; + + + + +UCLASS() +class MESHMODELINGTOOLS_API UBakedCurvatureMapToolProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() +public: + UPROPERTY(EditAnywhere, Category = CurvatureMap) + EBakedCurvatureTypeMode CurvatureType = EBakedCurvatureTypeMode::MeanAverage; + + UPROPERTY(EditAnywhere, Category = CurvatureMap) + EBakedCurvatureColorMode ColorMode = EBakedCurvatureColorMode::Grayscale; + + UPROPERTY(EditAnywhere, Category = CurvatureMap, meta = (UIMin = "0.1", UIMax = "2.0", ClampMin = "0.001", ClampMax = "100.0")) + float RangeMultiplier = 1.0; + + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = CurvatureMap, meta = (UIMin = "0.0", UIMax = "1.0")) + float MinRangeMultiplier = 0.0; + + UPROPERTY(EditAnywhere, Category = CurvatureMap) + EBakedCurvatureClampMode Clamping = EBakedCurvatureClampMode::None; + + /** Whether or not to apply Gaussian Blur to computed Map */ + UPROPERTY(EditAnywhere, Category = CurvatureMap) + bool bGaussianBlur = false; + + /** Pixel Radius of Gaussian Blur Kernel */ + UPROPERTY(EditAnywhere, Category = CurvatureMap, meta = (UIMin = "0", UIMax = "10.0", ClampMin = "0", ClampMax = "100.0")) + float BlurRadius = 2.25; +}; + + + +UCLASS() +class MESHMODELINGTOOLS_API UBakedTexture2DImageProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() +public: + UPROPERTY(EditAnywhere, Category = Texture2D, meta = (TransientToolProperty)) + UTexture2D* SourceTexture; + + UPROPERTY(EditAnywhere, Category = Texture2D) + int32 UVLayer = 0; +}; + + + /** * Detail Map Baking Tool @@ -152,7 +242,7 @@ public: virtual void Render(IToolsContextRenderAPI* RenderAPI) override; virtual bool HasCancel() const override { return true; } - virtual bool HasAccept() const override; + virtual bool HasAccept() const override { return true; } virtual bool CanAccept() const override; protected: @@ -168,6 +258,12 @@ protected: UPROPERTY() UBakedOcclusionMapToolProperties* OcclusionMapProps; + UPROPERTY() + UBakedCurvatureMapToolProperties* CurvatureMapProps; + + UPROPERTY() + UBakedTexture2DImageProperties* Texture2DProps; + UPROPERTY() UBakedOcclusionMapVisualizationProperties* VisualizationProps; @@ -178,24 +274,43 @@ protected: USimpleDynamicMeshComponent* DynamicMeshComponent; + UPROPERTY() + UMaterialInstanceDynamic* PreviewMaterial; + TSharedPtr BaseMeshDescription; TSharedPtr> BaseMeshTangents; FDynamicMesh3 BaseMesh; FDynamicMeshAABBTree3 BaseSpatial; - FDynamicMesh3 DetailMesh; - FDynamicMeshAABBTree3 DetailSpatial; + bool bIsBakeToSelf = false; - void InvalidateOcclusion(); - void InvalidateNormals(); + TSharedPtr DetailMesh; + TSharedPtr DetailSpatial; + int32 DetailMeshTimestamp = 0; + void UpdateDetailMesh(); + bool bDetailMeshValid = false; bool bResultValid; void UpdateResult(); + void UpdateOnModeChange(); void UpdateVisualization(); - UPROPERTY() - UMaterialInstanceDynamic* PreviewMaterial; + TPimplPtr BakeCache; + struct FBakeCacheSettings + { + FImageDimensions Dimensions; + int32 UVLayer; + int32 DetailTimestamp; + + bool operator==(const FBakeCacheSettings& Other) const + { + return Dimensions == Other.Dimensions && UVLayer == Other.UVLayer && DetailTimestamp == Other.DetailTimestamp; + } + }; + FBakeCacheSettings CachedBakeCacheSettings; + + struct FNormalMapSettings { @@ -210,6 +325,10 @@ protected: UPROPERTY() UTexture2D* CachedNormalMap; + void UpdateResult_Normal(); + + + struct FOcclusionMapSettings { FImageDimensions Dimensions; @@ -227,11 +346,83 @@ protected: UPROPERTY() UTexture2D* CachedOcclusionMap; + void UpdateResult_Occlusion(); + + + + struct FCurvatureMapSettings + { + FImageDimensions Dimensions; + int32 RayCount = 1; + int32 CurvatureType = 0; + float RangeMultiplier = 1.0; + float MinRangeMultiplier = 0.0; + int32 ColorMode = 0; + int32 ClampMode = 0; + float MaxDistance = 1.0; + float BlurRadius = 1.0; + + bool operator==(const FCurvatureMapSettings& Other) const + { + return Dimensions == Other.Dimensions && RayCount == Other.RayCount && CurvatureType == Other.CurvatureType && RangeMultiplier == Other.RangeMultiplier && MinRangeMultiplier == Other.MinRangeMultiplier && ColorMode == Other.ColorMode && ClampMode == Other.ClampMode && MaxDistance == Other.MaxDistance && BlurRadius == Other.BlurRadius; + } + }; + FCurvatureMapSettings CachedCurvatureMapSettings; + UPROPERTY() + UTexture2D* CachedCurvatureMap; + + void UpdateResult_Curvature(); + + + + + struct FMeshPropertyMapSettings + { + FImageDimensions Dimensions; + int32 PropertyTypeIndex; + + bool operator==(const FMeshPropertyMapSettings& Other) const + { + return Dimensions == Other.Dimensions && PropertyTypeIndex == Other.PropertyTypeIndex; + } + }; + FMeshPropertyMapSettings CachedMeshPropertyMapSettings; + UPROPERTY() + UTexture2D* CachedMeshPropertyMap; + + void UpdateResult_MeshProperty(); + + + + + struct FTexture2DImageSettings + { + FImageDimensions Dimensions; + int32 UVLayer = 0; + + bool operator==(const FTexture2DImageSettings& Other) const + { + return Dimensions == Other.Dimensions && UVLayer == Other.UVLayer; + } + }; + FTexture2DImageSettings CachedTexture2DImageSettings; + UPROPERTY() + UTexture2D* CachedTexture2DImageMap; + + void UpdateResult_Texture2DImage(); + + + + // empty maps are shown when nothing is computed + UPROPERTY() UTexture2D* EmptyNormalMap; UPROPERTY() - UTexture2D* EmptyOcclusionMap; + UTexture2D* EmptyColorMapBlack; + + UPROPERTY() + UTexture2D* EmptyColorMapWhite; void InitializeEmptyMaps(); diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/DeformMeshPolygonsTool.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/DeformMeshPolygonsTool.h index 0188fad1386d..e493b9bd84cc 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/DeformMeshPolygonsTool.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/DeformMeshPolygonsTool.h @@ -228,6 +228,7 @@ protected: void PrecomputeTopology(); FGroupTopologySelector TopoSelector; + FGroupTopologySelector::FSelectionSettings GetTopoSelectorSettings(); // // data for current drag diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/DrawAndRevolveTool.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/DrawAndRevolveTool.h index d74348a89f24..b46aa5bb8e62 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/DrawAndRevolveTool.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/DrawAndRevolveTool.h @@ -17,6 +17,7 @@ class UCollectSurfacePathMechanic; class UConstructionPlaneMechanic; +class UCurveControlPointsMechanic; class FCurveSweepOp; UCLASS() @@ -44,16 +45,20 @@ public: bool bConnectOpenProfileToAxis = true; /** Determines the draw plane and the rotation axis (X in the plane). Can only be edited until the first point is added. */ - UPROPERTY(EditAnywhere, Category = DrawPlane, meta = (EditCondition = "AllowedToEditDrawPlane != 0")) + UPROPERTY(EditAnywhere, Category = DrawPlane, meta = (EditCondition = "bAllowedToEditDrawPlane", HideEditConditionToggle)) FTransform DrawPlaneAndAxis = FTransform(FRotator(90, 0, 0)); /** Determines whether plane control widget snaps to world grid (only relevant if world coordinate mode is active in viewport) .*/ UPROPERTY(EditAnywhere, Category = DrawPlane) bool bSnapToWorldGrid = false; + /** Enables/disables snapping while editing the profile curve. */ + UPROPERTY(EditAnywhere, Category = ProfileCurve) + bool bEnableSnapping = true; + // Not user visible- used to disallow draw plane modification. - UPROPERTY() - int AllowedToEditDrawPlane = 1; // Using an int instead of a bool because the editor adds a user-editable checkbox otherwise + UPROPERTY(meta = (TransientToolProperty)) + bool bAllowedToEditDrawPlane = true; }; UCLASS() @@ -72,7 +77,7 @@ public: /** Draws a profile curve and revolves it around an axis. */ UCLASS() -class MESHMODELINGTOOLS_API UDrawAndRevolveTool : public UInteractiveTool, public IClickBehaviorTarget, public IHoverBehaviorTarget +class MESHMODELINGTOOLS_API UDrawAndRevolveTool : public UInteractiveTool { GENERATED_BODY() @@ -80,6 +85,9 @@ public: virtual void SetWorld(UWorld* World) { TargetWorld = World; } virtual void SetAssetAPI(IToolsContextAssetAPI* NewAssetApi) { AssetAPI = NewAssetApi; } + virtual void RegisterActions(FInteractiveToolActionSet& ActionSet) override; + void OnBackspacePress(); + virtual bool HasCancel() const override { return true; } virtual bool HasAccept() const override { return true; } virtual bool CanAccept() const override; @@ -92,16 +100,6 @@ public: virtual void OnPropertyModified(UObject* PropertySet, FProperty* Property) override; - // IClickBehaviorTarget API - virtual FInputRayHit IsHitByClick(const FInputDeviceRay& ClickPos) override; - virtual void OnClicked(const FInputDeviceRay& ClickPos) override; - - // IHoverBehaviorTarget API - virtual FInputRayHit BeginHoverSequenceHitTest(const FInputDeviceRay& PressPos) override; - virtual bool OnUpdateHover(const FInputDeviceRay& DevicePos) override; - virtual void OnBeginHover(const FInputDeviceRay& DevicePos) override {} //do nothing - virtual void OnEndHover() override {} // do nothing - protected: UWorld* TargetWorld; @@ -118,10 +116,8 @@ protected: void UpdateRevolutionAxis(const FTransform& PlaneTransform); - void UndoCurrentOperation(); - UPROPERTY() - UCollectSurfacePathMechanic* DrawProfileCurveMechanic = nullptr; + UCurveControlPointsMechanic* ControlPointsMechanic = nullptr; UPROPERTY() UConstructionPlaneMechanic* PlaneMechanic = nullptr; @@ -139,22 +135,5 @@ protected: void GenerateAsset(const FDynamicMeshOpResult& Result); - friend class FRevolveToolStateChange; friend class URevolveOperatorFactory; }; - -/** Used to support undo while drawing the profile curve. -*/ -class MESHMODELINGTOOLS_API FRevolveToolStateChange : public FToolCommandChange -{ -public: - bool bHaveDoneUndo = false; - - FRevolveToolStateChange() - {} - - virtual void Apply(UObject* Object) override {} - virtual void Revert(UObject* Object) override; - virtual bool HasExpired(UObject* Object) const override; - virtual FString ToString() const override; -}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/DrawPolygonTool.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/DrawPolygonTool.h index 6c4fb2830027..7c19153ccdfd 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/DrawPolygonTool.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/DrawPolygonTool.h @@ -104,7 +104,8 @@ public: float FeatureSizeRatio = .25; /** Extrusion Distance in non-interactive mode */ - UPROPERTY(EditAnywhere, NonTransactional, Category = Polygon, meta = (UIMin = "-1000", UIMax = "1000", ClampMin = "-10000", ClampMax = "10000")) + UPROPERTY(EditAnywhere, NonTransactional, Category = Polygon, meta = (UIMin = "-1000", UIMax = "1000", ClampMin = "-10000", ClampMax = "10000", + EditCondition = "OutputMode == EDrawPolygonOutputMode::ExtrudedConstant")) float ExtrudeHeight = 100.0f; /** Extrusion Distance in non-interactive mode */ diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/EdgeLoopInsertionTool.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/EdgeLoopInsertionTool.h new file mode 100644 index 000000000000..1c2941feb0fb --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/EdgeLoopInsertionTool.h @@ -0,0 +1,243 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "BaseBehaviors/BehaviorTargetInterfaces.h" +#include "ModelingOperators.h" //IDynamicMeshOperatorFactory +#include "InteractiveTool.h" //UInteractiveToolPropertySet +#include "InteractiveToolBuilder.h" //UInteractiveToolBuilder +#include "InteractiveToolChange.h" //FToolCommandChange +#include "MeshOpPreviewHelpers.h" //FDynamicMeshOpResult +#include "Selection/GroupTopologySelector.h" +#include "SingleSelectionTool.h" +#include "ToolDataVisualizer.h" + +#include "EdgeLoopInsertionTool.generated.h" + +class UDynamicMeshReplacementChangeTarget; + +UCLASS() +class MESHMODELINGTOOLS_API UEdgeLoopInsertionToolBuilder : public UInteractiveToolBuilder +{ + GENERATED_BODY() + +public: + IToolsContextAssetAPI* AssetAPI = nullptr; + + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; +}; + +UENUM() +enum class EEdgeLoopPositioningMode +{ + /** Edge loops will be evenly centered within a group. Allows for multiple insertions at a time. */ + Even, + + /** Edge loops will fall at the same length proportion at each edge they intersect (e.g., a quarter way down). */ + ProportionOffset, + + /** Edge loops will fall a constant distance away from the start of each edge they intersect + (e.g., 20 units down). Clamps to end if edge is too short. */ + DistanceOffset +}; + +UENUM() +enum class EEdgeLoopInsertionMode +{ + /** Existing groups will be deleted and new triangles will be created for the new groups. + Keeps topology simple but breaks non-planar groups and loses the UV's. */ + Retriangulate, + + /** Keeps existing triangles and cuts them to create a new path. May result in fragmented triangles over time.*/ + PlaneCut +}; + +UCLASS() +class MESHMODELINGTOOLS_API UEdgeLoopInsertionProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() + +public: + /** Determines how edge loops position themselves vertically relative to loop direction. */ + UPROPERTY(EditAnywhere, Category = InsertEdgeLoop) + EEdgeLoopPositioningMode PositionMode = EEdgeLoopPositioningMode::ProportionOffset; + + /** Determines how edge loops are added to the geometry */ + UPROPERTY(EditAnywhere, Category = InsertEdgeLoop) + EEdgeLoopInsertionMode InsertionMode = EEdgeLoopInsertionMode::PlaneCut; + + /** How many loops to insert at a time. Only used with "even" positioning mode. */ + UPROPERTY(EditAnywhere, Category = InsertEdgeLoop, meta = (UIMin = "0", UIMax = "20", ClampMin = "0", ClampMax = "500", + EditCondition = "PositionMode == EEdgeLoopPositioningMode::Even", EditConditionHides)) + int32 NumLoops = 1; + + UPROPERTY(EditAnywhere, Category = InsertEdgeLoop, AdvancedDisplay, meta = (UIMin = "0", UIMax = "1.0", ClampMin = "0", ClampMax = "1.0", + EditCondition = "PositionMode == EEdgeLoopPositioningMode::ProportionOffset && !bInteractive", EditConditionHides)) + double ProportionOffset = 0.5; + + UPROPERTY(EditAnywhere, Category = InsertEdgeLoop, AdvancedDisplay, meta = (UIMin = "0", ClampMin = "0", + EditCondition = "PositionMode == EEdgeLoopPositioningMode::DistanceOffset && !bInteractive", EditConditionHides)) + double DistanceOffset = 10.0; + + /** When false, the distance/proportion offset is numerically specified, and mouse clicks just choose the edge. */ + UPROPERTY(EditAnywhere, Category = InsertEdgeLoop, AdvancedDisplay, meta = ( + EditCondition = "PositionMode != EEdgeLoopPositioningMode::Even", EditConditionHides)) + bool bInteractive = true; + + /** Measure the distance offset from the opposite side of the edges. */ + UPROPERTY(EditAnywhere, Category = InsertEdgeLoop, meta = ( + EditCondition = "PositionMode == EEdgeLoopPositioningMode::DistanceOffset", EditConditionHides)) + bool bFlipOffsetDirection = false; + + UPROPERTY(EditAnywhere, Category = InsertEdgeLoop) + bool bWireframe = true; + + /** How close a new loop edge needs to pass next to an existing vertex to use that vertex rather than creating a new one. */ + UPROPERTY(EditAnywhere, Category = InsertEdgeLoop, AdvancedDisplay) + double VertexTolerance = 0.001; +}; + +UCLASS() +class MESHMODELINGTOOLS_API UEdgeLoopInsertionOperatorFactory : public UObject, public IDynamicMeshOperatorFactory +{ + GENERATED_BODY() + +public: + // IDynamicMeshOperatorFactory API + virtual TUniquePtr MakeNewOperator() override; + + UPROPERTY() + UEdgeLoopInsertionTool* Tool; +}; + +/** Tool for inserting (group) edge loops into a mesh. */ +UCLASS() +class MESHMODELINGTOOLS_API UEdgeLoopInsertionTool : public USingleSelectionTool, public IHoverBehaviorTarget, public IClickBehaviorTarget +{ + GENERATED_BODY() + +public: + + friend class UEdgeLoopInsertionOperatorFactory; + friend class FEdgeLoopInsertionChangeBookend; + + UEdgeLoopInsertionTool() {}; + + virtual void Setup() override; + virtual void Shutdown(EToolShutdownType ShutdownType) override; + + virtual void SetWorld(UWorld* World) { TargetWorld = World; } + virtual void SetAssetAPI(IToolsContextAssetAPI* AssetAPIIn) { AssetAPI = AssetAPIIn; } + + virtual void OnTick(float DeltaTime) override; + virtual void Render(IToolsContextRenderAPI* RenderAPI) override; + + virtual bool HasCancel() const override { return true; } + virtual bool HasAccept() const override { return true; } + virtual bool CanAccept() const override; + + virtual void OnPropertyModified(UObject* PropertySet, FProperty* Property) override; + + // IHoverBehaviorTarget + virtual FInputRayHit BeginHoverSequenceHitTest(const FInputDeviceRay& PressPos) override; + virtual void OnBeginHover(const FInputDeviceRay& DevicePos) override {} + virtual bool OnUpdateHover(const FInputDeviceRay& DevicePos) override; + virtual void OnEndHover() override; + + // IClickBehaviorTarget + virtual FInputRayHit IsHitByClick(const FInputDeviceRay& ClickPos) override; + virtual void OnClicked(const FInputDeviceRay& ClickPos) override; + +protected: + + UPROPERTY() + UEdgeLoopInsertionProperties* Settings = nullptr; + + TSharedPtr CurrentMesh; + TSharedPtr CurrentTopology; + FDynamicMeshAABBTree3 MeshSpatial; + FGroupTopologySelector TopologySelector; + + TArray> PreviewEdges; + + FViewCameraState CameraState; + + UPROPERTY() + UMeshOpPreviewWithBackgroundCompute* Preview; + + UWorld* TargetWorld; + IToolsContextAssetAPI* AssetAPI; + + FToolDataVisualizer ExistingEdgesRenderer; + FToolDataVisualizer PreviewEdgeRenderer; + FGroupTopologySelector::FSelectionSettings TopologySelectorSettings; + + void SetupPreview(); + + FInputRayHit HitTest(const FRay& WorldRay); + bool UpdateHoveredItem(const FRay& WorldRay); + + void ConditionallyUpdatePreview(int32 NewGroupID, double* NewInputLength = nullptr); + void ClearPreview(); + + // Taken from user interaction, read as inputs by the op factory + int32 InputGroupEdgeID = FDynamicMesh3::InvalidID; + double InteractiveInputLength = 0; + + // Lets us reset the preview to the original mesh using the op + bool bShowingBaseMesh = false; + + // On valid clicks, we wait to finish the background op and apply it before taking more input. + // Gets reset OnTick when the result is ready. + bool bWaitingForInsertionCompletion = false; + + // Copied over on op completion + bool bLastComputeSucceeded = false; + TSharedPtr LatestOpTopologyResult; + + // Used to expire undo/redo changes on op shutdown. + int32 CurrentChangeStamp = 0; + + /** + * Expires the tool-associated changes in the undo/redo stack. The ComponentTarget + * changes will stay (we want this). + */ + inline void ExpireChanges() + { + ++CurrentChangeStamp; + } + + +}; + +/** + * This change object is a bit of a hack: if it is emitted on both sides of the associated + * ComponentTarget change, it will reload the current mesh and topology from the target + * on Undo/Redo, thereby propagating it to the tool. + */ +class MESHMODELINGTOOLS_API FEdgeLoopInsertionChangeBookend : public FToolCommandChange +{ +public: + FEdgeLoopInsertionChangeBookend(int32 CurrentChangeStamp, bool bBeforeChangeIn) + : ChangeStamp(CurrentChangeStamp) + , bBeforeChange(bBeforeChangeIn) + {}; + + virtual void Apply(UObject* Object) override; + virtual void Revert(UObject* Object) override; + virtual bool HasExpired(UObject* Object) const override + { + return Cast(Object)->CurrentChangeStamp != ChangeStamp; + } + virtual FString ToString() const override + { + return TEXT("FEdgeLoopInsertionChangeBookend"); + } + +protected: + int32 ChangeStamp; + bool bBeforeChange; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/EditMeshPolygonsTool.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/EditMeshPolygonsTool.h index 82c7fcbaf4f6..93e7b55448de 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/EditMeshPolygonsTool.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/EditMeshPolygonsTool.h @@ -50,8 +50,11 @@ enum class ELocalFrameMode }; +/** + * These are properties that do not get enabled/disabled based on the action + */ UCLASS() -class MESHMODELINGTOOLS_API UPolyEditTransformProperties : public UInteractiveToolPropertySet +class MESHMODELINGTOOLS_API UPolyEditCommonProperties : public UInteractiveToolPropertySet { GENERATED_BODY() @@ -59,7 +62,10 @@ public: UPROPERTY(EditAnywhere, Category = Options) bool bShowWireframe = false; -UPROPERTY(EditAnywhere, Category = Gizmo) + UPROPERTY(EditAnywhere, Category = Options) + bool bSelectEdgeLoops = false; + + UPROPERTY(EditAnywhere, Category = Gizmo) ELocalFrameMode LocalFrameMode = ELocalFrameMode::FromGeometry; UPROPERTY(EditAnywhere, Category = Gizmo) @@ -67,7 +73,6 @@ UPROPERTY(EditAnywhere, Category = Gizmo) UPROPERTY(EditAnywhere, Category = Gizmo) bool bSnapToWorldGrid = false; - }; @@ -318,6 +323,56 @@ public: }; +/** + * Settings for Inset operation + */ +UCLASS() +class MESHMODELINGTOOLS_API UPolyEditInsetProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() + +public: + /** Determines whether vertices in inset region should be projected back onto input surface */ + UPROPERTY(EditAnywhere, Category = Inset) + bool bReproject = true; + + /** Amount of smoothing applied to inset boundary */ + UPROPERTY(EditAnywhere, Category = Inset, meta = (UIMin = "0.0", UIMax = "1.0", EditCondition = "bBoundaryOnly == false")) + float Softness = 0.5; + + /** Controls whether inset operation will move interior vertices as well as border vertices */ + UPROPERTY(EditAnywhere, Category = Inset, AdvancedDisplay) + bool bBoundaryOnly = false; + + /** Tweak area scaling when solving for interior vertices */ + UPROPERTY(EditAnywhere, Category = Inset, AdvancedDisplay, meta = (UIMin = "0.0", UIMax = "1.0", EditCondition = "bBoundaryOnly == false")) + float AreaScale = true; +}; + + + +UCLASS() +class MESHMODELINGTOOLS_API UPolyEditOutsetProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() + +public: + /** Amount of smoothing applied to outset boundary */ + UPROPERTY(EditAnywhere, Category = Inset, meta = (UIMin = "0.0", UIMax = "1.0", EditCondition = "bBoundaryOnly == false")) + float Softness = 0.5; + + /** Controls whether outset operation will move interior vertices as well as border vertices */ + UPROPERTY(EditAnywhere, Category = Inset, AdvancedDisplay) + bool bBoundaryOnly = false; + + /** Tweak area scaling when solving for interior vertices */ + UPROPERTY(EditAnywhere, Category = Inset, AdvancedDisplay, meta = (UIMin = "0.0", UIMax = "1.0", EditCondition = "bBoundaryOnly == false")) + float AreaScale = true; +}; + + + + UENUM() @@ -412,7 +467,7 @@ protected: USimpleDynamicMeshComponent* DynamicMeshComponent = nullptr; UPROPERTY() - UPolyEditTransformProperties* TransformProps; + UPolyEditCommonProperties* CommonProps; UPROPERTY() UEditMeshPolygonsToolActions* EditActions; @@ -430,6 +485,12 @@ protected: UPROPERTY() UPolyEditExtrudeProperties* ExtrudeProperties; + UPROPERTY() + UPolyEditInsetProperties* InsetProperties; + + UPROPERTY() + UPolyEditOutsetProperties* OutsetProperties; + UPROPERTY() UPolyEditCutProperties* CutProperties; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/MeshVertexSculptTool.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/MeshVertexSculptTool.h index 7cf36b94b182..b1952fc52e35 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/MeshVertexSculptTool.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/MeshVertexSculptTool.h @@ -61,6 +61,12 @@ enum class EMeshVertexSculptBrushType : uint8 /** Move vertices parallel to the view plane */ Move UMETA(DisplayName = "Move"), + /** Grab Brush, fall-off alters the influence of the grab */ + PullKelvin UMETA(DisplayName = "Kelvin Grab"), + + /** Grab Brush that may generate cusps, fall-off alters the influence of the grab */ + PullSharpKelvin UMETA(DisplayName = "Sharp Kelvin Grab"), + /** Smooth mesh vertices */ Smooth UMETA(DisplayName = "Smooth"), @@ -79,9 +85,15 @@ enum class EMeshVertexSculptBrushType : uint8 /** Displace vertices along their vertex normals */ Inflate UMETA(DisplayName = "Inflate"), + /** Scale Brush will inflate or pinch radially from the center of the brush */ + ScaleKelvin UMETA(DisplayName = "Kelvin Scale"), + /** Move vertices towards the center of the brush (Ctrl to push away)*/ Pinch UMETA(DisplayName = "Pinch"), + /** Twist Brush moves vertices in the plane perpendicular to the local mesh normal */ + TwistKelvin UMETA(DisplayName = "Kelvin Twist"), + /** Move vertices towards the average plane of the brush stamp region */ Flatten UMETA(DisplayName = "Flatten"), diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/CollisionGeometryVisualization.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/CollisionGeometryVisualization.h new file mode 100644 index 000000000000..722ed6e9495b --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/CollisionGeometryVisualization.h @@ -0,0 +1,21 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Physics/PhysicsDataCollection.h" +#include "Drawing/PreviewGeometryActor.h" + +namespace UE +{ + namespace PhysicsTools + { + /** + * Create line sets in a UPreviewGeometry for all the elements in a Physics Data Collection. + * Spheres and Capsules are drawn as 3-axis wireframes. Convexes are added as wireframes. + */ + void InitializePreviewGeometryLines(const FPhysicsDataCollection& PhysicsData, UPreviewGeometry* PreviewGeom, + const FColor& LineColor, float LineThickness, float DepthBias = 0.0, int32 CircleStepResolution = 16 ); + } +} + + diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/CollisionPropertySets.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/CollisionPropertySets.h new file mode 100644 index 000000000000..f23bbd502963 --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/CollisionPropertySets.h @@ -0,0 +1,144 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InteractiveTool.h" +#include "Engine/Classes/PhysicsEngine/AggregateGeom.h" +#include "BodySetupEnums.h" +#include "CollisionPropertySets.generated.h" + + +class FPhysicsDataCollection; + + +UENUM() +enum class ECollisionGeometryMode +{ + /** Use project physics settings (DefaultShapeComplexity) */ + Default = CTF_UseDefault, + /** Create both simple and complex shapes. Simple shapes are used for regular scene queries and collision tests. Complex shape (per poly) is used for complex scene queries.*/ + SimpleAndComplex = CTF_UseSimpleAndComplex, + /** Create only simple shapes. Use simple shapes for all scene queries and collision tests.*/ + UseSimpleAsComplex = CTF_UseSimpleAsComplex, + /** Create only complex shapes (per poly). Use complex shapes for all scene queries and collision tests. Can be used in simulation for static shapes only (i.e can be collided against but not moved through forces or velocity.) */ + UseComplexAsSimple = CTF_UseComplexAsSimple +}; + + +USTRUCT() +struct MESHMODELINGTOOLS_API FPhysicsSphereData +{ + GENERATED_BODY() + + UPROPERTY(VisibleAnywhere, Category = Sphere) + float Radius; + + UPROPERTY(VisibleAnywhere, Category = Sphere) + FTransform Transform; + + UPROPERTY(VisibleAnywhere, Category = Sphere) + FKShapeElem Element; +}; + +USTRUCT() +struct MESHMODELINGTOOLS_API FPhysicsBoxData +{ + GENERATED_BODY() + + UPROPERTY(VisibleAnywhere, Category = Sphere) + FVector Dimensions; + + UPROPERTY(VisibleAnywhere, Category = Sphere) + FTransform Transform; + + UPROPERTY(VisibleAnywhere, Category = Sphere) + FKShapeElem Element; +}; + +USTRUCT() +struct MESHMODELINGTOOLS_API FPhysicsCapsuleData +{ + GENERATED_BODY() + + UPROPERTY(VisibleAnywhere, Category = Sphere) + float Radius; + + UPROPERTY(VisibleAnywhere, Category = Sphere) + float Length; + + UPROPERTY(VisibleAnywhere, Category = Sphere) + FTransform Transform; + + UPROPERTY(VisibleAnywhere, Category = Sphere) + FKShapeElem Element; +}; + +USTRUCT() +struct MESHMODELINGTOOLS_API FPhysicsConvexData +{ + GENERATED_BODY() + + UPROPERTY(VisibleAnywhere, Category = Convex) + int32 NumVertices; + + UPROPERTY(VisibleAnywhere, Category = Convex) + int32 NumFaces; + + UPROPERTY(VisibleAnywhere, Category = Sphere) + FKShapeElem Element; +}; + + +UCLASS() +class MESHMODELINGTOOLS_API UPhysicsObjectToolPropertySet : public UInteractiveToolPropertySet +{ + GENERATED_BODY() +public: + UPROPERTY(VisibleAnywhere, Category = PhysicsData) + FString ObjectName; + + UPROPERTY(VisibleAnywhere, Category = PhysicsData) + ECollisionGeometryMode CollisionType; + + UPROPERTY(VisibleAnywhere, Category = PhysicsData) + TArray Spheres; + + UPROPERTY(VisibleAnywhere, Category = PhysicsData) + TArray Boxes; + + UPROPERTY(VisibleAnywhere, Category = PhysicsData) + TArray Capsules; + + UPROPERTY(VisibleAnywhere, Category = PhysicsData) + TArray Convexes; + + void Reset(); +}; + + + + +UCLASS() +class MESHMODELINGTOOLS_API UCollisionGeometryVisualizationProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, Category = Visualization) + float LineThickness = 3.0f; + + UPROPERTY(EditAnywhere, Category = Visualization) + FColor Color = FColor::Red; +}; + + + + +namespace UE +{ + namespace PhysicsTools + { + void InitializePhysicsToolObjectPropertySet(const FPhysicsDataCollection* PhysicsData, UPhysicsObjectToolPropertySet* PropSet); + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/ExtractCollisionGeometryTool.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/ExtractCollisionGeometryTool.h new file mode 100644 index 000000000000..3614cd0b548a --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/ExtractCollisionGeometryTool.h @@ -0,0 +1,77 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "SingleSelectionTool.h" +#include "InteractiveToolBuilder.h" +#include "DynamicMesh3.h" +#include "Physics/CollisionPropertySets.h" +#include "ExtractCollisionGeometryTool.generated.h" + +class UPreviewGeometry; +class UPreviewMesh; + +UCLASS() +class MESHMODELINGTOOLS_API UExtractCollisionGeometryToolBuilder : public UInteractiveToolBuilder +{ + GENERATED_BODY() + +public: + IToolsContextAssetAPI* AssetAPI = nullptr; + + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; +}; + + + + +/** + * Mesh Inspector Tool for visualizing mesh information + */ +UCLASS() +class MESHMODELINGTOOLS_API UExtractCollisionGeometryTool : public USingleSelectionTool +{ + GENERATED_BODY() +public: + virtual void SetWorld(UWorld* World) { this->TargetWorld = World; } + virtual void SetAssetAPI(IToolsContextAssetAPI* InAssetAPI) { this->AssetAPI = InAssetAPI; } + + virtual void Setup() override; + virtual void Shutdown(EToolShutdownType ShutdownType) override; + + virtual void OnTick(float DeltaTime) override; + + virtual bool HasCancel() const override { return true; } + virtual bool HasAccept() const override { return true; } + virtual bool CanAccept() const override; + +protected: + + UPROPERTY() + UCollisionGeometryVisualizationProperties* VizSettings = nullptr; + + UPROPERTY() + UPhysicsObjectToolPropertySet* ObjectProps; + +protected: + UPROPERTY() + UPreviewGeometry* PreviewElements; + + UPROPERTY() + UPreviewMesh* PreviewMesh; + + // these are TSharedPtr because TPimplPtr cannot currently be added to a TArray? + TSharedPtr PhysicsInfo; + + UWorld* TargetWorld = nullptr; + IToolsContextAssetAPI* AssetAPI = nullptr; + + FDynamicMesh3 CurrentMesh; + bool bResultValid = false; + void RecalculateMesh(); + + bool bVisualizationDirty = false; + void UpdateVisualization(); +}; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/PhysicsInspectorTool.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/PhysicsInspectorTool.h new file mode 100644 index 000000000000..cd0383a528bb --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/PhysicsInspectorTool.h @@ -0,0 +1,63 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "MultiSelectionTool.h" +#include "InteractiveToolBuilder.h" +#include "Physics/CollisionPropertySets.h" +#include "PhysicsInspectorTool.generated.h" + +class UPreviewGeometry; + +UCLASS() +class MESHMODELINGTOOLS_API UPhysicsInspectorToolBuilder : public UInteractiveToolBuilder +{ + GENERATED_BODY() + +public: + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; +}; + + + + +/** + * Mesh Inspector Tool for visualizing mesh information + */ +UCLASS() +class MESHMODELINGTOOLS_API UPhysicsInspectorTool : public UMultiSelectionTool +{ + GENERATED_BODY() +public: + virtual void Setup() override; + virtual void Shutdown(EToolShutdownType ShutdownType) override; + + virtual void OnTick(float DeltaTime) override; + + virtual bool HasCancel() const override { return false; } + virtual bool HasAccept() const override { return false; } + virtual bool CanAccept() const override { return false; } + +protected: + + UPROPERTY() + UCollisionGeometryVisualizationProperties* VizSettings = nullptr; + + UPROPERTY() + TArray ObjectData; + +protected: + UPROPERTY() + TArray PreviewElements; + + // these are TSharedPtr because TPimplPtr cannot currently be added to a TArray? + TArray> PhysicsInfos; + + void InitializeGeometry(const FPhysicsDataCollection& PhysicsData, UPreviewGeometry* PreviewGeom); + void InitializeObjectProperties(const FPhysicsDataCollection& PhysicsData, UPhysicsObjectToolPropertySet* PropSet); + + bool bVisualizationDirty = false; + void UpdateVisualization(); +}; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/SetCollisionGeometryTool.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/SetCollisionGeometryTool.h new file mode 100644 index 000000000000..a28e6b793c8f --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/Physics/SetCollisionGeometryTool.h @@ -0,0 +1,221 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "MultiSelectionTool.h" +#include "InteractiveToolBuilder.h" +#include "DynamicMesh3.h" +#include "SphereTypes.h" +#include "OrientedBoxTypes.h" +#include "CapsuleTypes.h" +#include "Physics/CollisionPropertySets.h" +#include "SetCollisionGeometryTool.generated.h" + +class UPreviewGeometry; +class FMeshSimpleShapeApproximation; + + +UCLASS() +class MESHMODELINGTOOLS_API USetCollisionGeometryToolBuilder : public UInteractiveToolBuilder +{ + GENERATED_BODY() + +public: + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; +}; + + + + +UENUM() +enum class ESetCollisionGeometryInputMode +{ + CombineAll = 0, + PerInputObject = 1, + PerMeshComponent = 2, + PerMeshGroup = 3 +}; + + +UENUM() +enum class ECollisionGeometryType +{ + KeepExisting = 0, + AlignedBoxes = 1, + OrientedBoxes = 2, + MinimalSpheres = 3, + Capsules = 4, + ConvexHulls = 5, + SweptHulls = 6, + MinVolume = 10, + + None = 11 +}; + + + +UENUM() +enum class EProjectedHullAxis +{ + X = 0, + Y = 1, + Z = 2, + SmallestBoxDimension = 3, + SmallestVolume = 4 +}; + + +UCLASS() +class MESHMODELINGTOOLS_API USetCollisionGeometryToolProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, Category = Options) + ECollisionGeometryType GeometryType = ECollisionGeometryType::AlignedBoxes; + + UPROPERTY(EditAnywhere, Category = Options) + ESetCollisionGeometryInputMode InputMode = ESetCollisionGeometryInputMode::PerInputObject; + + UPROPERTY(EditAnywhere, Category = Options) + bool bUseWorldSpace = false; + + UPROPERTY(EditAnywhere, Category = Options) + bool bRemoveContained = true; + + UPROPERTY(EditAnywhere, Category = Options) + bool bEnableMaxCount = true; + + UPROPERTY(EditAnywhere, Category = Options, meta = (UIMin = "4", UIMax = "100", ClampMin = "4", ClampMax = "9999999", EditCondition = "bEnableMaxCount")) + int32 MaxCount = 50; + + UPROPERTY(EditAnywhere, Category = Options, AdvancedDisplay) + float MinThickness = 0.01; + + UPROPERTY(EditAnywhere, Category = AutoDetect) + bool bDetectBoxes = true; + + UPROPERTY(EditAnywhere, Category = AutoDetect) + bool bDetectSpheres = true; + + UPROPERTY(EditAnywhere, Category = AutoDetect) + bool bDetectCapsules = true; + + UPROPERTY(EditAnywhere, Category = ConvexHulls, meta = (EditConditionHides, EditCondition = "GeometryType == ECollisionGeometryType::ConvexHulls")) + bool bSimplifyHulls = true; + + UPROPERTY(EditAnywhere, Category = ConvexHulls, meta = (UIMin = "4", UIMax = "100", ClampMin = "4", ClampMax = "9999999", + EditConditionHides, EditCondition = "GeometryType == ECollisionGeometryType::ConvexHulls")) + int32 HullTargetFaceCount = 20; + + UPROPERTY(EditAnywhere, Category = SweptHulls, meta = (EditConditionHides, EditCondition = "GeometryType == ECollisionGeometryType::SweptHulls")) + bool bSimplifyPolygons = true; + + UPROPERTY(EditAnywhere, Category = SweptHulls, meta = (UIMin = "0", UIMax = "10", ClampMin = "0", ClampMax = "100000", + EditConditionHides, EditCondition = "GeometryType == ECollisionGeometryType::SweptHulls")) + float HullTolerance = 0.1; + + UPROPERTY(EditAnywhere, Category = SweptHulls, meta = (UIMin = "0", UIMax = "10", ClampMin = "0", ClampMax = "100000", + EditConditionHides, EditCondition = "GeometryType == ECollisionGeometryType::SweptHulls")) + EProjectedHullAxis SweepAxis = EProjectedHullAxis::SmallestVolume; + + UPROPERTY(EditAnywhere, Category = OutputOptions) + bool bAppendToExisting = false; + + UPROPERTY(EditAnywhere, Category = OutputOptions) + ECollisionGeometryMode SetCollisionType = ECollisionGeometryMode::SimpleAndComplex; +}; + + + + + +/** + * Mesh Inspector Tool for visualizing mesh information + */ +UCLASS() +class MESHMODELINGTOOLS_API USetCollisionGeometryTool : public UMultiSelectionTool +{ + GENERATED_BODY() +public: + virtual void Setup() override; + virtual void Shutdown(EToolShutdownType ShutdownType) override; + + virtual void OnTick(float DeltaTime) override; + + virtual bool HasCancel() const override { return true; } + virtual bool HasAccept() const override { return true; } + virtual bool CanAccept() const override { return true; } + +protected: + + UPROPERTY() + USetCollisionGeometryToolProperties* Settings = nullptr; + + + UPROPERTY() + UCollisionGeometryVisualizationProperties* VizSettings = nullptr; + + UPROPERTY() + UPhysicsObjectToolPropertySet* CollisionProps; + +protected: + UPROPERTY() + UPreviewGeometry* PreviewGeom; + + TArray SourceObjectIndices; + bool bSourcesHidden = false; + + enum class EDetectedCollisionGeometry + { + None, + Sphere = 2, + Box = 4, + Capsule = 8, + Convex = 16 + }; + + struct FSourceMesh + { + FDynamicMesh3 Mesh; + + EDetectedCollisionGeometry DetectedType = EDetectedCollisionGeometry::None; + + FSphere3d DetectedSphere; + FOrientedBox3d DetectedBox; + FCapsule3d DetectedCapsule; + }; + + bool bInputMeshesValid = false; + TArray> InputMeshes; + TArray> CombinedInputMeshes; + TArray> SeparatedInputMeshes; + TArray> PerGroupInputMeshes; + + TSharedPtr InputMeshesApproximator; + TSharedPtr CombinedInputMeshesApproximator; + TSharedPtr SeparatedMeshesApproximator; + TSharedPtr PerGroupMeshesApproximator; + + void PrecomputeInputMeshes(); + void InitializeDerivedMeshSet( + const TArray>& FromInputMeshes, + TArray>& ToMeshes, + TFunctionRef TrisConnectedPredicate); + TSharedPtr& GetApproximator(ESetCollisionGeometryInputMode MeshSetMode); + + FTransform OrigTargetTransform; + FVector TargetScale3D; + + bool bResultValid = false; + TSharedPtr InitialCollision; + TSharedPtr GeneratedCollision; + + void UpdateGeneratedCollision(); +// TSharedPtr GenerateCollision_MinVolume(); + + bool bVisualizationDirty = false; + void UpdateVisualization(); +}; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingToolsEditorOnly/Private/UVLayoutTool.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingToolsEditorOnly/Private/UVLayoutTool.cpp index 34f8de302353..e61e09716e64 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingToolsEditorOnly/Private/UVLayoutTool.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingToolsEditorOnly/Private/UVLayoutTool.cpp @@ -66,10 +66,6 @@ UUVLayoutToolProperties::UUVLayoutToolProperties() } -UUVLayoutAdvancedProperties::UUVLayoutAdvancedProperties() -{ -} - UUVLayoutTool::UUVLayoutTool() { @@ -92,16 +88,31 @@ void UUVLayoutTool::Setup() BasicProperties = NewObject(this, TEXT("UV Projection Settings")); BasicProperties->RestoreProperties(this); - AdvancedProperties = NewObject(this, TEXT("Advanced Settings")); - // initialize our properties AddToolPropertySource(BasicProperties); - AddToolPropertySource(AdvancedProperties); MaterialSettings = NewObject(this); MaterialSettings->RestoreProperties(this); AddToolPropertySource(MaterialSettings); + // if we only have one object, add optional UV layout view + if (ComponentTargets.Num() == 1) + { + UVLayoutView = NewObject(this); + UVLayoutView->CreateInWorld(TargetWorld); + + FComponentMaterialSet MaterialSet; + ComponentTargets[0]->GetMaterialSet(MaterialSet); + UVLayoutView->SetSourceMaterials(MaterialSet); + + UVLayoutView->SetSourceWorldPosition( + ComponentTargets[0]->GetOwnerActor()->GetTransform(), + ComponentTargets[0]->GetOwnerActor()->GetComponentsBoundingBox()); + + UVLayoutView->Settings->RestoreProperties(this); + AddToolPropertySource(UVLayoutView->Settings); + } + UpdateVisualization(); } @@ -142,6 +153,11 @@ void UUVLayoutTool::UpdateNumPreviews() Preview->PreviewMesh->UpdatePreview(OriginalDynamicMeshes[PreviewIdx].Get()); Preview->PreviewMesh->SetTransform(ComponentTargets[PreviewIdx]->GetWorldTransform()); + Preview->OnMeshUpdated.AddLambda([this](UMeshOpPreviewWithBackgroundCompute* Compute) + { + OnPreviewMeshUpdated(Compute); + }); + Preview->SetVisibility(true); } } @@ -150,6 +166,12 @@ void UUVLayoutTool::UpdateNumPreviews() void UUVLayoutTool::Shutdown(EToolShutdownType ShutdownType) { + if (UVLayoutView) + { + UVLayoutView->Settings->SaveProperties(this); + UVLayoutView->Disconnect(); + } + BasicProperties->SaveProperties(this); MaterialSettings->SaveProperties(this); @@ -181,9 +203,25 @@ TUniquePtr UUVLayoutOperatorFactory::MakeNewOperator() FTransform LocalToWorld = Tool->ComponentTargets[ComponentIndex]->GetWorldTransform(); Op->OriginalMesh = Tool->OriginalDynamicMeshes[ComponentIndex]; - Op->bSeparateUVIslands = Tool->BasicProperties->bSeparateUVIslands; + + switch (Tool->BasicProperties->LayoutType) + { + case EUVLayoutType::Transform: + Op->UVLayoutMode = EUVLayoutOpLayoutModes::TransformOnly; + break; + case EUVLayoutType::Stack: + Op->UVLayoutMode = EUVLayoutOpLayoutModes::StackInUnitRect; + break; + case EUVLayoutType::Repack: + Op->UVLayoutMode = EUVLayoutOpLayoutModes::RepackToUnitRect; + break; + } + + //Op->bSeparateUVIslands = Tool->BasicProperties->bSeparateUVIslands; Op->TextureResolution = Tool->BasicProperties->TextureResolution; + Op->bAllowFlips = Tool->BasicProperties->bAllowFlips; Op->UVScaleFactor = Tool->BasicProperties->UVScaleFactor; + Op->UVTranslation = FVector2f(Tool->BasicProperties->UVTranslate); Op->SetTransform(LocalToWorld); return Op; @@ -193,6 +231,13 @@ TUniquePtr UUVLayoutOperatorFactory::MakeNewOperator() void UUVLayoutTool::Render(IToolsContextRenderAPI* RenderAPI) { + GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CameraState); + + if (UVLayoutView) + { + UVLayoutView->Render(RenderAPI); + } + } void UUVLayoutTool::OnTick(float DeltaTime) @@ -201,27 +246,50 @@ void UUVLayoutTool::OnTick(float DeltaTime) { Preview->Tick(DeltaTime); } -} - - -#if WITH_EDITOR -void UUVLayoutTool::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) -{ - UpdateNumPreviews(); - for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews) + if (UVLayoutView) { - Preview->InvalidateResult(); + UVLayoutView->OnTick(DeltaTime); } + + } -#endif + + void UUVLayoutTool::OnPropertyModified(UObject* PropertySet, FProperty* Property) { - // if we don't know what changed, or we know checker density changed, update checker material - UpdateVisualization(); + if (PropertySet == BasicProperties) + { + UpdateNumPreviews(); + for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews) + { + Preview->InvalidateResult(); + } + } + else if (PropertySet == MaterialSettings) + { + // if we don't know what changed, or we know checker density changed, update checker material + UpdateVisualization(); + } } + +void UUVLayoutTool::OnPreviewMeshUpdated(UMeshOpPreviewWithBackgroundCompute* Compute) +{ + if (UVLayoutView) + { + FDynamicMesh3 ResultMesh; + if (Compute->GetCurrentResultCopy(ResultMesh, false) == false) + { + return; + } + UVLayoutView->UpdateUVMesh(&ResultMesh); + } + +} + + void UUVLayoutTool::UpdateVisualization() { MaterialSettings->UpdateMaterials(); diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingToolsEditorOnly/Public/UVLayoutTool.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingToolsEditorOnly/Public/UVLayoutTool.h index 6f1ba2a756bc..792355653f31 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingToolsEditorOnly/Public/UVLayoutTool.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingToolsEditorOnly/Public/UVLayoutTool.h @@ -11,6 +11,7 @@ #include "DynamicMesh3.h" #include "BaseTools/SingleClickTool.h" #include "Properties/MeshMaterialProperties.h" +#include "Drawing/UVLayoutPreview.h" #include "UVLayoutTool.generated.h" @@ -37,6 +38,14 @@ public: +UENUM() +enum class EUVLayoutType +{ + Transform, + Stack, + Repack +}; + /** @@ -50,32 +59,31 @@ class MESHMODELINGTOOLSEDITORONLY_API UUVLayoutToolProperties : public UInteract public: UUVLayoutToolProperties(); - /** Separate UV charts so that they do not overlap and have unique texels, or stack all the charts together */ + /** Type of transformation to apply to input UV islands */ UPROPERTY(EditAnywhere, Category = UVLayout) - bool bSeparateUVIslands = true; + EUVLayoutType LayoutType = EUVLayoutType::Repack; /** Expected resolution of output textures; controls spacing left between charts */ - UPROPERTY(EditAnywhere, Category = UVLayout, meta = (UIMin = "64", UIMax = "2048", ClampMin = "2", ClampMax = "4096", EditCondition = "bSeparateUVIslands")) + UPROPERTY(EditAnywhere, Category = UVLayout, meta = (UIMin = "64", UIMax = "2048", ClampMin = "2", ClampMax = "4096")) int TextureResolution = 1024; - UPROPERTY(EditAnywhere, Category = UVLayout) + /** Apply this uniform scaling to the UVs after any layout recalculation */ + UPROPERTY(EditAnywhere, Category = UVLayout, meta = (UIMin = "0.1", UIMax = "5.0", ClampPin = "0.0001", ClampMax = "10000") ) float UVScaleFactor = 1; + + /** Apply this 2D translation to the UVs after any layout recalculation, and after scaling */ + UPROPERTY(EditAnywhere, Category = UVLayout) + FVector2D UVTranslate = FVector2D(0,0); + + /** Allow the packer to flip the orientation of UV islands if it save space. May cause problems for downstream operations, not recommended. */ + UPROPERTY(EditAnywhere, Category = UVLayout, AdvancedDisplay) + bool bAllowFlips = false; + }; -/** - * Advanced properties - */ -UCLASS() -class MESHMODELINGTOOLSEDITORONLY_API UUVLayoutAdvancedProperties : public UInteractiveToolPropertySet -{ - GENERATED_BODY() - -public: - UUVLayoutAdvancedProperties(); -}; /** @@ -125,10 +133,6 @@ public: virtual bool HasAccept() const override; virtual bool CanAccept() const override; -#if WITH_EDITOR - virtual void PostEditChangeProperty(FPropertyChangedEvent &PropertyChangedEvent) override; -#endif - virtual void OnPropertyModified(UObject* PropertySet, FProperty* Property) override; protected: @@ -136,9 +140,6 @@ protected: UPROPERTY() UUVLayoutToolProperties* BasicProperties; - UPROPERTY() - UUVLayoutAdvancedProperties* AdvancedProperties; - UPROPERTY() UExistingMeshMaterialProperties* MaterialSettings = nullptr; @@ -154,7 +155,15 @@ protected: FViewCameraState CameraState; void UpdateNumPreviews(); + void UpdateVisualization(); + void OnPreviewMeshUpdated(UMeshOpPreviewWithBackgroundCompute* Compute); + void GenerateAsset(const TArray& Results); + + +protected: + UPROPERTY() + UUVLayoutPreview* UVLayoutView = nullptr; }; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/LineSetComponent.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/LineSetComponent.cpp index fa646ce69b62..19adfa0322f1 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/LineSetComponent.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/LineSetComponent.cpp @@ -254,6 +254,22 @@ void ULineSetComponent::InsertLine(const int32 ID, const FRenderableLine& Overla bBoundsDirty = true; } +void ULineSetComponent::SetLineStart(const int32 ID, const FVector& NewPostion) +{ + FRenderableLine& OverlayLine = Lines[ID]; + OverlayLine.Start = NewPostion; + MarkRenderStateDirty(); + bBoundsDirty = true; +} + +void ULineSetComponent::SetLineEnd(const int32 ID, const FVector& NewPostion) +{ + FRenderableLine& OverlayLine = Lines[ID]; + OverlayLine.End = NewPostion; + MarkRenderStateDirty(); + bBoundsDirty = true; +} + void ULineSetComponent::SetLineColor(const int32 ID, const FColor& NewColor) { FRenderableLine& OverlayLine = Lines[ID]; @@ -316,7 +332,7 @@ void ULineSetComponent::RemoveLine(const int32 ID) bool ULineSetComponent::IsLineValid(const int32 ID) const { - return Lines.IsAllocated(ID); + return Lines.IsValidIndex(ID); } FPrimitiveSceneProxy* ULineSetComponent::CreateSceneProxy() diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/PointSetComponent.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/PointSetComponent.cpp index 3bd39c3829e7..fdeb3eac753c 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/PointSetComponent.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/PointSetComponent.cpp @@ -279,6 +279,12 @@ void UPointSetComponent::InsertPoint(const int32 ID, const FRenderablePoint& Ove } +const FRenderablePoint& UPointSetComponent::GetPoint(const int32 ID) +{ + return Points[ID]; +} + + void UPointSetComponent::SetPointColor(const int32 ID, const FColor& NewColor) { FRenderablePoint& OverlayPoint = Points[ID]; @@ -295,6 +301,24 @@ void UPointSetComponent::SetPointSize(const int32 ID, const float NewSize) } +void UPointSetComponent::SetPointPosition(const int32 ID, const FVector& NewPosition) +{ + FRenderablePoint& OverlayPoint = Points[ID]; + OverlayPoint.Position = NewPosition; + MarkRenderStateDirty(); + bBoundsDirty = true; +} + +void UPointSetComponent::SetAllPointsColor(const FColor& NewColor) +{ + for (FRenderablePoint& Point : Points) + { + Point.Color = NewColor; + } + MarkRenderStateDirty(); +} + + void UPointSetComponent::RemovePoint(const int32 ID) { Points.RemoveAt(ID); @@ -305,7 +329,7 @@ void UPointSetComponent::RemovePoint(const int32 ID) bool UPointSetComponent::IsPointValid(const int32 ID) const { - return ID < Points.GetMaxIndex() && Points.IsAllocated(ID); + return Points.IsValidIndex(ID); } @@ -337,6 +361,19 @@ FBoxSphereBounds UPointSetComponent::CalcBounds(const FTransform& LocalToWorld) } Bounds = FBoxSphereBounds(Box); bBoundsDirty = false; + + // TODO: This next bit is not ideal because the point size is specified in onscreen pixels, + // so the true amount by which we would need to expand bounds depends on camera location, FOV, etc. + // We mainly do this as a hack against a problem in ortho viewports, which cull small items + // based on their bounds, and a set consisting of a single point will always be culled due + // to having 0-sized bounds. It's worth noting that when zooming out sufficiently far, the + // point will still be culled even with this hack, however. + // The proper solution is to be able to opt out of the ortho culling behavior, which is something + // we need to add. + if (Points.Num() > 0) + { + Bounds = Bounds.ExpandBy(Points[0].Size); + } } return Bounds.TransformBy(LocalToWorld); } diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/PolyEditPreviewMesh.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/PolyEditPreviewMesh.cpp index d8a81c4424e1..7b659e4924b8 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/PolyEditPreviewMesh.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/PolyEditPreviewMesh.cpp @@ -210,7 +210,7 @@ void UPolyEditPreviewMesh::InitializeInsetType(const FDynamicMesh3* SourceMesh, } -void UPolyEditPreviewMesh::UpdateInsetType(double NewOffset) +void UPolyEditPreviewMesh::UpdateInsetType(double NewOffset, bool bReproject, double Softness, double AreaScaleT, bool bBoundaryOnly) { FDynamicMesh3 EditPatch(InitialEditPatch); FInsetMeshRegion Inset(&EditPatch); @@ -219,6 +219,10 @@ void UPolyEditPreviewMesh::UpdateInsetType(double NewOffset) Inset.Triangles.Add(tid); } Inset.InsetDistance = NewOffset; + Inset.bReproject = bReproject; + Inset.Softness = Softness; + Inset.AreaCorrection = AreaScaleT; + Inset.bSolveRegionInteriors = !bBoundaryOnly; Inset.Apply(); FMeshNormals::QuickRecomputeOverlayNormals(EditPatch); diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/TriangleSetComponent.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/TriangleSetComponent.cpp index 5a10f740c677..936ae2c85ca2 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/TriangleSetComponent.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/TriangleSetComponent.cpp @@ -271,9 +271,42 @@ void UTriangleSetComponent::RemoveTriangle(const int32 ID) bBoundsDirty = true; } + + +int32 UTriangleSetComponent::AddTriangle(const FVector& A, const FVector& B, const FVector& C, const FVector& Normal, const FColor& Color, UMaterialInterface* Material) +{ + FRenderableTriangle NewTriangle; + NewTriangle.Material = Material; + + NewTriangle.Vertex0 = { A, FVector2D(0,0), Normal, Color }; + NewTriangle.Vertex1 = { B, FVector2D(1,0), Normal, Color }; + NewTriangle.Vertex2 = { C, FVector2D(1,1), Normal, Color }; + + return AddTriangle(NewTriangle); +} + +FIndex2i UTriangleSetComponent::AddQuad(const FVector& A, const FVector& B, const FVector& C, const FVector& D, const FVector& Normal, const FColor& Color, UMaterialInterface* Material) +{ + FRenderableTriangle NewTriangle0; + NewTriangle0.Material = Material; + + NewTriangle0.Vertex0 = { A, FVector2D(0,0), Normal, Color }; + NewTriangle0.Vertex1 = { B, FVector2D(1,0), Normal, Color }; + NewTriangle0.Vertex2 = { C, FVector2D(1,1), Normal, Color }; + + FRenderableTriangle NewTriangle1 = NewTriangle0; + NewTriangle1.Vertex1 = NewTriangle1.Vertex2; + NewTriangle1.Vertex2 = { D, FVector2D(0,1), Normal, Color }; + + int32 Index0 = AddTriangle(NewTriangle0); + int32 Index1 = AddTriangle(NewTriangle1); + return FIndex2i(Index0, Index1); +} + + bool UTriangleSetComponent::IsTriangleValid(const int32 ID) const { - return Triangles.IsAllocated(ID); + return Triangles.IsValidIndex(ID); } FPrimitiveSceneProxy* UTriangleSetComponent::CreateSceneProxy() diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/UVLayoutPreview.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/UVLayoutPreview.cpp new file mode 100644 index 000000000000..1a0e9e30bf8d --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Drawing/UVLayoutPreview.cpp @@ -0,0 +1,222 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Drawing/UVLayoutPreview.h" +#include "ToolSetupUtil.h" + + +UUVLayoutPreview::~UUVLayoutPreview() +{ + checkf(PreviewMesh == nullptr, TEXT("You must explicitly Disconnect() UVLayoutPreview before it is GCd")); +} + + + +void UUVLayoutPreview::CreateInWorld(UWorld* World) +{ + PreviewMesh = NewObject(this); + PreviewMesh->CreateInWorld(World, FTransform::Identity); + + TriangleComponent = NewObject(PreviewMesh->GetActor()); + TriangleComponent->SetupAttachment(PreviewMesh->GetRootComponent()); + TriangleComponent->RegisterComponent(); + + Settings = NewObject(this); + Settings->WatchProperty(Settings->bVisible, [this](bool) { bSettingsModified = true; }); + Settings->WatchProperty(Settings->bShowWireframe, [this](bool) { bSettingsModified = true; }); + //Settings->WatchProperty(Settings->bWireframe, [this](bool) { bSettingsModified = true; }); + bSettingsModified = true; + + BackingRectangleMaterial = ToolSetupUtil::GetSelectionMaterial(FLinearColor::White, nullptr); + if (BackingRectangleMaterial == nullptr) + { + BackingRectangleMaterial = ToolSetupUtil::GetDefaultMaterial(); + } +} + + +void UUVLayoutPreview::Disconnect() +{ + PreviewMesh->Disconnect(); + PreviewMesh = nullptr; +} + + +void UUVLayoutPreview::SetSourceMaterials(const FComponentMaterialSet& MaterialSet) +{ + SourceMaterials = MaterialSet; + + PreviewMesh->SetMaterials(SourceMaterials.Materials); +} + + +void UUVLayoutPreview::SetSourceWorldPosition(FTransform WorldTransform, FBox WorldBounds) +{ + SourceObjectWorldBounds = FAxisAlignedBox3d(WorldBounds); + + SourceObjectFrame = FFrame3d(WorldTransform); +} + + +void UUVLayoutPreview::SetCurrentCameraState(const FViewCameraState& CameraStateIn) +{ + CameraState = CameraStateIn; +} + + +void UUVLayoutPreview::SetTransform(const FTransform& UseTransform) +{ + PreviewMesh->SetTransform(UseTransform); +} + + +void UUVLayoutPreview::SetVisible(bool bVisible) +{ + PreviewMesh->SetVisible(bVisible); +} + + +void UUVLayoutPreview::Render(IToolsContextRenderAPI* RenderAPI) +{ + SetCurrentCameraState(RenderAPI->GetCameraState()); + + RecalculatePosition(); + + if (Settings->bVisible) + { + float ScaleFactor = GetCurrentScale(); + FVector Origin = (FVector)CurrentWorldFrame.Origin; + FVector DX = ScaleFactor * (FVector)CurrentWorldFrame.X(); + FVector DY = ScaleFactor * (FVector)CurrentWorldFrame.Y(); + + RenderAPI->GetPrimitiveDrawInterface()->DrawLine( + Origin, Origin+DX, FLinearColor::Black, SDPG_Foreground, 0.5f, 0.0f, true); + RenderAPI->GetPrimitiveDrawInterface()->DrawLine( + Origin+DX, Origin+DX+DY, FLinearColor::Black, SDPG_Foreground, 0.5f, 0.0f, true); + RenderAPI->GetPrimitiveDrawInterface()->DrawLine( + Origin+DX+DY, Origin+DY, FLinearColor::Black, SDPG_Foreground, 0.5f, 0.0f, true); + RenderAPI->GetPrimitiveDrawInterface()->DrawLine( + Origin+DY, Origin, FLinearColor::Black, SDPG_Foreground, 0.5f, 0.0f, true); + } +} + + + +void UUVLayoutPreview::OnTick(float DeltaTime) +{ + if (bSettingsModified) + { + SetVisible(Settings->bVisible); + + PreviewMesh->EnableWireframe(Settings->bShowWireframe); + + bSettingsModified = false; + } +} + + +float UUVLayoutPreview::GetCurrentScale() +{ + return Settings->ScaleFactor * SourceObjectWorldBounds.Height(); +} + + +void UUVLayoutPreview::UpdateUVMesh(const FDynamicMesh3* SourceMesh, int32 SourceUVLayer) +{ + FDynamicMesh3 UVMesh; + UVMesh.EnableAttributes(); + FDynamicMeshUVOverlay* NewUVOverlay = UVMesh.Attributes()->GetUVLayer(0); + FDynamicMeshNormalOverlay* NewNormalOverlay = UVMesh.Attributes()->PrimaryNormals(); + + FAxisAlignedBox2f Bounds = FAxisAlignedBox2f(FVector2f::Zero(), FVector2f::One()); + const FDynamicMeshUVOverlay* UVOverlay = SourceMesh->Attributes()->GetUVLayer(SourceUVLayer); + for (int32 tid : SourceMesh->TriangleIndicesItr()) + { + if (UVOverlay->IsSetTriangle(tid)) + { + FIndex3i UVTri = UVOverlay->GetTriangle(tid); + + FVector2f UVs[3]; + for (int32 j = 0; j < 3; ++j) + { + UVs[j] = UVOverlay->GetElement(UVTri[j]); + Bounds.Contain(UVs[j]); + } + + FIndex3i NewTri, NewUVTri, NewNormalTri; + for (int32 j = 0; j < 3; ++j) + { + NewUVTri[j] = NewUVOverlay->AppendElement(UVs[j]); + NewTri[j] = UVMesh.AppendVertex(FVector3d(UVs[j].X, UVs[j].Y, 0)); + NewNormalTri[j] = NewNormalOverlay->AppendElement(FVector3f::UnitZ()); + } + + FVector2f EdgeUV1 = UVs[1] - UVs[0]; + FVector2f EdgeUV2 = UVs[2] - UVs[0]; + float SignedUVArea = 0.5f * (EdgeUV1.X * EdgeUV2.Y - EdgeUV1.Y * EdgeUV2.X); + + if (SignedUVArea > 0) + { + Swap(NewTri.A, NewTri.B); + Swap(NewUVTri.A, NewUVTri.B); + } + + + int32 NewTriID = UVMesh.AppendTriangle(NewTri); + NewUVOverlay->SetTriangle(NewTriID, NewUVTri); + NewNormalOverlay->SetTriangle(NewTriID, NewNormalTri); + } + } + + Bounds.Expand(0.01); + + float BackZ = -0.01; + TriangleComponent->Clear(); + if (bShowBackingRectangle) + { + TriangleComponent->AddQuad( + FVector(Bounds.Min.X, Bounds.Min.Y, BackZ), + FVector(Bounds.Min.X, Bounds.Max.Y, BackZ), + FVector(Bounds.Max.X, Bounds.Max.Y, BackZ), + FVector(Bounds.Max.X, Bounds.Min.Y, BackZ), + FVector(0, 0, -1), FColor::White, BackingRectangleMaterial); + } + + PreviewMesh->UpdatePreview(MoveTemp(UVMesh)); +} + + + + +void UUVLayoutPreview::RecalculatePosition() +{ + FFrame3d ObjFrame; + ObjFrame.AlignAxis(2, -CameraState.Forward()); + ObjFrame.ConstrainedAlignAxis(0, (FVector3d)CameraState.Right(), ObjFrame.Z()); + ObjFrame.Origin = SourceObjectWorldBounds.Center(); // SourceObjectFrame.Origin; + + FAxisAlignedBox2d ProjectedBounds = FAxisAlignedBox2d::Empty(); + for (int32 k = 0; k < 8; ++k) + { + ProjectedBounds.Contain(ObjFrame.ToPlaneUV(SourceObjectWorldBounds.GetCorner(k))); + } + + double UseScale = GetCurrentScale(); + + double ShiftRight = Settings->Shift.X * (ProjectedBounds.Max.X + (ProjectedBounds.Width() * 0.1)); + //double ShiftRight = Settings->Shift.X * (0.5 * SourceObjectWorldBounds.DiagonalLength()); + if (Settings->WhichSide == EUVLayoutPreviewSide::Left) + { + ShiftRight = ProjectedBounds.Min.X - Settings->Shift.X * (UseScale + (ProjectedBounds.Width() * 0.1)); + } + + //double ShiftUp = ProjectedBounds.Max.Y + Settings->ShiftY * (ProjectedBounds.Height() + Settings->ScaleFactor); + double ShiftUp = Settings->Shift.Y * UseScale; + ObjFrame.Origin += ShiftRight * ObjFrame.X() - ShiftUp * ObjFrame.Y(); + + CurrentWorldFrame = ObjFrame; + + FTransform3d Transform(ObjFrame.Rotation, ObjFrame.Origin); + Transform.SetScale(UseScale * FVector3d::One()); + + SetTransform((FTransform)Transform); +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Mechanics/CurveControlPointsMechanic.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Mechanics/CurveControlPointsMechanic.cpp new file mode 100644 index 000000000000..8871566162c4 --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Mechanics/CurveControlPointsMechanic.cpp @@ -0,0 +1,1352 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Mechanics/CurveControlPointsMechanic.h" + +#include "BaseBehaviors/SingleClickBehavior.h" +#include "BaseBehaviors/MouseHoverBehavior.h" +#include "BaseGizmos/TransformGizmo.h" +#include "BaseGizmos/TransformProxy.h" +#include "Drawing/PreviewGeometryActor.h" +#include "Drawing/LineSetComponent.h" +#include "Drawing/PointSetComponent.h" +#include "InteractiveToolManager.h" +#include "InteractiveToolActionSet.h" +#include "Polyline3.h" +#include "ToolSceneQueriesUtil.h" +#include "ToolSetupUtil.h" +#include "Transforms/MultiTransformer.h" + +#define LOCTEXT_NAMESPACE "UCurveControlPointsMechanic" + +const FText PointAdditionTransactionText = LOCTEXT("PointAddition", "Point Addition"); +const FText PointDeletionTransactionText = LOCTEXT("PointDeletion", "Point Deletion"); +const FText PointDeselectionTransactionText = LOCTEXT("PointDeselection", "Point Deselection"); +const FText PointSelectionTransactionText = LOCTEXT("PointSelection", "Point Selection"); +const FText PointMovementTransactionText = LOCTEXT("PointMovement", "Point Movement"); +const FText InitializationCompletedTransactionText = LOCTEXT("InitializationCompleted", "InitializationCompleted"); + +UCurveControlPointsMechanic::~UCurveControlPointsMechanic() +{ + checkf(PreviewGeometryActor == nullptr, TEXT("Shutdown() should be called before UCurveControlPointsMechanic is destroyed.")); +} + +void UCurveControlPointsMechanic::Setup(UInteractiveTool* ParentToolIn) +{ + UInteractionMechanic::Setup(ParentToolIn); + + ClickBehavior = NewObject(); + ClickBehavior->Initialize(this); + ClickBehavior->Modifiers.RegisterModifier(ShiftModifierId, FInputDeviceState::IsShiftKeyDown); + ClickBehavior->Modifiers.RegisterModifier(CtrlModifierId, FInputDeviceState::IsCtrlKeyDown); + ParentTool->AddInputBehavior(ClickBehavior); + + HoverBehavior = NewObject(); + HoverBehavior->Initialize(this); + HoverBehavior->Modifiers.RegisterModifier(ShiftModifierId, FInputDeviceState::IsShiftKeyDown); + HoverBehavior->Modifiers.RegisterModifier(CtrlModifierId, FInputDeviceState::IsCtrlKeyDown); + ParentTool->AddInputBehavior(HoverBehavior); + + // We use custom materials that are visible through other objects. + // TODO: This probably should be configurable. For instance, we may want the segments to be dashed, or not visible at all. + DrawnControlPoints = NewObject(); + DrawnControlPoints->SetPointMaterial( + LoadObject(nullptr, TEXT("/MeshModelingToolset/Materials/PointSetOverlaidComponentMaterial"))); + DrawnControlSegments = NewObject(); + DrawnControlSegments->SetLineMaterial( + LoadObject(nullptr, TEXT("/MeshModelingToolset/Materials/LineSetOverlaidComponentMaterial"))); + PreviewPoint = NewObject(); + PreviewPoint->SetPointMaterial( + LoadObject(nullptr, TEXT("/MeshModelingToolset/Materials/PointSetOverlaidComponentMaterial"))); + PreviewSegment = NewObject(); + PreviewSegment->SetLineMaterial( + LoadObject(nullptr, TEXT("/MeshModelingToolset/Materials/LineSetOverlaidComponentMaterial"))); + + InitializationCurveColor = FColor::Yellow; + NormalCurveColor = FColor::Red; + CurrentSegmentsColor = bInteractiveInitializationMode ? InitializationCurveColor : NormalCurveColor; + SegmentsThickness = 4.0f; + CurrentPointsColor = bInteractiveInitializationMode ? InitializationCurveColor : NormalCurveColor; + PointsSize = 8.0f; + HoverColor = FColor::Green; + SelectedColor = FColor::Yellow; + PreviewColor = HoverColor; + SnapLineColor = FColor::Yellow; + DepthBias = 1.0f; + + GeometrySetToleranceTest = [this](const FVector3d& Position1, const FVector3d& Position2) { + if (CameraState.bIsOrthographic) + { + // We could just always use ToolSceneQueriesUtil::PointSnapQuery. But in ortho viewports, we happen to know + // that the only points that we will ever give this function will be the closest points between a ray and + // some geometry, meaning that the vector between them will be orthogonal to the view ray. With this knowledge, + // we can do the tolerance computation more efficiently than PointSnapQuery can, since we don't need to project + // down to the view plane. + // As in PointSnapQuery, we convert our angle-based tolerance to one we can use in an ortho viewport (instead of + // dividing our field of view into 90 visual angle degrees, we divide the plane into 90 units). + float OrthoTolerance = ToolSceneQueriesUtil::GetDefaultVisualAngleSnapThreshD() * CameraState.OrthoWorldCoordinateWidth / 90.0; + return Position1.DistanceSquared(Position2) < OrthoTolerance * OrthoTolerance; + } + else + { + return ToolSceneQueriesUtil::PointSnapQuery(CameraState, Position1, Position2); + } + }; + + SnapEngine.SnapMetricFunc = [this](const FVector3d& Position1, const FVector3d& Position2) { + return ToolSceneQueriesUtil::PointSnapMetric(this->CameraState, Position1, Position2); + }; + SnapEngine.SnapMetricTolerance = ToolSceneQueriesUtil::GetDefaultVisualAngleSnapThreshD(); + + // So far, snapping to known lengths has seemed more inconvenient than useful, but we + // can enable this if we decide we want it. It will require a bit of extra code to make it + // obvious what we're snapping to in that case. + SnapEngine.bEnableSnapToKnownLengths = false; + + LineSnapIDMin = FPointPlanarSnapSolver::BaseExternalLineID; + LineSnapPriority = SnapEngine.MinInternalPriority() - 1; // lower is more important + + // We need to be able to detect when we snap to first/last points in the curve for leaving initialization mode + FirstPointSnapID = FPointPlanarSnapSolver::BaseExternalPointID; + LastPointSnapID = FirstPointSnapID + 1; + EndpointSnapPriority = LineSnapPriority - SnapEngine.IntersectionPriorityDelta - 1; // more important than user lines, or intersections with user lines + + UInteractiveGizmoManager* GizmoManager = GetParentTool()->GetToolManager()->GetPairedGizmoManager(); + PointTransformProxy = NewObject(this); + PointTransformGizmo = GizmoManager->CreateCustomTransformGizmo( + ETransformGizmoSubElements::TranslateAxisX | ETransformGizmoSubElements::TranslateAxisY | ETransformGizmoSubElements::TranslatePlaneXY, + GetParentTool()); + PointTransformProxy->OnTransformChanged.AddUObject(this, &UCurveControlPointsMechanic::GizmoTransformChanged); + PointTransformProxy->OnBeginTransformEdit.AddUObject(this, &UCurveControlPointsMechanic::GizmoTransformStarted); + PointTransformProxy->OnEndTransformEdit.AddUObject(this, &UCurveControlPointsMechanic::GizmoTransformEnded); + PointTransformGizmo->SetActiveTarget(PointTransformProxy); + PointTransformGizmo->SetVisibility(false); + + // We force the coordinate system to be local so that the gizmo only moves in the plane we specify + PointTransformGizmo->bUseContextCoordinateSystem = false; + PointTransformGizmo->CurrentCoordinateSystem = EToolContextCoordinateSystem::Local; +} + +void UCurveControlPointsMechanic::SetWorld(UWorld* World) +{ + // It may be unreasonable to worry about SetWorld being called more than once, but let's be safe anyway + if (PreviewGeometryActor) + { + PreviewGeometryActor->Destroy(); + } + + // We need the world so we can create the geometry actor in the right place. + FRotator Rotation(0.0f, 0.0f, 0.0f); + FActorSpawnParameters SpawnInfo; + PreviewGeometryActor = World->SpawnActor(FVector::ZeroVector, Rotation, SpawnInfo); + + // Attach the rendering components to the actor + DrawnControlPoints->Rename(nullptr, PreviewGeometryActor); // Changes the "outer" + PreviewGeometryActor->SetRootComponent(DrawnControlPoints); + if (DrawnControlPoints->IsRegistered()) + { + DrawnControlPoints->ReregisterComponent(); + } + else + { + DrawnControlPoints->RegisterComponent(); + } + + DrawnControlSegments->Rename(nullptr, PreviewGeometryActor); // Changes the "outer" + DrawnControlSegments->AttachToComponent(DrawnControlPoints, FAttachmentTransformRules::KeepWorldTransform); + if (DrawnControlSegments->IsRegistered()) + { + DrawnControlSegments->ReregisterComponent(); + } + else + { + DrawnControlSegments->RegisterComponent(); + } + + PreviewPoint->Rename(nullptr, PreviewGeometryActor); // Changes the "outer" + PreviewPoint->AttachToComponent(DrawnControlPoints, FAttachmentTransformRules::KeepWorldTransform); + if (PreviewPoint->IsRegistered()) + { + PreviewPoint->ReregisterComponent(); + } + else + { + PreviewPoint->RegisterComponent(); + } + + PreviewSegment->Rename(nullptr, PreviewGeometryActor); // Changes the "outer" + PreviewSegment->AttachToComponent(DrawnControlPoints, FAttachmentTransformRules::KeepWorldTransform); + if (PreviewSegment->IsRegistered()) + { + PreviewSegment->ReregisterComponent(); + } + else + { + PreviewSegment->RegisterComponent(); + } +} + +void UCurveControlPointsMechanic::Shutdown() +{ + if (PreviewGeometryActor) + { + PreviewGeometryActor->Destroy(); + PreviewGeometryActor = nullptr; + } + + if (PointTransformGizmo) + { + PointTransformGizmo->Shutdown(); + PointTransformGizmo = nullptr; + } +} + +void UCurveControlPointsMechanic::Render(IToolsContextRenderAPI* RenderAPI) +{ + // TODO: Should we cache the camera state here or somewhere else? + GetParentTool()->GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CameraState); + + // Draw the lines that we're currently snapped to + if (bSnappingEnabled && SnapEngine.HaveActiveSnap()) + { + const int SNAP_LINE_HALF_LENGTH = 9999; + + FPrimitiveDrawInterface* PDI = RenderAPI->GetPrimitiveDrawInterface(); + float PDIScale = RenderAPI->GetCameraState().GetPDIScalingFactor(); + if (SnapEngine.HaveActiveSnapLine()) + { + FLine3d SnapLine = SnapEngine.GetActiveSnapLine(); + PDI->DrawLine((FVector)SnapLine.PointAt(-SNAP_LINE_HALF_LENGTH), (FVector)SnapLine.PointAt(SNAP_LINE_HALF_LENGTH), + SnapLineColor, SDPG_Foreground, 0.5 * PDIScale, 0.0f, true); + } + else + { + // See if we snapped to an intersection of two lines + if (SnapEngine.HaveActiveSnapIntersection()) + { + // Draw both of the lines that are intersecting + FLine3d SnapLine = SnapEngine.GetActiveSnapLine(); + PDI->DrawLine((FVector)SnapLine.PointAt(-SNAP_LINE_HALF_LENGTH), (FVector)SnapLine.PointAt(SNAP_LINE_HALF_LENGTH), + SnapLineColor, SDPG_Foreground, 0.5 * PDIScale, 0.0f, true); + SnapLine = SnapEngine.GetIntersectionSecondLine(); + PDI->DrawLine((FVector)SnapLine.PointAt(-SNAP_LINE_HALF_LENGTH), (FVector)SnapLine.PointAt(SNAP_LINE_HALF_LENGTH), + SnapLineColor, SDPG_Foreground, 0.5 * PDIScale, 0.0f, true); + } + } + } +} + +void UCurveControlPointsMechanic::Initialize(const TArray& Points, bool bIsLoopIn) +{ + // This also clears selection and hover + ClearPoints(); + + for (const FVector3d& Point : Points) + { + AppendPoint(Point); + } + + bIsLoop = bIsLoopIn; + + SnapEngine.UpdatePointHistory(Points); + UpdateSnapTargetsForHover(); +} + +void UCurveControlPointsMechanic::ClearPoints() +{ + ClearSelection(); + ClearHover(); + + ControlPoints.Empty(); + GeometrySet.Reset(); + DrawnControlSegments->Clear(); + DrawnControlPoints->Clear(); + SnapEngine.Reset(); +} + +int32 UCurveControlPointsMechanic::AppendPoint(const FVector3d& Point) +{ + return InsertPointAt(ControlPoints.Num(), Point); +} + +int32 UCurveControlPointsMechanic::InsertPointAt(int32 SequencePosition, const FVector3d& NewPointCoordinates, const int32* KnownPointID) +{ + // Add the point + int32 NewPointID = ControlPoints.InsertPointAt(SequencePosition, NewPointCoordinates, KnownPointID); + GeometrySet.AddPoint(NewPointID, NewPointCoordinates); + FRenderablePoint RenderablePoint((FVector)NewPointCoordinates, CurrentPointsColor, PointsSize); + DrawnControlPoints->InsertPoint(NewPointID, RenderablePoint); + + // See if we need to add some segments + if (ControlPoints.Num() > 1) + { + if (bIsLoop || SequencePosition != 0) + { + // Alter (or add) the preceding segment to go to the new point. + int32 PreviousSequencePosition = (SequencePosition + ControlPoints.Num() - 1) % ControlPoints.Num(); + int32 PreviousID = ControlPoints.GetPointIDAt(PreviousSequencePosition); + + FPolyline3d SegmentPolyline(TArray{ControlPoints.GetPointCoordinates(PreviousID), NewPointCoordinates}); + + if (DrawnControlSegments->IsLineValid(PreviousID)) + { + DrawnControlSegments->SetLineEnd(PreviousID, (FVector)NewPointCoordinates); + + GeometrySet.UpdateCurve(PreviousID, SegmentPolyline); + } + else + { + FRenderableLine RenderableSegment((FVector)ControlPoints.GetPointCoordinates(PreviousID), + (FVector)NewPointCoordinates, CurrentSegmentsColor, SegmentsThickness, DepthBias); + DrawnControlSegments->InsertLine(PreviousID, RenderableSegment); + + GeometrySet.AddCurve(PreviousID, SegmentPolyline); + } + } + if (bIsLoop || SequencePosition != ControlPoints.Num() - 1) + { + // Create a segment going to the next point + int32 NextSequencePosition = (SequencePosition + 1) % ControlPoints.Num(); + + FPolyline3d SegmentPolyline(TArray{ControlPoints.GetPointCoordinatesAt(NextSequencePosition), NewPointCoordinates}); + GeometrySet.AddCurve(NewPointID, SegmentPolyline); + + FRenderableLine RenderableSegment((FVector)NewPointCoordinates, (FVector)ControlPoints.GetPointCoordinatesAt(NextSequencePosition), + CurrentSegmentsColor, SegmentsThickness, DepthBias); + DrawnControlSegments->InsertLine(NewPointID, RenderableSegment); + } + } + + // Update the snapping support + SnapEngine.InsertHistoryPoint(NewPointCoordinates, SequencePosition); + if (bInteractiveInitializationMode) + { + // See if we have a new endpoint for interactive mode to snap to + if (SequencePosition == ControlPoints.Num() - 1) + { + SnapEngine.RemovePointTargetsByID(LastPointSnapID); + SnapEngine.AddPointTarget(NewPointCoordinates, LastPointSnapID, EndpointSnapPriority); + + // Also need to go ahead and regenerate snap targets, since we don't rely on a selection in initialization mode. + UpdateSnapTargetsForHover(); + } + if (SequencePosition == 0) + { + SnapEngine.RemovePointTargetsByID(FirstPointSnapID); + SnapEngine.AddPointTarget(NewPointCoordinates, FirstPointSnapID, EndpointSnapPriority); + } + } + + return NewPointID; +} + +void UCurveControlPointsMechanic::SetIsLoop(bool bIsLoopIn) +{ + // Only do stuff if things changed + if (bIsLoopIn != bIsLoop) + { + // Add/remove closing segment + if (ControlPoints.Num() > 1) + { + if (bIsLoopIn) + { + FPolyline3d SegmentPolyline(TArray{ControlPoints.GetPointCoordinates(ControlPoints.Last()), ControlPoints.GetPointCoordinates(ControlPoints.First())}); + GeometrySet.AddCurve(ControlPoints.Last(), SegmentPolyline); + + FRenderableLine RenderableSegment((FVector)ControlPoints.GetPointCoordinates(ControlPoints.Last()), + (FVector)ControlPoints.GetPointCoordinates(ControlPoints.First()), CurrentSegmentsColor, SegmentsThickness, DepthBias); + DrawnControlSegments->InsertLine(ControlPoints.Last(), RenderableSegment); + } + else + { + // Need to remove the loop closing segment + GeometrySet.RemoveCurve(ControlPoints.Last()); + DrawnControlSegments->RemoveLine(ControlPoints.Last()); + } + } + + bIsLoop = bIsLoopIn; + } +} + +void UCurveControlPointsMechanic::ExtractPointPositions(TArray& PositionsOut) +{ + for (int32 PointID : ControlPoints.PointIDItr()) + { + PositionsOut.Add(ControlPoints.GetPointCoordinates(PointID)); + } +} + +void UCurveControlPointsMechanic::SetInteractiveInitialization(bool bOn) +{ + // Only do things if things actually changed + if (bInteractiveInitializationMode != bOn) + { + bInteractiveInitializationMode = bOn; + + // The visualization is different colors in different modes. + CurrentSegmentsColor = bInteractiveInitializationMode ? InitializationCurveColor : NormalCurveColor; + CurrentPointsColor = bInteractiveInitializationMode ? InitializationCurveColor : NormalCurveColor; + DrawnControlPoints->SetAllPointsColor(CurrentPointsColor); + DrawnControlSegments->SetAllLinesColor(CurrentSegmentsColor); + + if (bOn) + { + // If we're in initialization mode, we can't have any selection, and we can't be in a loop. + ClearSelection(); + ClearHover(); + SetIsLoop(false); + + // We now need to be able to snap the new segment, especially against the first and last points, as + // this brings us out of initialization mode. + if (ControlPoints.Num() > 0) + { + SnapEngine.AddPointTarget(ControlPoints.GetPointCoordinatesAt(0), FirstPointSnapID, EndpointSnapPriority); + SnapEngine.AddPointTarget(ControlPoints.GetPointCoordinatesAt(ControlPoints.Last()), LastPointSnapID, EndpointSnapPriority); + } + + SnapEngine.RegenerateTargetLinesAround(ControlPoints.Num()); + } + else + { + // If we've left initialization mode, we no longer need to snap against the endpoints + SnapEngine.RemovePointTargetsByID(FirstPointSnapID); + SnapEngine.RemovePointTargetsByID(LastPointSnapID); + } + } +} + +void UCurveControlPointsMechanic::GizmoTransformStarted(UTransformProxy* Proxy) +{ + ParentTool->GetToolManager()->BeginUndoTransaction(PointMovementTransactionText); + + GizmoStartPosition = Proxy->GetTransform().GetTranslation(); + + SelectedPointStartPositions.SetNum(SelectedPointIDs.Num()); + for (int32 i = 0; i < SelectedPointIDs.Num(); ++i) + { + SelectedPointStartPositions[i] = ControlPoints.GetPointCoordinates(SelectedPointIDs[i]); + } + + if (SelectedPointIDs.Num() == 1) + { + SnapEngine.RegenerateTargetLinesAround(ControlPoints.GetSequencePosition(SelectedPointIDs[0]), bIsLoop); + } + + bGizmoBeingDragged = true; +} + +void UCurveControlPointsMechanic::GizmoTransformChanged(UTransformProxy* Proxy, FTransform Transform) +{ + if (SelectedPointIDs.Num() == 0 || !bGizmoBeingDragged) + { + return; + } + + bool bPointsChanged = false; + + FVector Displacement = Transform.GetTranslation() - GizmoStartPosition; + if (Displacement != FVector::ZeroVector) + { + // Do snapping only if we have a single point selected. + if (SelectedPointIDs.Num() == 1 && (bSnappingEnabled ^ bSnapToggle)) + { + FVector3d NewLocation = SelectedPointStartPositions[0] + Displacement; + if (bSnappingEnabled ^ bSnapToggle) + { + SnapEngine.UpdateSnappedPoint(NewLocation); + if (SnapEngine.HaveActiveSnap()) + { + NewLocation = SnapEngine.GetActiveSnapToPoint(); + } + } + + // When snapping, it is possible that a point didn't actually end up moving, and doesn't need updating. + if (ControlPoints.GetPointCoordinates(SelectedPointIDs[0]) != NewLocation) + { + UpdatePointLocation(SelectedPointIDs[0], NewLocation); + bPointsChanged = true; + } + } + else + { + for (int32 i = 0; i < SelectedPointIDs.Num(); ++i) + { + UpdatePointLocation(SelectedPointIDs[i], SelectedPointStartPositions[i] + Displacement); + } + bPointsChanged = true; + } + } + + if (bPointsChanged) + { + OnPointsChanged.Broadcast(); + } +} + +void UCurveControlPointsMechanic::GizmoTransformEnded(UTransformProxy* Proxy) +{ + for (int32 i = 0; i < SelectedPointIDs.Num(); ++i) + { + ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( + SelectedPointIDs[i], SelectedPointStartPositions[i], ControlPoints.GetPointCoordinates(SelectedPointIDs[i]), + CurrentChangeStamp), PointMovementTransactionText); + + // Note that we can't do this in UpdatePointLocation despite that being conceptually nice, because we don't + // want to change what we're snapping to as we drag a point around (technically we could make it work if we + // made the snap engine not reset snaps at each update, since the points that we're snapping to remain stationary, + // but best not to). + UpdateSnapHistoryPoint(ControlPoints.GetSequencePosition(SelectedPointIDs[i]), ControlPoints.GetPointCoordinates(SelectedPointIDs[i])); + } + + SelectedPointStartPositions.Reset(); + + // We may need to reset the gizmo if our snapping caused the final point position to differ from the gizmo position. + UpdateGizmoLocation(); + + // No need to draw the snap line anymore + SnapEngine.ResetActiveSnap(); + + UpdateSnapTargetsForHover(); + + ParentTool->GetToolManager()->EndUndoTransaction(); + + bGizmoBeingDragged = false; +} + +void UCurveControlPointsMechanic::UpdatePointLocation(int32 PointID, const FVector3d& NewLocation) +{ + ControlPoints.SetPointCoordinates(PointID, NewLocation); + GeometrySet.UpdatePoint(PointID, NewLocation); + DrawnControlPoints->SetPointPosition(PointID, (FVector)NewLocation); + + int32 SequencePosition = ControlPoints.GetSequencePosition(PointID); + + // Update the segment going to this point. + if (bIsLoop || PointID != ControlPoints.First()) + { + int32 PreviousSequencePosition = (SequencePosition + ControlPoints.Num() - 1) % ControlPoints.Num(); + DrawnControlSegments->SetLineEnd(ControlPoints.GetPointIDAt(PreviousSequencePosition), (FVector)NewLocation); + + FPolyline3d SegmentPolyline(TArray{ControlPoints.GetPointCoordinatesAt(PreviousSequencePosition), NewLocation}); + GeometrySet.UpdateCurve(ControlPoints.GetPointIDAt(PreviousSequencePosition), SegmentPolyline); + } + + // Update the segment going from this point. + if (bIsLoop || PointID != ControlPoints.Last()) + { + DrawnControlSegments->SetLineStart(PointID, (FVector)NewLocation); + + FPolyline3d SegmentPolyline(TArray{NewLocation, ControlPoints.GetPointCoordinatesAt((SequencePosition + 1) % ControlPoints.Num())}); + GeometrySet.UpdateCurve(PointID, SegmentPolyline); + } +} + + +bool UCurveControlPointsMechanic::HitTest(const FInputDeviceRay& ClickPos, FInputRayHit& ResultOut) +{ + FGeometrySet3::FNearest Nearest; + + // See if we are adding a new point (either in interactive initialization, or by adding a point on the end) + if (bInteractiveInitializationMode || + (bInsertPointToggle && !bIsLoop && SelectedPointIDs.Num() == 1 + && (SelectedPointIDs[0] == ControlPoints.First() || SelectedPointIDs[0] == ControlPoints.Last()))) + { + FVector3d HitPoint; + bool bHit = DrawPlane.RayPlaneIntersection(ClickPos.WorldRay.Origin, ClickPos.WorldRay.Direction, 2, HitPoint); + ResultOut = FInputRayHit(ClickPos.WorldRay.GetParameter((FVector)HitPoint)); + return bHit; + } + // Otherwise, see if we are in insert mode and hitting a segment + else if (bInsertPointToggle) + { + if (GeometrySet.FindNearestCurveToRay(ClickPos.WorldRay, Nearest, GeometrySetToleranceTest)) + { + ResultOut = FInputRayHit(Nearest.RayParam); + return true; + } + } + // See if we hit a point for selection + else if (GeometrySet.FindNearestPointToRay(ClickPos.WorldRay, Nearest, GeometrySetToleranceTest)) + { + ResultOut = FInputRayHit(Nearest.RayParam); + return true; + } + return false; +} + +FInputRayHit UCurveControlPointsMechanic::IsHitByClick(const FInputDeviceRay& ClickPos) +{ + FInputRayHit Result; + HitTest(ClickPos, Result); + return Result; +} + +void UCurveControlPointsMechanic::OnClicked(const FInputDeviceRay& ClickPos) +{ + FGeometrySet3::FNearest Nearest; + + if (bInteractiveInitializationMode) + { + FVector3d NewPointCoordinates; + if (!DrawPlane.RayPlaneIntersection(ClickPos.WorldRay.Origin, ClickPos.WorldRay.Direction, 2, NewPointCoordinates)) + { + // Missed the plane entirely (probably in ortho mode). Nothing to do. + return; + } + + // When snapping is disabled, we still need to be able to snap to the first/last points to get out of the mode. + bool bLeavingMode = false; + SnapEngine.UpdateSnappedPoint(NewPointCoordinates); + if (SnapEngine.HaveActiveSnap()) + { + if (SnapEngine.GetActiveSnapTargetID() == FirstPointSnapID || SnapEngine.GetActiveSnapTargetID() == LastPointSnapID) + { + ParentTool->GetToolManager()->BeginUndoTransaction(InitializationCompletedTransactionText); + + bool bClosedLoop = SnapEngine.GetActiveSnapTargetID() == FirstPointSnapID; + SetIsLoop(bClosedLoop); + SetInteractiveInitialization(false); + ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( + true, bClosedLoop, CurrentChangeStamp), InitializationCompletedTransactionText); + + ParentTool->GetToolManager()->EndUndoTransaction(); + + bLeavingMode = true; + } + else if (bSnappingEnabled ^ bSnapToggle) + { + NewPointCoordinates = SnapEngine.GetActiveSnapToPoint(); + } + + // Don't want to draw the snap line after the click + SnapEngine.ResetActiveSnap(); + } + + if (bLeavingMode) + { + OnModeChanged.Broadcast(); + } + else + { + // Adding a point in initialization mode + ParentTool->GetToolManager()->BeginUndoTransaction(PointAdditionTransactionText); + + int32 NewPointID = InsertPointAt(ControlPoints.Num(), NewPointCoordinates); + ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( + ControlPoints.Num() - 1, NewPointID, NewPointCoordinates, true, CurrentChangeStamp), PointAdditionTransactionText); + + ParentTool->GetToolManager()->EndUndoTransaction(); + OnPointsChanged.Broadcast(); + + // Prepare snap targets for next point + UpdateSnapTargetsForHover(); + } + } + + else if (bInsertPointToggle) + { + // Adding on an existing edge takes priority to adding to the end. + if (GeometrySet.FindNearestCurveToRay(ClickPos.WorldRay, Nearest, GeometrySetToleranceTest)) + { + ParentTool->GetToolManager()->BeginUndoTransaction(PointAdditionTransactionText); + + int32 SequencePosition = ControlPoints.GetSequencePosition(Nearest.ID); + int32 NewPointID = InsertPointAt(SequencePosition + 1, Nearest.NearestGeoPoint); + ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( + SequencePosition + 1, NewPointID, Nearest.NearestGeoPoint, true, CurrentChangeStamp), PointAdditionTransactionText); + + ChangeSelection(NewPointID, false); + + ParentTool->GetToolManager()->EndUndoTransaction(); + OnPointsChanged.Broadcast(); + } + + // Try to add to one of the ends + else if (SelectedPointIDs.Num() == 1 && !bIsLoop + && (SelectedPointIDs[0] == ControlPoints.First() || SelectedPointIDs[0] == ControlPoints.Last())) + { + ParentTool->GetToolManager()->BeginUndoTransaction(PointAdditionTransactionText); + + int32 NewPointID = -1; + FVector3d NewPointCoordinates; + DrawPlane.RayPlaneIntersection(ClickPos.WorldRay.Origin, ClickPos.WorldRay.Direction, 2, NewPointCoordinates); + + if (bSnappingEnabled ^ bSnapToggle) + { + SnapEngine.UpdateSnappedPoint(NewPointCoordinates); + if (SnapEngine.HaveActiveSnap()) + { + NewPointCoordinates = SnapEngine.GetActiveSnapToPoint(); + SnapEngine.ResetActiveSnap(); + } + } + + // Do the actual insertion + if (SelectedPointIDs[0] == ControlPoints.First()) + { + NewPointID = InsertPointAt(0, NewPointCoordinates); + ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( + 0, NewPointID, NewPointCoordinates, true, CurrentChangeStamp), PointAdditionTransactionText); + } + else + { + NewPointID = InsertPointAt(ControlPoints.Num(), NewPointCoordinates); + ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( + ControlPoints.Num() - 1, NewPointID, NewPointCoordinates, true, CurrentChangeStamp), PointAdditionTransactionText); + } + ChangeSelection(NewPointID, false); + + ParentTool->GetToolManager()->EndUndoTransaction(); + OnPointsChanged.Broadcast(); + } + } + // Otherwise, check for plain old selection + else if (GeometrySet.FindNearestPointToRay(ClickPos.WorldRay, Nearest, GeometrySetToleranceTest)) + { + ParentTool->GetToolManager()->BeginUndoTransaction(PointSelectionTransactionText); + + ChangeSelection(Nearest.ID, bAddToSelectionToggle); + + ParentTool->GetToolManager()->EndUndoTransaction(); + } +} + +void UCurveControlPointsMechanic::ChangeSelection(int32 NewPointID, bool bAddToSelection) +{ + // If not adding to selection, clear it + if (!bAddToSelection && SelectedPointIDs.Num() > 0) + { + for (int32 PointID : SelectedPointIDs) + { + // We check for validity here because we'd like to be able to use this function to deselect points after + // deleting them. + if (DrawnControlPoints->IsPointValid(PointID)) + { + DrawnControlPoints->SetPointColor(PointID, CurrentPointsColor); + + ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( + PointID, false, CurrentChangeStamp), PointDeselectionTransactionText); + } + } + + SelectedPointIDs.Empty(); + } + + // We check for validity here because giving an invalid id (such as -1) with bAddToSelection == false + // is an easy way to clear the selection. + if (ControlPoints.IsValidPoint(NewPointID)) + { + if (bAddToSelection && DeselectPoint(NewPointID)) + { + ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( + NewPointID, false, CurrentChangeStamp), PointDeselectionTransactionText); + } + else + { + SelectPoint(NewPointID); + + ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( + NewPointID, true, CurrentChangeStamp), PointSelectionTransactionText); + } + } + + UpdateGizmoLocation(); +} + +void UCurveControlPointsMechanic::UpdateGizmoLocation() +{ + if (!PointTransformGizmo) + { + return; + } + + if (SelectedPointIDs.Num() == 0) + { + PointTransformGizmo->SetVisibility(false); + } + else + { + FVector3d NewGizmoLocation; + for (int32 PointID : SelectedPointIDs) + { + NewGizmoLocation += ControlPoints.GetPointCoordinates(PointID); + } + NewGizmoLocation /= SelectedPointIDs.Num(); + + PointTransformGizmo->ReinitializeGizmoTransform(FTransform((FQuat)DrawPlane.Rotation, (FVector)NewGizmoLocation)); + PointTransformGizmo->SetVisibility(true); + } +} + +void UCurveControlPointsMechanic::SetPlane(const FFrame3d& DrawPlaneIn) +{ + DrawPlane = DrawPlaneIn; + UpdateGizmoLocation(); // Gizmo is constrained to plane + + SnapEngine.Plane = DrawPlane; +} + +void UCurveControlPointsMechanic::SetSnappingEnabled(bool bOn) +{ + bSnappingEnabled = bOn; +} + +void UCurveControlPointsMechanic::AddSnapLine(int32 LineID, const FLine3d& Line) +{ + SnapEngine.AddLineTarget(Line, LineSnapIDMin + LineID, LineSnapPriority); +} + +void UCurveControlPointsMechanic::RemoveSnapLine(int32 LineID) +{ + SnapEngine.RemoveLineTargetsByID(LineSnapIDMin + LineID); +} + +bool UCurveControlPointsMechanic::DeselectPoint(int32 PointID) +{ + bool PointFound = false; + int32 IndexInSelection; + if (SelectedPointIDs.Find(PointID, IndexInSelection)) + { + SelectedPointIDs.RemoveAt(IndexInSelection); + DrawnControlPoints->SetPointColor(PointID, CurrentPointsColor); + + PointFound = true; + + UpdateSnapTargetsForHover(); + } + + return PointFound; +} + +void UCurveControlPointsMechanic::SelectPoint(int32 PointID) +{ + SelectedPointIDs.Add(PointID); + DrawnControlPoints->SetPointColor(PointID, SelectedColor); + + UpdateSnapTargetsForHover(); +} + +void UCurveControlPointsMechanic::ClearSelection() +{ + ChangeSelection(-1, false); +} + +FInputRayHit UCurveControlPointsMechanic::BeginHoverSequenceHitTest(const FInputDeviceRay& PressPos) +{ + FInputRayHit Result; + HitTest(PressPos, Result); + return Result; +} + +void UCurveControlPointsMechanic::OnBeginHover(const FInputDeviceRay& DevicePos) +{ + OnUpdateHover(DevicePos); +} + +void UCurveControlPointsMechanic::ClearHover() +{ + if (HoveredPointID >= 0) + { + DrawnControlPoints->SetPointColor(HoveredPointID, PreHoverPointColor); + HoveredPointID = -1; + } + PreviewPoint->Clear(); + PreviewSegment->Clear(); +} + +void UCurveControlPointsMechanic::UpdateSnapTargetsForHover() +{ + if (ControlPoints.Num() == 0) + { + // Clear targets + SnapEngine.RegenerateTargetLines(false, false); + return; + } + + if (bInteractiveInitializationMode) + { + SnapEngine.RegenerateTargetLinesAround(ControlPoints.Num()); + } + else if (SelectedPointIDs.Num() == 1) + { + if (SelectedPointIDs[0] == ControlPoints.First()) + { + SnapEngine.RegenerateTargetLinesAround(-1); + } + else if (SelectedPointIDs[0] == ControlPoints.Last()) + { + SnapEngine.RegenerateTargetLinesAround(ControlPoints.Num()); + } + else + { + SnapEngine.RegenerateTargetLines(false, false); // clear targets + } + } + else + { + SnapEngine.RegenerateTargetLines(false, false); // clear targets + } +} + +void UCurveControlPointsMechanic::UpdateSnapHistoryPoint(int32 Index, FVector3d NewPosition) +{ + SnapEngine.RemoveHistoryPoint(Index); + SnapEngine.InsertHistoryPoint(NewPosition, Index); +} + +bool UCurveControlPointsMechanic::OnUpdateHover(const FInputDeviceRay& DevicePos) +{ + FGeometrySet3::FNearest Nearest; + + // In edit mode, point insertion on an edge has priority. + if (!bInteractiveInitializationMode && bInsertPointToggle && GeometrySet.FindNearestCurveToRay(DevicePos.WorldRay, Nearest, GeometrySetToleranceTest)) + { + ClearHover(); + FRenderablePoint RenderablePoint((FVector)Nearest.NearestGeoPoint, PreviewColor, PointsSize); + PreviewPoint->InsertPoint(0, RenderablePoint); + } + + // Otherwise, see if we are hovering an insertion on the end. This is always the case in interactive initialization mode, + // and requires having a point on the end selected in edit mode. + else if (bInteractiveInitializationMode || + (bInsertPointToggle && SelectedPointIDs.Num() == 1 && (SelectedPointIDs[0] == ControlPoints.First() || SelectedPointIDs[0] == ControlPoints.Last()))) + { + FVector3d HitPoint; + if (!DrawPlane.RayPlaneIntersection(DevicePos.WorldRay.Origin, DevicePos.WorldRay.Direction, 2, HitPoint)) + { + // We're probably looking in an ortho viewport and missing the plane. End the hover. + return false; + } + else + { + // Snap the hitpoint if applicable + if (bInteractiveInitializationMode || (bSnappingEnabled ^ bSnapToggle)) + { + SnapEngine.UpdateSnappedPoint(HitPoint); + if (SnapEngine.HaveActiveSnap()) + { + // We always snap to the start/end points because that's how we get out of initialization mode, and we don't want to + // risk the user not knowing what to do if they set snapping to be disabled. + if ((bSnappingEnabled ^ bSnapToggle) || SnapEngine.GetActiveSnapTargetID() == FirstPointSnapID + || SnapEngine.GetActiveSnapTargetID() == LastPointSnapID) + { + HitPoint = SnapEngine.GetActiveSnapToPoint(); + } + else + { + // If we're not snapping, we don't want to render the snap line + SnapEngine.ResetActiveSnap(); + } + } + } + + // Redraw preview + ClearHover(); + FRenderablePoint RenderablePoint((FVector)HitPoint, PreviewColor, PointsSize); + PreviewPoint->InsertPoint(0, RenderablePoint); + + if (ControlPoints.Num() > 0) + { + int32 OriginPointID = bInteractiveInitializationMode ? ControlPoints.Last() : SelectedPointIDs[0]; + + FRenderableLine RenderableLine((FVector)ControlPoints.GetPointCoordinates(OriginPointID), + (FVector)HitPoint, PreviewColor, SegmentsThickness, DepthBias); + PreviewSegment->InsertLine(0, RenderableLine); + } + } + } + + // Otherwise, see if we're hovering a point for selection + else if (!bInsertPointToggle && GeometrySet.FindNearestPointToRay(DevicePos.WorldRay, Nearest, GeometrySetToleranceTest)) + { + // Only need to update the hover if we changed the point + if (Nearest.ID != HoveredPointID) + { + ClearHover(); + HoveredPointID = Nearest.ID; + PreHoverPointColor = DrawnControlPoints->GetPoint(HoveredPointID).Color; + DrawnControlPoints->SetPointColor(HoveredPointID, HoverColor); + } + } + else + { + // Not hovering anything, so done hovering + return false; + } + + return true; +} + +void UCurveControlPointsMechanic::OnEndHover() +{ + ClearHover(); + SnapEngine.ResetActiveSnap(); +} + +// Detects Ctrl and Shift key states +void UCurveControlPointsMechanic::OnUpdateModifierState(int ModifierID, bool bIsOn) +{ + if (ModifierID == ShiftModifierId) + { + bAddToSelectionToggle = bIsOn; + bSnapToggle = bIsOn; + } + else if (ModifierID == CtrlModifierId) + { + bInsertPointToggle = bIsOn; + } +} + + +void UCurveControlPointsMechanic::DeleteSelectedPoints() +{ + if (SelectedPointIDs.Num() == 0) + { + return; + } + + ParentTool->GetToolManager()->BeginUndoTransaction(PointDeletionTransactionText); + + // There are minor inefficiencies in the way we delete multiple points since we sometimes do edge updates + // for edges that get deleted later in the loop, and we upate the map inside ControlPoints gets updated each + // time, but avoiding these would make the code more cumbersome. + + // For the purposes of undo/redo, it is more convenient to clear the selection before deleting the points, so that + // on undo, the points get added back before being reselected. + TArray PointsToDelete = SelectedPointIDs; + ClearSelection(); + + for (int32 PointID : PointsToDelete) + { + ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( + ControlPoints.GetSequencePosition(PointID), PointID, ControlPoints.GetPointCoordinates(PointID), + false, CurrentChangeStamp), PointDeletionTransactionText); + DeletePoint(PointID); + } + + ParentTool->GetToolManager()->EndUndoTransaction(); + OnPointsChanged.Broadcast(); +} + +int32 UCurveControlPointsMechanic::DeletePoint(int32 PointID) +{ + int32 SequencePosition = ControlPoints.GetSequencePosition(PointID); + + // Deal with the segments: + // See if there is a preceding point + if (ControlPoints.Num() > 1 && (bIsLoop || SequencePosition > 0)) + { + int32 PreviousPointID = ControlPoints.GetPointIDAt((SequencePosition + ControlPoints.Num() - 1) % ControlPoints.Num()); + + // See if there is a point to connect to after the about-to-be-deleted one + if (ControlPoints.Num() > 2 && (bIsLoop || SequencePosition < ControlPoints.Num() - 1)) + { + // Move edge + FVector3d NextPointCoordinates = ControlPoints.GetPointCoordinatesAt((SequencePosition + 1) % ControlPoints.Num()); + + DrawnControlSegments->SetLineEnd(PreviousPointID, (FVector)NextPointCoordinates); + FPolyline3d SegmentPolyline(TArray{ControlPoints.GetPointCoordinates(PreviousPointID), NextPointCoordinates}); + GeometrySet.UpdateCurve(PreviousPointID, SegmentPolyline); + } + else + { + // Delete edge + GeometrySet.RemoveCurve(PreviousPointID); + DrawnControlSegments->RemoveLine(PreviousPointID); + } + } + + // Delete outgoing edge if there is one. + if (DrawnControlSegments->IsLineValid(PointID)) + { + GeometrySet.RemoveCurve(PointID); + DrawnControlSegments->RemoveLine(PointID); + } + + // Delete the point itself. + GeometrySet.RemovePoint(PointID); + DrawnControlPoints->RemovePoint(PointID); + ControlPoints.RemovePointAt(SequencePosition); + + SnapEngine.RemoveHistoryPoint(SequencePosition); + + // Even though initialization mode doesn't allow for explicit deletion, it could happen through undo. + if (bInteractiveInitializationMode) + { + // Update start/end snap targets, and update snapping for hover if we deleted the last point. + if (SequencePosition == ControlPoints.Num()) + { + SnapEngine.RemovePointTargetsByID(LastPointSnapID); + if (ControlPoints.Num() > 0) + { + SnapEngine.AddPointTarget(ControlPoints.GetPointCoordinates(ControlPoints.Last()), LastPointSnapID, EndpointSnapPriority); + } + + UpdateSnapTargetsForHover(); + } + if (SequencePosition == 0) + { + SnapEngine.RemovePointTargetsByID(FirstPointSnapID); + if (ControlPoints.Num() > 0) + { + SnapEngine.AddPointTarget(ControlPoints.GetPointCoordinates(ControlPoints.First()), FirstPointSnapID, EndpointSnapPriority); + } + } + } + + return PointID; +} + + +// ==================== Undo/redo object functions ==================== + +FCurveControlPointsMechanicSelectionChange::FCurveControlPointsMechanicSelectionChange(int32 PointIDIn, + bool AddedIn, int32 ChangeStampIn) + : PointID(PointIDIn) + , Added(AddedIn) + , ChangeStamp(ChangeStampIn) +{} + +void FCurveControlPointsMechanicSelectionChange::Apply(UObject* Object) +{ + UCurveControlPointsMechanic* Mechanic = Cast(Object); + if (Added) + { + Mechanic->SelectPoint(PointID); + } + else + { + Mechanic->DeselectPoint(PointID); + } + Mechanic->UpdateGizmoLocation(); +} + +void FCurveControlPointsMechanicSelectionChange::Revert(UObject* Object) +{ + UCurveControlPointsMechanic* Mechanic = Cast(Object); + if (Added) + { + Mechanic->DeselectPoint(PointID); + } + else + { + Mechanic->SelectPoint(PointID); + } + Mechanic->UpdateGizmoLocation(); +} + +FString FCurveControlPointsMechanicSelectionChange::ToString() const +{ + return TEXT("FCurveControlPointsMechanicSelectionChange"); +} + + +FCurveControlPointsMechanicInsertionChange::FCurveControlPointsMechanicInsertionChange(int32 SequencePositionIn, + int32 PointIDIn, const FVector3d& CoordinatesIn, bool AddedIn, int32 ChangeStampIn) + : SequencePosition(SequencePositionIn) + , PointID(PointIDIn) + , Coordinates(CoordinatesIn) + , Added(AddedIn) + , ChangeStamp(ChangeStampIn) +{} + +void FCurveControlPointsMechanicInsertionChange::Apply(UObject* Object) +{ + UCurveControlPointsMechanic* Mechanic = Cast(Object); + if (Added) + { + Mechanic->InsertPointAt(SequencePosition, Coordinates, &PointID); + } + else + { + Mechanic->DeletePoint(PointID); + } + Mechanic->OnPointsChanged.Broadcast(); +} + +void FCurveControlPointsMechanicInsertionChange::Revert(UObject* Object) +{ + UCurveControlPointsMechanic* Mechanic = Cast(Object); + if (Added) + { + Mechanic->DeletePoint(PointID); + } + else + { + Mechanic->InsertPointAt(SequencePosition, Coordinates, &PointID); + } + Mechanic->OnPointsChanged.Broadcast(); +} + +FString FCurveControlPointsMechanicInsertionChange::ToString() const +{ + return TEXT("FCurveControlPointsMechanicSelectionChange"); +} + +FCurveControlPointsMechanicModeChange::FCurveControlPointsMechanicModeChange(bool bDoneWithInitializationIn, bool bIsLoopIn, int32 ChangeStampIn) + : bDoneWithInitialization(bDoneWithInitializationIn) + , bIsLoop(bIsLoopIn) + , ChangeStamp(ChangeStampIn) +{} + +void FCurveControlPointsMechanicModeChange::Apply(UObject* Object) +{ + UCurveControlPointsMechanic* Mechanic = Cast(Object); + if (bDoneWithInitialization) + { + Mechanic->SetInteractiveInitialization(false); + Mechanic->SetIsLoop(bIsLoop); + } + else + { + Mechanic->SetInteractiveInitialization(true); + Mechanic->SetIsLoop(false); + } + Mechanic->OnModeChanged.Broadcast(); +} + +void FCurveControlPointsMechanicModeChange::Revert(UObject* Object) +{ + UCurveControlPointsMechanic* Mechanic = Cast(Object); + if (bDoneWithInitialization) + { + Mechanic->SetInteractiveInitialization(true); + Mechanic->SetIsLoop(false); + } + else + { + Mechanic->SetInteractiveInitialization(false); + Mechanic->SetIsLoop(bIsLoop); + } + Mechanic->OnModeChanged.Broadcast(); +} + +FString FCurveControlPointsMechanicModeChange::ToString() const +{ + return TEXT("FCurveControlPointsMechanicModeChange"); +} + + +FCurveControlPointsMechanicMovementChange::FCurveControlPointsMechanicMovementChange(int32 PointIDIn, + const FVector3d& OriginalPositionIn, const FVector3d& NewPositionIn, int32 ChangeStampIn) + : PointID(PointIDIn) + , OriginalPosition(OriginalPositionIn) + , NewPosition(NewPositionIn) + , ChangeStamp(ChangeStampIn) +{} + +void FCurveControlPointsMechanicMovementChange::Apply(UObject* Object) +{ + UCurveControlPointsMechanic* Mechanic = Cast(Object); + Mechanic->UpdatePointLocation(PointID, NewPosition); + Mechanic->UpdateGizmoLocation(); + Mechanic->UpdateSnapHistoryPoint(Mechanic->ControlPoints.GetSequencePosition(PointID), NewPosition); + Mechanic->UpdateSnapTargetsForHover(); // Only actually necessary if this was an end point, but easy enough to do + Mechanic->OnPointsChanged.Broadcast(); +} + +void FCurveControlPointsMechanicMovementChange::Revert(UObject* Object) +{ + UCurveControlPointsMechanic* Mechanic = Cast(Object); + Mechanic->UpdatePointLocation(PointID, OriginalPosition); + Mechanic->UpdateGizmoLocation(); + Mechanic->UpdateSnapHistoryPoint(Mechanic->ControlPoints.GetSequencePosition(PointID), OriginalPosition); + Mechanic->UpdateSnapTargetsForHover(); + Mechanic->OnPointsChanged.Broadcast(); +} + +FString FCurveControlPointsMechanicMovementChange::ToString() const +{ + return TEXT("FCurveControlPointsMechanicMovementChange"); +} + + + + +// ==================== FOrderedPoints functions ==================== + +UCurveControlPointsMechanic::FOrderedPoints::FOrderedPoints(const FOrderedPoints& ToCopy) + : Vertices(ToCopy.Vertices) + , Sequence(ToCopy.Sequence) +{ +} + +UCurveControlPointsMechanic::FOrderedPoints::FOrderedPoints(const TArray& PointSequence) +{ + ReInitialize(PointSequence); +} + +int32 UCurveControlPointsMechanic::FOrderedPoints::AppendPoint(const FVector3d& PointCoordinates) +{ + int32 PointID = Vertices.Add(PointCoordinates); + Sequence.Add(PointID); + PointIDToSequencePosition.Add(PointID, Sequence.Num()-1); + return PointID; +} + +int32 UCurveControlPointsMechanic::FOrderedPoints::InsertPointAt(int32 SequencePosition, + const FVector3d& VertCoordinates, const int32* KnownPointID) +{ + // Everything from this point onward moves further in the sequence, so update map + for (int32 i = SequencePosition; i < Sequence.Num(); ++i) + { + ++PointIDToSequencePosition[Sequence[i]]; + } + + int32 PointID; + if (KnownPointID) + { + Vertices.Insert(*KnownPointID, VertCoordinates); + PointID = *KnownPointID; + } + else + { + PointID = Vertices.Add(VertCoordinates); + } + + Sequence.Insert(PointID, SequencePosition); + PointIDToSequencePosition.Add(PointID, SequencePosition); + return PointID; +} + +int32 UCurveControlPointsMechanic::FOrderedPoints::RemovePointAt(int32 SequencePosition) +{ + check(SequencePosition >= 0 && SequencePosition < Sequence.Num()); + + // Everything past this point moves back in sequence, so update map + for (int32 i = SequencePosition + 1; i < Sequence.Num(); ++i) + { + --PointIDToSequencePosition[Sequence[i]]; + } + + int32 PointID = Sequence[SequencePosition]; + Vertices.RemoveAt(PointID); + Sequence.RemoveAt(SequencePosition); + PointIDToSequencePosition.Remove(PointID); + + return PointID; +} + +void UCurveControlPointsMechanic::FOrderedPoints::Empty() +{ + Vertices.Empty(); + Sequence.Empty(); + PointIDToSequencePosition.Empty(); +} + +void UCurveControlPointsMechanic::FOrderedPoints::ReInitialize(const TArray& PointSequence) +{ + Empty(); + + Vertices.Reserve(PointSequence.Num()); + Sequence.Reserve(PointSequence.Num()); + PointIDToSequencePosition.Reserve(PointSequence.Num()); + for (int i = 0; i < PointSequence.Num(); ++i) + { + int32 PointID = Vertices.Add(PointSequence[i]); + Sequence.Add(PointID); + PointIDToSequencePosition.Add(PointID, i); + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/MeshOpPreviewHelpers.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/MeshOpPreviewHelpers.cpp index 0b9e21bffc21..bc2288795fa8 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/MeshOpPreviewHelpers.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/MeshOpPreviewHelpers.cpp @@ -92,6 +92,17 @@ void UMeshOpPreviewWithBackgroundCompute::InvalidateResult() } +bool UMeshOpPreviewWithBackgroundCompute::GetCurrentResultCopy(FDynamicMesh3& MeshOut, bool bOnlyIfValid) +{ + if ( HaveValidResult() || bOnlyIfValid == false) + { + MeshOut.Copy( *PreviewMesh->GetMesh() ); + return true; + } + return false; +} + + void UMeshOpPreviewWithBackgroundCompute::ConfigureMaterials(UMaterialInterface* StandardMaterialIn, UMaterialInterface* WorkingMaterialIn) { TArray Materials; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Physics/PhysicsDataCollection.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Physics/PhysicsDataCollection.cpp new file mode 100644 index 000000000000..90a2cefb73f0 --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Physics/PhysicsDataCollection.cpp @@ -0,0 +1,84 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Physics/PhysicsDataCollection.h" +#include "Physics/CollisionGeometryConversion.h" + +#include "Engine/Classes/Engine/StaticMesh.h" +#include "Engine/Classes/Components/StaticMeshComponent.h" +#include "Engine/Classes/PhysicsEngine/BodySetup.h" + + + +void FPhysicsDataCollection::InitializeFromComponent(const UActorComponent* Component, bool bInitializeAggGeom) +{ + const UStaticMeshComponent* StaticMeshComponent = CastChecked(Component); + const UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh(); + + SourceComponent = StaticMeshComponent; + BodySetup = StaticMesh->BodySetup; + + ExternalScale3D = FVector(1.f, 1.f, 1.f); + + if (bInitializeAggGeom) + { + AggGeom = BodySetup->AggGeom; + // transfer AggGeom to FSimpleShapeSet3d... + } +} + + +void FPhysicsDataCollection::InitializeFromExisting(const FPhysicsDataCollection& Other) +{ + SourceComponent = Other.SourceComponent; + BodySetup = Other.BodySetup; + + ExternalScale3D = Other.ExternalScale3D; +} + + + +void FPhysicsDataCollection::CopyGeometryFromExisting(const FPhysicsDataCollection& Other) +{ + Geometry = Other.Geometry; + AggGeom = Other.AggGeom; +} + + +void FPhysicsDataCollection::ClearAggregate() +{ + AggGeom = FKAggregateGeom(); +} + +void FPhysicsDataCollection::CopyGeometryToAggregate() +{ + for (FBoxShape3d& BoxGeom : Geometry.Boxes) + { + FKBoxElem Element; + UE::Geometry::GetFKElement(BoxGeom.Box, Element); + AggGeom.BoxElems.Add(Element); + } + + for (FSphereShape3d& SphereGeom : Geometry.Spheres) + { + FKSphereElem Element; + UE::Geometry::GetFKElement(SphereGeom.Sphere, Element); + AggGeom.SphereElems.Add(Element); + } + + for (FCapsuleShape3d& CapsuleGeom : Geometry.Capsules) + { + FKSphylElem Element; + UE::Geometry::GetFKElement(CapsuleGeom.Capsule, Element); + AggGeom.SphylElems.Add(Element); + } + + for (FConvexShape3d& ConvexGeom : Geometry.Convexes) + { + FKConvexElem Element; + UE::Geometry::GetFKElement(ConvexGeom.Mesh, Element); + AggGeom.ConvexElems.Add(Element); + } +} + + + diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/PreviewMesh.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/PreviewMesh.cpp index 13d908d610a7..d358148d8d81 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/PreviewMesh.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/PreviewMesh.cpp @@ -196,7 +196,7 @@ void UPreviewMesh::SetVisible(bool bVisible) { if (DynamicMeshComponent != nullptr) { - DynamicMeshComponent->SetVisibility(bVisible); + DynamicMeshComponent->SetVisibility(bVisible, true); } } @@ -237,6 +237,19 @@ void UPreviewMesh::UpdatePreview(const FDynamicMesh3* Mesh) } } +void UPreviewMesh::UpdatePreview(FDynamicMesh3&& Mesh) +{ + DynamicMeshComponent->SetDrawOnTop(this->bDrawOnTop); + + *(DynamicMeshComponent->GetMesh()) = MoveTemp(Mesh); + DynamicMeshComponent->NotifyMeshUpdated(); + + if (bBuildSpatialDataStructure) + { + MeshAABBTree.SetMesh(DynamicMeshComponent->GetMesh(), true); + } +} + const FDynamicMesh3* UPreviewMesh::GetMesh() const { diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Selection/GroupTopologySelector.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Selection/GroupTopologySelector.cpp index e9bdf638b876..a60cf7216f11 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Selection/GroupTopologySelector.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Selection/GroupTopologySelector.cpp @@ -6,9 +6,10 @@ #include "ToolDataVisualizer.h" #include "ToolSceneQueriesUtil.h" -// Local utility function forward declaration +// Local utility function forward declarations bool IsOccluded(const FGeometrySet3::FNearest& ClosestElement, const FVector3d& ViewOrigin, const FDynamicMeshAABBTree3* Spatial); - +void AddNewEdgeLoopEdgesFromCorner(const FGroupTopology& Topology, int32 EdgeID, int32 CornerID, TSet& EdgeSet); +bool GetNextEdgeLoopEdge(const FGroupTopology& Topology, int32 IncomingEdgeID, int32 CornerID, int32& NextEdgeIDOut); FGroupTopologySelector::FGroupTopologySelector() { @@ -87,24 +88,8 @@ const FGeometrySet3& FGroupTopologySelector::GetGeometrySet() return GeometrySet; } - -void FGroupTopologySelector::UpdateEnableFlags(bool bFaceHits, bool bEdgeHits, bool bCornerHits) -{ - bEnableFaceHits = bFaceHits; - bEnableEdgeHits = bEdgeHits; - bEnableCornerHits = bCornerHits; -} - -void FGroupTopologySelector::UpdateSelectionModeFlags(bool bPreferAlignedElementIn, bool bSelectDownRayIn, bool bIgnoreOcclusionIn) -{ - bPreferAlignedElement = bPreferAlignedElementIn; - bSelectDownRay = bSelectDownRayIn; - bIgnoreOcclusion = bIgnoreOcclusionIn; -} - - -bool FGroupTopologySelector::FindSelectedElement(const FRay3d& Ray, FGroupTopologySelection& ResultOut, - FVector3d& SelectedPositionOut, FVector3d& SelectedNormalOut, int32* EdgeSegmentIdOut) +bool FGroupTopologySelector::FindSelectedElement(const FSelectionSettings& Settings, const FRay3d& Ray, + FGroupTopologySelection& ResultOut, FVector3d& SelectedPositionOut, FVector3d& SelectedNormalOut, int32* EdgeSegmentIdOut) { // These get used for finding intersections with triangles and corners/edges, repectively. FDynamicMeshAABBTree3* Spatial = GetSpatial(); @@ -128,25 +113,25 @@ bool FGroupTopologySelector::FindSelectedElement(const FRay3d& Ray, FGroupTopolo } // Deal with corner hits first (and edges that project to a corner) - if (bEnableCornerHits || (bEnableEdgeHits && bPreferAlignedElement)) + if (Settings.bEnableCornerHits || (Settings.bEnableEdgeHits && Settings.bPreferProjectedElement)) { - if (DoCornerBasedSelection(Ray, Spatial, TopoSpatial, ResultOut, SelectedPositionOut, EdgeSegmentIdOut)) + if (DoCornerBasedSelection(Settings, Ray, Spatial, TopoSpatial, ResultOut, SelectedPositionOut, EdgeSegmentIdOut)) { return true; } } // If corner selection didn't yield results, try edge selection - if (bEnableEdgeHits || (bEnableFaceHits && bPreferAlignedElement)) + if (Settings.bEnableEdgeHits || (Settings.bEnableFaceHits && Settings.bPreferProjectedElement)) { - if (DoEdgeBasedSelection(Ray, Spatial, TopoSpatial, ResultOut, SelectedPositionOut, EdgeSegmentIdOut)) + if (DoEdgeBasedSelection(Settings, Ray, Spatial, TopoSpatial, ResultOut, SelectedPositionOut, EdgeSegmentIdOut)) { return true; } } // If we still haven't found a selection, go ahead and select the face that we found earlier - if (bEnableFaceHits && bActuallyHitSurface) + if (Settings.bEnableFaceHits && bActuallyHitSurface) { ResultOut.SelectedGroupIDs.Add(Topology->GetGroupID(HitTriangleID)); SelectedPositionOut = TriangleHitPos; @@ -156,7 +141,8 @@ bool FGroupTopologySelector::FindSelectedElement(const FRay3d& Ray, FGroupTopolo return false; } -bool FGroupTopologySelector::DoCornerBasedSelection(const FRay3d& Ray, FDynamicMeshAABBTree3* Spatial, const FGeometrySet3& TopoSpatial, +bool FGroupTopologySelector::DoCornerBasedSelection(const FSelectionSettings& Settings, + const FRay3d& Ray, FDynamicMeshAABBTree3* Spatial, const FGeometrySet3& TopoSpatial, FGroupTopologySelection& ResultOut, FVector3d& SelectedPositionOut, int32 *EdgeSegmentIdOut) const { // These will store our results, depending on whether we select all along the ray or not. @@ -168,7 +154,7 @@ bool FGroupTopologySelector::DoCornerBasedSelection(const FRay3d& Ray, FDynamicM // Start by getting the closest element const FGeometrySet3::FNearest* ClosestElement = nullptr; - if (!bSelectDownRay) + if (!Settings.bSelectDownRay) { if (TopoSpatial.FindNearestPointToRay(Ray, SingleElement, PointsWithinToleranceTest)) { @@ -199,7 +185,7 @@ bool FGroupTopologySelector::DoCornerBasedSelection(const FRay3d& Ray, FDynamicM } // Also bail if the closest element is not visible. - if (!bIgnoreOcclusion && IsOccluded(*ClosestElement, Ray.Origin, Spatial)) + if (!Settings.bIgnoreOcclusion && IsOccluded(*ClosestElement, Ray.Origin, Spatial)) { return false; } @@ -212,7 +198,7 @@ bool FGroupTopologySelector::DoCornerBasedSelection(const FRay3d& Ray, FDynamicM // orthographic view, we need to see that they lie on a ray with view ray direction and closest corner origin, whereas in // perspective, they would need to lie on a ray from camer through closest corner (which will differ due to tolerance). // Because the "select down ray" behavior is only useful in orthographic viewports in the first place, we do it that way. - if (bSelectDownRay) + if (Settings.bSelectDownRay) { DownRayElements.Add(ClosestElement->ID); for (const FGeometrySet3::FNearest& Element : ElementsWithinTolerance) @@ -233,7 +219,7 @@ bool FGroupTopologySelector::DoCornerBasedSelection(const FRay3d& Ray, FDynamicM } // Try to select edges that project to corners. - if (bPreferAlignedElement && bEnableEdgeHits) + if (Settings.bPreferProjectedElement && Settings.bEnableEdgeHits) { TSet AddedTopologyEdges; @@ -265,7 +251,7 @@ bool FGroupTopologySelector::DoCornerBasedSelection(const FRay3d& Ray, FDynamicM } // If relevant, get all the other colinear edges - if (bSelectDownRay && AddedTopologyEdges.Num() > 0) + if (Settings.bSelectDownRay && AddedTopologyEdges.Num() > 0) { for (int i = 1; i < DownRayElements.Num(); ++i) // skip 0 because it is closest and done { @@ -300,9 +286,9 @@ bool FGroupTopologySelector::DoCornerBasedSelection(const FRay3d& Ray, FDynamicM }//end selecting projected edges // If getting projected edges didn't work out, go ahead and add the corners. - if (bEnableCornerHits) + if (Settings.bEnableCornerHits) { - if (bSelectDownRay) + if (Settings.bSelectDownRay) { for (int32 Id : DownRayElements) { @@ -319,7 +305,8 @@ bool FGroupTopologySelector::DoCornerBasedSelection(const FRay3d& Ray, FDynamicM return false; } -bool FGroupTopologySelector::DoEdgeBasedSelection(const FRay3d& Ray, FDynamicMeshAABBTree3* Spatial, const FGeometrySet3& TopoSpatial, +bool FGroupTopologySelector::DoEdgeBasedSelection(const FSelectionSettings& Settings, const FRay3d& Ray, + FDynamicMeshAABBTree3* Spatial, const FGeometrySet3& TopoSpatial, FGroupTopologySelection& ResultOut, FVector3d& SelectedPositionOut, int32* EdgeSegmentIdOut) const { // These will store our results, depending on whether we select all along the ray or not. @@ -331,7 +318,7 @@ bool FGroupTopologySelector::DoEdgeBasedSelection(const FRay3d& Ray, FDynamicMes // Start by getting the closest element const FGeometrySet3::FNearest* ClosestElement = nullptr; - if (!bSelectDownRay) + if (!Settings.bSelectDownRay) { if (TopoSpatial.FindNearestCurveToRay(Ray, SingleElement, PointsWithinToleranceTest)) { @@ -362,7 +349,7 @@ bool FGroupTopologySelector::DoEdgeBasedSelection(const FRay3d& Ray, FDynamicMes } // Also bail if the closest element is not visible. - if (!bIgnoreOcclusion && IsOccluded(*ClosestElement, Ray.Origin, Spatial)) + if (!Settings.bIgnoreOcclusion && IsOccluded(*ClosestElement, Ray.Origin, Spatial)) { return false; } @@ -373,7 +360,7 @@ bool FGroupTopologySelector::DoEdgeBasedSelection(const FRay3d& Ray, FDynamicMes // If we have other edges, we need to filter them to only those that project onto the closest element. This would be done // differently for perspective cameras vs orthographic projection, but since the behavior is only useful in ortho mode, // we do it that way. - if (bSelectDownRay) + if (Settings.bSelectDownRay) { // Closest element is a given DownRayElements.Add(FIndex2i(ClosestElement->ID, ClosestElement->PolySegmentIdx)); @@ -404,7 +391,7 @@ bool FGroupTopologySelector::DoEdgeBasedSelection(const FRay3d& Ray, FDynamicMes } // Try to select faces that project to the closest edge - if (bPreferAlignedElement && bEnableFaceHits) + if (Settings.bPreferProjectedElement && Settings.bEnableFaceHits) { TSet AddedGroups; @@ -450,7 +437,7 @@ bool FGroupTopologySelector::DoEdgeBasedSelection(const FRay3d& Ray, FDynamicMes } // If relevant, get all the other coplanar faces - if (bSelectDownRay && AddedGroups.Num() > 0) + if (Settings.bSelectDownRay && AddedGroups.Num() > 0) { for (int i = 1; i < DownRayElements.Num(); ++i) // skip 0 because it is closest and done { @@ -490,9 +477,9 @@ bool FGroupTopologySelector::DoEdgeBasedSelection(const FRay3d& Ray, FDynamicMes } // If we didn't end up selecting projected faces, and we have edges to select, select them - if (bEnableEdgeHits) + if (Settings.bEnableEdgeHits) { - if (bSelectDownRay) + if (Settings.bSelectDownRay) { for (const FIndex2i ElementTuple : DownRayElements) { @@ -527,6 +514,114 @@ bool IsOccluded(const FGeometrySet3::FNearest& ClosestElement, const FVector3d& } +bool FGroupTopologySelector::ExpandSelectionByEdgeLoops(FGroupTopologySelection& Selection) +{ + TSet EdgeSet(Selection.SelectedEdgeIDs); + int32 OriginalNumEdges = Selection.SelectedEdgeIDs.Num(); + for (int32 Eid : Selection.SelectedEdgeIDs) + { + const FGroupTopology::FGroupEdge& Edge = Topology->Edges[Eid]; + if (Edge.EndpointCorners[0] == IndexConstants::InvalidID) + { + continue; // This FGroupEdge is a loop unto itself (and already in our selection, since we're looking at it). + } + + // Go forward and backward adding edges + AddNewEdgeLoopEdgesFromCorner(*Topology, Eid, Edge.EndpointCorners[0], EdgeSet); + AddNewEdgeLoopEdgesFromCorner(*Topology, Eid, Edge.EndpointCorners[1], EdgeSet); + } + + if (EdgeSet.Num() > OriginalNumEdges) + { + for (int32 EdgeID : EdgeSet) + { + Selection.SelectedEdgeIDs.AddUnique(EdgeID); + } + return true; + } + else + { + return false; + } +} + +void AddNewEdgeLoopEdgesFromCorner(const FGroupTopology& Topology, int32 EdgeID, int32 CornerID, TSet& EdgeSet) +{ + const FGroupTopology::FCorner& CurrentCorner = Topology.Corners[CornerID]; + + int32 LastCornerID = CornerID; + int32 LastEdgeID = EdgeID; + while (true) + { + int32 NextEid; + if (!GetNextEdgeLoopEdge(Topology, LastEdgeID, LastCornerID, NextEid)) + { + break; // Probably not a valence 4 corner + } + if (EdgeSet.Contains(NextEid)) + { + break; // Either we finished the loop, or we'll continue it from another selection + } + + EdgeSet.Add(NextEid); + + LastEdgeID = NextEid; + const FGroupTopology::FGroupEdge& LastEdge = Topology.Edges[LastEdgeID]; + LastCornerID = LastEdge.EndpointCorners[0] == LastCornerID ? LastEdge.EndpointCorners[1] : LastEdge.EndpointCorners[0]; + + check(LastCornerID != IndexConstants::InvalidID); + } +} + +bool GetNextEdgeLoopEdge(const FGroupTopology& Topology, int32 IncomingEdgeID, int32 CornerID, int32& NextEdgeIDOut) +{ + // It's worth noting that the approach here breaks down in pathological cases where the same group is present + // multiple times around a corner (i.e. the group is not contiguous, and separate islands share a corner). + // It's not practical to worry about those cases. + + NextEdgeIDOut = IndexConstants::InvalidID; + const FGroupTopology::FCorner& CurrentCorner = Topology.Corners[CornerID]; + + if (CurrentCorner.NeighbourGroupIDs.Num() != 4) + { + return false; // Not a valence 4 corner + } + + const FGroupTopology::FGroupEdge& IncomingEdge = Topology.Edges[IncomingEdgeID]; + + // We want to find the edge that shares this corner but does not border either of the neighboring groups of + // the incoming edge. + + for (int32 Gid : CurrentCorner.NeighbourGroupIDs) + { + if (Gid == IncomingEdge.Groups[0] || Gid == IncomingEdge.Groups[1]) + { + continue; // This is one of the neighboring groups of the incoming edge + } + + // Iterate through all edges of group + const FGroupTopology::FGroup* Group = Topology.FindGroupByID(Gid); + for (const FGroupTopology::FGroupBoundary& Boundary : Group->Boundaries) + { + for (int32 Eid : Boundary.GroupEdges) + { + const FGroupTopology::FGroupEdge& CandidateEdge = Topology.Edges[Eid]; + + // Edge must share corner but not neighboring groups + if ((CandidateEdge.EndpointCorners[0] == CornerID || CandidateEdge.EndpointCorners[1] == CornerID) + && CandidateEdge.Groups[0] != IncomingEdge.Groups[0] && CandidateEdge.Groups[0] != IncomingEdge.Groups[1] + && CandidateEdge.Groups[1] != IncomingEdge.Groups[0] && CandidateEdge.Groups[1] != IncomingEdge.Groups[1]) + { + NextEdgeIDOut = Eid; + return true; + } + } + } + } + return false; +} + + void FGroupTopologySelector::DrawSelection(const FGroupTopologySelection& Selection, FToolDataVisualizer* Renderer, const FViewCameraState* CameraState) { FLinearColor UseColor = Renderer->LineColor; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Selection/PolygonSelectionMechanic.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Selection/PolygonSelectionMechanic.cpp index 011ef1981018..587f2bdf5f97 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Selection/PolygonSelectionMechanic.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Selection/PolygonSelectionMechanic.cpp @@ -183,23 +183,22 @@ void UPolygonSelectionMechanic::NotifyMeshChanged(bool bTopologyModified) } -bool UPolygonSelectionMechanic::TopologyHitTest(const FRay& WorldRay, FHitResult& OutHit) +bool UPolygonSelectionMechanic::TopologyHitTest(const FRay& WorldRay, FHitResult& OutHit, bool bUseOrthoSettings) { FGroupTopologySelection Selection; - return TopologyHitTest(WorldRay, OutHit, Selection); + return TopologyHitTest(WorldRay, OutHit, Selection, bUseOrthoSettings); } -bool UPolygonSelectionMechanic::TopologyHitTest(const FRay& WorldRay, FHitResult& OutHit, FGroupTopologySelection& OutSelection) +bool UPolygonSelectionMechanic::TopologyHitTest(const FRay& WorldRay, FHitResult& OutHit, FGroupTopologySelection& OutSelection, bool bUseOrthoSettings) { FRay3d LocalRay(TargetTransform.InverseTransformPosition(WorldRay.Origin), TargetTransform.InverseTransformVector(WorldRay.Direction)); LocalRay.Direction.Normalize(); - UpdateTopoSelector(); - FVector3d LocalPosition, LocalNormal; int32 EdgeSegmentId; // Only used if hit is an edge - if (TopoSelector.FindSelectedElement(LocalRay, OutSelection, LocalPosition, LocalNormal, &EdgeSegmentId) == false) + FGroupTopologySelector::FSelectionSettings TopoSelectorSettings = GetTopoSelectorSettings(bUseOrthoSettings); + if (TopoSelector.FindSelectedElement(TopoSelectorSettings, LocalRay, OutSelection, LocalPosition, LocalNormal, &EdgeSegmentId) == false) { return false; } @@ -244,28 +243,29 @@ bool UPolygonSelectionMechanic::TopologyHitTest(const FRay& WorldRay, FHitResult -void UPolygonSelectionMechanic::UpdateTopoSelector(bool bUseOrthoSettings) +FGroupTopologySelector::FSelectionSettings UPolygonSelectionMechanic::GetTopoSelectorSettings(bool bUseOrthoSettings) { - bool bFaces = Properties->bSelectFaces; - bool bEdges = Properties->bSelectEdges; - bool bVertices = Properties->bSelectVertices; + FGroupTopologySelector::FSelectionSettings Settings; + + Settings.bEnableFaceHits = Properties->bSelectFaces; + Settings.bEnableEdgeHits = Properties->bSelectEdges; + Settings.bEnableCornerHits = Properties->bSelectVertices; if (PersistentSelection.IsEmpty() == false && GetAddToSelectionModifierStateFunc() == true) { - bFaces = bFaces && PersistentSelection.SelectedGroupIDs.Num() > 0; - bEdges = bEdges && PersistentSelection.SelectedEdgeIDs.Num() > 0; - bVertices = bVertices && PersistentSelection.SelectedCornerIDs.Num() > 0; + Settings.bEnableFaceHits = Settings.bEnableFaceHits && PersistentSelection.SelectedGroupIDs.Num() > 0; + Settings.bEnableEdgeHits = Settings.bEnableEdgeHits && PersistentSelection.SelectedEdgeIDs.Num() > 0; + Settings.bEnableCornerHits = Settings.bEnableCornerHits && PersistentSelection.SelectedCornerIDs.Num() > 0; } - TopoSelector.UpdateEnableFlags(bFaces, bEdges, bVertices); if (bUseOrthoSettings) { - TopoSelector.UpdateSelectionModeFlags(Properties->bPreferProjectedElement, Properties->bSelectDownRay, Properties->bIgnoreOcclusion); - } - else - { - TopoSelector.UpdateSelectionModeFlags(false, false, false); + Settings.bPreferProjectedElement = Properties->bPreferProjectedElement; + Settings.bSelectDownRay = Properties->bSelectDownRay; + Settings.bIgnoreOcclusion = Properties->bIgnoreOcclusion; } + + return Settings; } @@ -280,9 +280,14 @@ bool UPolygonSelectionMechanic::UpdateHighlight(const FRay& WorldRay) LocalRay.Direction.Normalize(); HilightSelection.Clear(); - UpdateTopoSelector(CameraState.bIsOrthographic); FVector3d LocalPosition, LocalNormal; - bool bHit = TopoSelector.FindSelectedElement(LocalRay, HilightSelection, LocalPosition, LocalNormal); + FGroupTopologySelector::FSelectionSettings TopoSelectorSettings = GetTopoSelectorSettings(CameraState.bIsOrthographic); + bool bHit = TopoSelector.FindSelectedElement(TopoSelectorSettings, LocalRay, HilightSelection, LocalPosition, LocalNormal); + + if (HilightSelection.SelectedEdgeIDs.Num() > 0 && ShouldSelectEdgeLoopsFunc()) + { + TopoSelector.ExpandSelectionByEdgeLoops(HilightSelection); + } // Currently we draw highlighted edges/vertices differently from highlighted faces. Edges/vertices // get drawn in the Render() call, so it is sufficient to just update HighlightSelection above. @@ -343,23 +348,55 @@ bool UPolygonSelectionMechanic::UpdateSelection(const FRay& WorldRay, FVector3d& TargetTransform.InverseTransformVector(WorldRay.Direction)); LocalRay.Direction.Normalize(); - UpdateTopoSelector(CameraState.bIsOrthographic); - bool bSelectionModified = false; FVector3d LocalPosition, LocalNormal; FGroupTopologySelection Selection; - if (TopoSelector.FindSelectedElement(LocalRay, Selection, LocalPosition, LocalNormal)) + FGroupTopologySelector::FSelectionSettings TopoSelectorSettings = GetTopoSelectorSettings(CameraState.bIsOrthographic); + if (TopoSelector.FindSelectedElement(TopoSelectorSettings, LocalRay, Selection, LocalPosition, LocalNormal)) { LocalHitPositionOut = LocalPosition; LocalHitNormalOut = LocalNormal; + + bool bSelectedEdgeLoops = false; + if (Selection.SelectedEdgeIDs.Num() > 0 && ShouldSelectEdgeLoopsFunc()) + { + bSelectedEdgeLoops = true; + TopoSelector.ExpandSelectionByEdgeLoops(Selection); + } if (GetAddToSelectionModifierStateFunc()) { - PersistentSelection.Toggle(Selection); + if (bSelectedEdgeLoops) + { + // We don't want to toggle if we're adding loops to the selection + // unless the entire loop was selected to begin with. + bool bLoopsAlreadySelected = true; + for (int32 Eid : Selection.SelectedEdgeIDs) + { + if (!PersistentSelection.SelectedEdgeIDs.Contains(Eid)) + { + bLoopsAlreadySelected = false; + break; + } + } + if (bLoopsAlreadySelected) + { + PersistentSelection.Remove(Selection); + } + else + { + PersistentSelection.Append(Selection); + } + } + else + { + PersistentSelection.Toggle(Selection); + } } else { PersistentSelection = Selection; } + bSelectionModified = true; } else diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Snapping/BasePositionSnapSolver3.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Snapping/BasePositionSnapSolver3.cpp index 35233d4f7bad..652314369971 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Snapping/BasePositionSnapSolver3.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Snapping/BasePositionSnapSolver3.cpp @@ -137,10 +137,10 @@ bool FBasePositionSnapSolver3::IsIgnored(int TargetID) const } -const FBasePositionSnapSolver3::FSnapTargetPoint* FBasePositionSnapSolver3::FindBestSnapInSet(const TArray& TestTargets, double& MinMetric, int& MinPriority, +int32 FBasePositionSnapSolver3::FindIndexOfBestSnapInSet(const TArray& TestTargets, double& MinMetric, int& MinPriority, const TFunction& GetSnapPointFromFunc) { - const FSnapTargetPoint* BestTarget = nullptr; + int32 BestIndex = -1; int NumTargets = TestTargets.Num(); for (int k = 0; k < NumTargets; ++k) { @@ -178,11 +178,23 @@ const FBasePositionSnapSolver3::FSnapTargetPoint* FBasePositionSnapSolver3::Find if (Metric < MinMetric || TestTargets[k].Priority < MinPriority) { MinMetric = Metric; - BestTarget = &TestTargets[k]; + BestIndex = k; MinPriority = TestTargets[k].Priority; } } } + return BestIndex; +} + +const FBasePositionSnapSolver3::FSnapTargetPoint* FBasePositionSnapSolver3::FindBestSnapInSet(const TArray& TestTargets, double& MinMetric, int& MinPriority, + const TFunction& GetSnapPointFromFunc) +{ + const FSnapTargetPoint* BestTarget = nullptr; + int32 BestIndex = FindIndexOfBestSnapInSet(TestTargets, MinMetric, MinPriority, GetSnapPointFromFunc); + if (BestIndex >= 0) + { + BestTarget = &TestTargets[BestIndex]; + } return BestTarget; } diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Snapping/PointPlanarSnapSolver.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Snapping/PointPlanarSnapSolver.cpp index efe939986f65..93b25e876153 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Snapping/PointPlanarSnapSolver.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/Snapping/PointPlanarSnapSolver.cpp @@ -2,10 +2,11 @@ #include "Snapping/PointPlanarSnapSolver.h" + #include "VectorUtil.h" #include "LineTypes.h" #include "Quaternion.h" - +#include "ToolDataVisualizer.h" FPointPlanarSnapSolver::FPointPlanarSnapSolver() { @@ -16,21 +17,22 @@ void FPointPlanarSnapSolver::Reset() { FBasePositionSnapSolver3::Reset(); PointHistory.Reset(); + ResetGenerated(); } -void FPointPlanarSnapSolver::ResetActiveSnap() +/** Clears any existing generated snap targets. */ +void FPointPlanarSnapSolver::ResetGenerated() { - FBasePositionSnapSolver3::ResetActiveSnap(); GeneratedLines.Reset(); + IntersectionPoints.Reset(); + IntersectionSecondLinePointers.Reset(); GeneratedTargets.Reset(); } - void FPointPlanarSnapSolver::UpdatePointHistory(const TArray& Points) { PointHistory = Points; - GeneratedLines.Reset(); - GeneratedTargets.Reset(); + ResetGenerated(); } void FPointPlanarSnapSolver::UpdatePointHistory(const TArray& Points) @@ -41,15 +43,31 @@ void FPointPlanarSnapSolver::UpdatePointHistory(const TArray& Points) { PointHistory.Add(Points[k]); } - GeneratedLines.Reset(); - GeneratedTargets.Reset(); + ResetGenerated(); } +void FPointPlanarSnapSolver::AppendHistoryPoint(const FVector3d& Point) +{ + PointHistory.Add(Point); + ResetGenerated(); +} + +void FPointPlanarSnapSolver::InsertHistoryPoint(const FVector3d& Point, int32 Index) +{ + PointHistory.Insert(Point, Index); + ResetGenerated(); +} + +void FPointPlanarSnapSolver::RemoveHistoryPoint(int32 Index) +{ + PointHistory.RemoveAt(Index); + ResetGenerated(); +} void FPointPlanarSnapSolver::RegenerateTargetLines(bool bCardinalAxes, bool bLastHistorySegment) { - GeneratedLines.Reset(); + ResetGenerated(); int NumHistoryPts = PointHistory.Num(); if (NumHistoryPts == 0) @@ -81,6 +99,118 @@ void FPointPlanarSnapSolver::RegenerateTargetLines(bool bCardinalAxes, bool bLas } } +void FPointPlanarSnapSolver::RegenerateTargetLinesAround(int32 HistoryIndex, bool bWrapAround, bool bGenerateIntersections) +{ + ResetGenerated(); + + // We allow indices one to each side of our history to allow snapping based on endpoints. + if (PointHistory.Num() == 0 || HistoryIndex < -1 || HistoryIndex > PointHistory.Num()) + { + return; + } + + // Templates for the objects that we'll be adding + FSnapTargetLine AxisLine; + AxisLine.TargetID = CardinalAxisTargetID; + AxisLine.Priority = CardinalAxisPriority; + + // Add lines based on previous point + if (HistoryIndex > 0 || (bWrapAround && HistoryIndex == 0)) // Disallowing -1 here + { + int32 PreviousIndex = (HistoryIndex + PointHistory.Num() - 1) % PointHistory.Num(); + + AxisLine.Line = FLine3d(PointHistory[PreviousIndex], Plane.X()); + GeneratedLines.Add(AxisLine); + AxisLine.Line = FLine3d(PointHistory[PreviousIndex], Plane.Y()); + GeneratedLines.Add(AxisLine); + } + + + if (HistoryIndex < PointHistory.Num() - 1 || (bWrapAround && HistoryIndex == PointHistory.Num() - 1)) // Disallowing PointHistory.Num()) + { + int32 NextIndex = (HistoryIndex + 1) % PointHistory.Num(); + + AxisLine.Line = FLine3d(PointHistory[NextIndex], Plane.X()); + GeneratedLines.Add(AxisLine); + AxisLine.Line = FLine3d(PointHistory[NextIndex], Plane.Y()); + GeneratedLines.Add(AxisLine); + + } + + if (bGenerateIntersections) + { + GenerateLineIntersectionTargets(); + } +} + +void FPointPlanarSnapSolver::GenerateLineIntersectionTargets() +{ + IntersectionPoints.Reset(); + IntersectionSecondLinePointers.Reset(); + + const double IN_PLANE_THRESHOLD = KINDA_SMALL_NUMBER * 10; + + FSnapTargetPoint Intersection; + Intersection.TargetID = IntersectionTargetID; + + // Accumulate all lines in the plane so we can intersect them with each other. Also keep a 1:1 + // array with pointers to the original 3-d lines so we can link the intersection points to them. + TArray OriginalLinePointers; + TArray LinesInPlane; + for (const FSnapTargetLine& Line : GeneratedLines) + { + FVector3d OriginInPlane = Plane.ToFramePoint(Line.Line.Origin); + if (OriginInPlane.Z < IN_PLANE_THRESHOLD) + { + FVector3d DirectionInPlane = Plane.ToFrameVector(Line.Line.Direction); + if (DirectionInPlane.Z < IN_PLANE_THRESHOLD) + { + LinesInPlane.Add(FLine2d(OriginInPlane.XY(), DirectionInPlane.XY())); + OriginalLinePointers.Add(&Line); + } + } + } + for (const FSnapTargetLine& Line : TargetLines) + { + FVector3d OriginInPlane = Plane.ToFramePoint(Line.Line.Origin); + if (OriginInPlane.Z < IN_PLANE_THRESHOLD) + { + FVector3d DirectionInPlane = Plane.ToFrameVector(Line.Line.Direction); + if (DirectionInPlane.Z < IN_PLANE_THRESHOLD) + { + LinesInPlane.Add(FLine2d(OriginInPlane.XY(), DirectionInPlane.XY())); + OriginalLinePointers.Add(&Line); + } + } + } + + // Intersect all lines with each other + for (int i = 0; i < LinesInPlane.Num(); ++i) + { + for (int j = i; j < LinesInPlane.Num(); ++j) + { + FVector2d IntersectionPoint; + if (LinesInPlane[i].IntersectionPoint(LinesInPlane[j], IntersectionPoint)) + { + Intersection.Position = Plane.FromFramePoint(FVector3d(IntersectionPoint)); + + // Lines in plane are 1:1 with our OriginalLinePointers array, which has more info per line. + // Store the more important priority line in the snap target, and the second line in the + // IntersectionSecondLinePointers array. + + bool bImportanceIsSwapped = OriginalLinePointers[i]->Priority > OriginalLinePointers[j]->Priority; + const FSnapTargetLine* MoreImportantLine = bImportanceIsSwapped ? OriginalLinePointers[j] : OriginalLinePointers[i]; + const FSnapTargetLine* SecondLine = bImportanceIsSwapped ? OriginalLinePointers[i] : OriginalLinePointers[j]; + + Intersection.Priority = MoreImportantLine->Priority - IntersectionPriorityDelta; + Intersection.SnapLine = MoreImportantLine->Line; + IntersectionSecondLinePointers.Add(SecondLine); + + IntersectionPoints.Add(Intersection); + } + } + } +} void FPointPlanarSnapSolver::GenerateTargets(const FVector3d& PointIn) { @@ -100,6 +230,17 @@ void FPointPlanarSnapSolver::GenerateTargets(const FVector3d& PointIn) GeneratedTargets.Add(Target); } + for (int j = 0; j < TargetLines.Num(); ++j) + { + FSnapTargetPoint Target; + Target.Position = TargetLines[j].Line.NearestPoint(PointIn); + Target.TargetID = TargetLines[j].TargetID; + Target.Priority = TargetLines[j].Priority; + Target.bIsSnapLine = true; + Target.SnapLine = TargetLines[j].Line; + GeneratedTargets.Add(Target); + } + // length-along-line snaps if (bEnableSnapToKnownLengths) { @@ -134,6 +275,7 @@ void FPointPlanarSnapSolver::UpdateSnappedPoint(const FVector3d& PointIn) static constexpr int MIN_PRIORITY = TNumericLimits::Max(); int BestPriority = MIN_PRIORITY; const FSnapTargetPoint* BestSnapTarget = nullptr; + bActiveSnapIsIntersection = false; GenerateTargets(PointIn); @@ -142,7 +284,6 @@ void FPointPlanarSnapSolver::UpdateSnappedPoint(const FVector3d& PointIn) return PointIn; }; - const FSnapTargetPoint* BestPointTarget = FindBestSnapInSet(TargetPoints, MinMetric, BestPriority, GetSnapFromPointFunc); if (BestPointTarget != nullptr) { @@ -155,6 +296,14 @@ void FPointPlanarSnapSolver::UpdateSnappedPoint(const FVector3d& PointIn) BestSnapTarget = BestLineTarget; } + int32 BestIntersectionIndex = FindIndexOfBestSnapInSet(IntersectionPoints, MinMetric, BestPriority, GetSnapFromPointFunc); + if (BestIntersectionIndex >= 0) + { + BestSnapTarget = &IntersectionPoints[BestIntersectionIndex]; + bActiveSnapIsIntersection = true; + IntersectionSecondLine = IntersectionSecondLinePointers[BestIntersectionIndex]->Line; + } + if (bHaveActiveSnap && bEnableStableSnap && ActiveSnapTarget.bIsSnapLine == false) { if (TestSnapTarget(ActiveSnapTarget, MinMetric*StableSnapImproveThresh, BestPriority, GetSnapFromPointFunc)) @@ -163,7 +312,6 @@ void FPointPlanarSnapSolver::UpdateSnappedPoint(const FVector3d& PointIn) } } - if (BestSnapTarget != nullptr) { SetActiveSnapData(*BestSnapTarget, BestSnapTarget->Position, Plane.ToPlane(BestSnapTarget->Position, 2), MinMetric); @@ -174,3 +322,66 @@ void FPointPlanarSnapSolver::UpdateSnappedPoint(const FVector3d& PointIn) } } + +void FPointPlanarSnapSolver::DebugRender(IToolsContextRenderAPI* RenderAPI) +{ + FToolDataVisualizer Renderer; + Renderer.BeginFrame(RenderAPI); + double LineLength = 10000; + + int ActiveSnapID = (bHaveActiveSnap) ? GetActiveSnapTargetID() : -9999; + + for (const FSnapTargetLine& LineTarget : TargetLines) + { + if (IsIgnored(LineTarget.TargetID)) + { + continue; + } + + const FLine3d& Line = LineTarget.Line; + + FLinearColor UseColor = FColor::Cyan; + float LineWidth = (LineTarget.TargetID == ActiveSnapID) ? Renderer.LineThickness : Renderer.LineThickness * 0.5; + Renderer.DrawLine(Line.PointAt(-LineLength), Line.PointAt(LineLength), UseColor, LineWidth); + } + + for (const FSnapTargetLine& LineTarget : GeneratedLines) + { + if (IsIgnored(LineTarget.TargetID)) + { + continue; + } + + const FLine3d& Line = LineTarget.Line; + + FLinearColor UseColor = FColor::Cyan; + float LineWidth = (LineTarget.TargetID == ActiveSnapID) ? Renderer.LineThickness : Renderer.LineThickness * 0.5; + Renderer.DrawLine(Line.PointAt(-LineLength), Line.PointAt(LineLength), UseColor, LineWidth); + } + + for (const FSnapTargetPoint& Point : IntersectionPoints) + { + if (IsIgnored(Point.TargetID)) + { + continue; + } + + FLinearColor UseColor = FColor::Cyan; + float LineWidth = (Point.TargetID == ActiveSnapID) ? Renderer.LineThickness : Renderer.LineThickness * 0.5; + Renderer.DrawCircle(Point.Position, FVector3d(1, 0, 0), 3, 64, + UseColor, LineWidth, Renderer.bDepthTested); + } + + for (const FSnapTargetPoint& Point : TargetPoints) + { + if (IsIgnored(Point.TargetID)) + { + continue; + } + + FLinearColor UseColor = FColor::Cyan; + float LineWidth = (Point.TargetID == ActiveSnapID) ? Renderer.LineThickness : Renderer.LineThickness * 0.5; + Renderer.DrawCircle(Point.Position, FVector3d(1, 0, 0), 3, 64, + UseColor, LineWidth, Renderer.bDepthTested); + } +} diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/ToolSceneQueriesUtil.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/ToolSceneQueriesUtil.cpp index 7d3022809e37..3f3028f22e86 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/ToolSceneQueriesUtil.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/ToolSceneQueriesUtil.cpp @@ -47,6 +47,28 @@ bool ToolSceneQueriesUtil::PointSnapQuery(const FViewCameraState& CameraState, c } } +double ToolSceneQueriesUtil::PointSnapMetric(const FViewCameraState& CameraState, const FVector3d& Point1, const FVector3d& Point2) +{ + if (!CameraState.bIsOrthographic) + { + double VisualAngle = VectorUtil::OpeningAngleD(Point1, Point2, (FVector3d)CameraState.Position); + + // To go from a world space angle to a 90 degree division of the view, we divide by TrueFOVDegrees/90 (our normalization factor) + VisualAngle /= CameraState.GetFOVAngleNormalizationFactor(); + return FMathd::Abs(VisualAngle); + } + else + { + FVector3d ViewPlaneNormal = CameraState.Orientation.GetForwardVector(); + + // Get projected distance in the plane + FVector3d DistanceVector = Point1 - Point2; + DistanceVector = DistanceVector - (DistanceVector).Dot(ViewPlaneNormal) * ViewPlaneNormal; + + // We have one visual angle degree correspond to the width of the viewport divided by 90, so we divide by width/90. + return DistanceVector.Length() * 90.0 / CameraState.OrthoWorldCoordinateWidth; + } +} double ToolSceneQueriesUtil::CalculateViewVisualAngleD(const UInteractiveTool* Tool, const FVector3d& Point1, const FVector3d& Point2) diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/ToolSetupUtil.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/ToolSetupUtil.cpp index b14e273bdfbd..212510e3699c 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/ToolSetupUtil.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/ToolSetupUtil.cpp @@ -146,7 +146,6 @@ UMaterialInterface* ToolSetupUtil::GetSelectionMaterial(UInteractiveToolManager* UMaterialInterface* ToolSetupUtil::GetSelectionMaterial(const FLinearColor& UseColor, UInteractiveToolManager* ToolManager, float PercentDepthOffset) { - check(ToolManager != nullptr); // required for outer UMaterialInterface* Material = LoadObject(nullptr, TEXT("/MeshModelingToolset/Materials/SelectionMaterial")); if (Material == nullptr && ToolManager != nullptr) { diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/AssetUtils/Texture2DBuilder.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/AssetUtils/Texture2DBuilder.h index bcdfa222a9ef..3d98e687ade4 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/AssetUtils/Texture2DBuilder.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/AssetUtils/Texture2DBuilder.h @@ -5,6 +5,7 @@ #include "MathUtil.h" #include "VectorTypes.h" #include "Image/ImageDimensions.h" +#include "Image/ImageBuilder.h" #include "Engine/Classes/Engine/Texture2D.h" @@ -146,7 +147,6 @@ public: return IsEditable(); } - /** @return true if the texture data is currently locked and editable */ bool IsEditable() const { @@ -213,6 +213,17 @@ public: } + void Cancel() + { + bool bIsEditable = IsEditable(); + if (bIsEditable) + { + RawTexture2D->PlatformData->Mips[0].BulkData.Unlock(); + CurrentMipData = nullptr; + } + } + + /** * Clear all texels in the current Mip to the clear/default color for the texture build type */ @@ -230,6 +241,23 @@ public: } + /** + * Clear all texels in the current Mip to the given ClearColor + */ + void Clear(const FColor& ClearColor) + { + check(IsEditable()); + if (IsEditable()) + { + for (int64 k = 0; k < Dimensions.Num(); ++k) + { + CurrentMipData[k] = ClearColor; + } + } + } + + + /** * Get the texel at the given X/Y coordinates */ @@ -293,6 +321,73 @@ public: } + /** + * populate texel values from floating-point SourceImage + */ + bool Copy(const TImageBuilder& SourceImage, const bool bSRGB = false) + { + if (ensure(SourceImage.GetDimensions() == Dimensions) == false) + { + return false; + } + int64 Num = Dimensions.Num(); + for (int32 i = 0; i < Num; ++i) + { + FVector3f Pixel = SourceImage.GetPixel(i); + Pixel.X = FMathf::Clamp(Pixel.X, 0.0, 1.0); + Pixel.Y = FMathf::Clamp(Pixel.Y, 0.0, 1.0); + Pixel.Z = FMathf::Clamp(Pixel.Z, 0.0, 1.0); + FColor Texel = ((FLinearColor)Pixel).ToFColor(bSRGB); + SetTexel(i, Texel); + } + return true; + } + + /** + * populate texel values from floating-point SourceImage + */ + bool Copy(const TImageBuilder& SourceImage, const bool bSRGB = false) + { + if (ensure(SourceImage.GetDimensions() == Dimensions) == false) + { + return false; + } + int64 Num = Dimensions.Num(); + for (int32 i = 0; i < Num; ++i) + { + FVector4f Pixel = SourceImage.GetPixel(i); + Pixel.X = FMathf::Clamp(Pixel.X, 0.0, 1.0); + Pixel.Y = FMathf::Clamp(Pixel.Y, 0.0, 1.0); + Pixel.Z = FMathf::Clamp(Pixel.Z, 0.0, 1.0); + Pixel.W = FMathf::Clamp(Pixel.W, 0.0, 1.0); + FColor Texel = ((FLinearColor)Pixel).ToFColor(bSRGB); + SetTexel(i, Texel); + } + return true; + } + + + /** + * copy existing texel values to floating-point DestImage + */ + bool CopyTo(TImageBuilder& DestImage) const + { + if (ensure(DestImage.GetDimensions() == Dimensions) == false) + { + return false; + } + int64 Num = Dimensions.Num(); + for (int32 i = 0; i < Num; ++i) + { + FColor ByteColor = GetTexel(i); + FLinearColor FloatColor(ByteColor); + DestImage.SetPixel(i, FVector4f(FloatColor)); + } + return true; + } + + + /** * @return current locked Mip data. Nullptr if IsEditable() == false. * @warning this point is invalid after the texture is Committed! diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/LineSetComponent.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/LineSetComponent.h index 2d68d52f6624..02ea2967e22d 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/LineSetComponent.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/LineSetComponent.h @@ -69,6 +69,12 @@ public: /** Insert a line with the given ID to the overlay */ void InsertLine(const int32 ID, const FRenderableLine& OverlayLine); + /** Changes the start coordinates of a line */ + void SetLineStart(const int32 ID, const FVector& NewPostion); + + /** Changes the end coordinates of a line */ + void SetLineEnd(const int32 ID, const FVector& NewPostion); + /** Sets the color of a line */ void SetLineColor(const int32 ID, const FColor& NewColor); diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/PointSetComponent.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/PointSetComponent.h index 5792cbccd465..22c0e034d862 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/PointSetComponent.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/PointSetComponent.h @@ -61,19 +61,28 @@ public: /** Reserve enough memory for up to the given ID */ void ReservePoints(const int32 MaxID); - /** Add a point to the overlay */ + /** Add a point to the set */ int32 AddPoint(const FRenderablePoint& OverlayPoint); - /** Insert a point with the given ID to the overlay */ + /** Insert a point with the given ID into the set. */ void InsertPoint(const int32 ID, const FRenderablePoint& OverlayPoint); + /** Retrieve a point with the given id. */ + const FRenderablePoint& GetPoint(const int32 ID); + + /** Sets the position of a point (assumes its existence). */ + void SetPointPosition(const int32 ID, const FVector& NewPosition); + /** Sets the color of a point */ void SetPointColor(const int32 ID, const FColor& NewColor); /** Sets the size of a point */ void SetPointSize(const int32 ID, const float NewSize); - /** Remove a point from the overlay */ + /** Sets the color of all points currently in the set. */ + void SetAllPointsColor(const FColor& NewColor); + + /** Remove a point from the set. */ void RemovePoint(const int32 ID); /** Queries whether a point with the given ID exists */ diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/PolyEditPreviewMesh.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/PolyEditPreviewMesh.h index bf91478115f9..a5e8095c28e9 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/PolyEditPreviewMesh.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/PolyEditPreviewMesh.h @@ -55,7 +55,7 @@ public: void InitializeInsetType(const FDynamicMesh3* SourceMesh, const TArray& Triangles, const FTransform3d* MeshTransform = nullptr); - void UpdateInsetType(double NewOffset); + void UpdateInsetType(double NewOffset, bool bReproject = false, double Softness = 0.0, double AreaScaleT = 1.0, bool bBoundaryOnly = false); void MakeInsetTypeTargetMesh(FDynamicMesh3& TargetMesh); const FDynamicMesh3& GetInitialPatchMesh() const { return InitialEditPatch; } diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/TriangleSetComponent.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/TriangleSetComponent.h index deb7dc57851a..8de949bd0ca5 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/TriangleSetComponent.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/TriangleSetComponent.h @@ -6,6 +6,7 @@ #include "Components/MeshComponent.h" #include "Materials/MaterialInterface.h" +#include "IndexTypes.h" #include "TriangleSetComponent.generated.h" @@ -107,6 +108,19 @@ public: /** Queries whether a triangle with the given ID exists in the component. */ bool IsTriangleValid(const int32 ID) const; + + /** + * Add a triangle with the given vertices, normal, Color, and Material + * @return ID of the triangle created + */ + int32 AddTriangle(const FVector& A, const FVector& B, const FVector& C, const FVector& Normal, const FColor& Color, UMaterialInterface* Material); + + /** + * Add a Quad (two triangles) with the given vertices, normal, Color, and Material + * @return ID of the two triangles created + */ + FIndex2i AddQuad(const FVector& A, const FVector& B, const FVector& C, const FVector& D, const FVector& Normal, const FColor& Color, UMaterialInterface* Material); + private: //~ Begin UPrimitiveComponent Interface. diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/UVLayoutPreview.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/UVLayoutPreview.h new file mode 100644 index 000000000000..03578c1f7d1c --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Drawing/UVLayoutPreview.h @@ -0,0 +1,162 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "PreviewMesh.h" +#include "TriangleSetComponent.h" +#include "InteractiveTool.h" +#include "UVLayoutPreview.generated.h" + +class FDynamicMesh3; + + + +/** + * Where should in-viewport UVLayoutPreview be shown, relative to target object + */ +UENUM() +enum class EUVLayoutPreviewSide +{ + Left = 0, + Right = 1 +}; + + +/** + * Visualization settings for UV layout preview + */ +UCLASS() +class MODELINGCOMPONENTS_API UUVLayoutPreviewProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() +public: + /** Should be UV Layout be shown */ + UPROPERTY(EditAnywhere, Category = UVLayoutPreview) + bool bVisible = true; + + /** World-space scaling factor on the UV Layout */ + UPROPERTY(EditAnywhere, Category = UVLayoutPreview) + float ScaleFactor = 1.0; + + /** Where should the UV layout be positioned relative to the target object, relative to camera */ + UPROPERTY(EditAnywhere, Category = UVLayoutPreview) + EUVLayoutPreviewSide WhichSide = EUVLayoutPreviewSide::Right; + + /** If true, wireframe is shown for the UV layout */ + UPROPERTY(EditAnywhere, Category = UVLayoutPreview) + bool bShowWireframe = true; + + UPROPERTY(EditAnywhere, Category = UVLayoutPreview) + FVector2D Shift = FVector2D(1.0, 0.5); +}; + + + + +/** + * UUVLayoutPreview is a utility object that creates and manages a 3D plane on which a UV layout + * for a 3D mesh is rendered. The UV layout + */ +UCLASS(Transient) +class MODELINGCOMPONENTS_API UUVLayoutPreview : public UObject +{ + GENERATED_BODY() + +public: + virtual ~UUVLayoutPreview(); + + + /** + * Create preview mesh in the World with the given transform + */ + void CreateInWorld(UWorld* World); + + /** + * Remove and destroy preview mesh + */ + void Disconnect(); + + + /** + * Configure material set for UV-space preview mesh + */ + void SetSourceMaterials(const FComponentMaterialSet& MaterialSet); + + /** + * Specify the current world transform/bounds for the target object. + * UV layout preview is positioned relative to this box + */ + void SetSourceWorldPosition(FTransform WorldTransform, FBox WorldBounds); + + /** + * Update the current camera state, used to auto-position the UV layout preview + */ + void SetCurrentCameraState(const FViewCameraState& CameraState); + + /** + * Notify the UV Layout Preview that the source UVs have been modified + */ + void UpdateUVMesh(const FDynamicMesh3* SourceMesh, int32 SourceUVLayer = 0); + + /** + * Tick the UV Layout Preview, allowing it to upodate various settings + */ + void OnTick(float DeltaTime); + + /** + * Render the UV Layout Preview, allowing it to upodate various settings + */ + void Render(IToolsContextRenderAPI* RenderAPI); + + + /** + * Set the transform on the UV Layout preview mesh + */ + void SetTransform(const FTransform& UseTransform); + + /** + * Set the visibility of the UV Layout preview mesh + */ + void SetVisible(bool bVisible); + + + +public: + /** Visualization settings */ + UPROPERTY() + UUVLayoutPreviewProperties* Settings; + + /** PreviewMesh is initialized with a copy of an input mesh with UVs mapped to position, ie such that (X,Y,Z) = (U,V,0) */ + UPROPERTY() + UPreviewMesh* PreviewMesh; + + /** Set of additional triangles to draw, eg for backing rectangle, etc */ + UPROPERTY() + UTriangleSetComponent* TriangleComponent; + + /** Configure whether the backing rectangle should be shown */ + UPROPERTY() + bool bShowBackingRectangle = true; + + /** Configure the backing rectangle material */ + UPROPERTY() + UMaterialInterface* BackingRectangleMaterial = nullptr; + + +protected: + void RecalculatePosition(); + + FComponentMaterialSet SourceMaterials; + + FFrame3d SourceObjectFrame; + FAxisAlignedBox3d SourceObjectWorldBounds; + + FFrame3d CurrentWorldFrame; + + FViewCameraState CameraState; + + bool bSettingsModified = false; + + float GetCurrentScale(); +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Mechanics/CurveControlPointsMechanic.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Mechanics/CurveControlPointsMechanic.h new file mode 100644 index 000000000000..1027701801af --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Mechanics/CurveControlPointsMechanic.h @@ -0,0 +1,492 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "BaseBehaviors/BehaviorTargetInterfaces.h" +#include "FrameTypes.h" +#include "InteractionMechanic.h" +#include "InteractiveToolChange.h" +#include "Snapping/PointPlanarSnapSolver.h" +#include "Spatial/GeometrySet3.h" +#include "ToolContextInterfaces.h" //FViewCameraState +#include "VectorTypes.h" +#include "VectorUtil.h" + +#include "CurveControlPointsMechanic.generated.h" + +class APreviewGeometryActor; +class ULineSetComponent; +class UMouseHoverBehavior; +class UPointSetComponent; +class USingleClickInputBehavior; +class UTransformGizmo; +class UTransformProxy; + +/** + * A mechanic for displaying a sequence of control points and moving them about. Has an interactive initialization mode for + * first setting the points. + * + * When editing, hold shift to select multiple points. Hold Ctrl to add an extra point along an edge. To add points to either end of + * the sequence, first select either the first or last point and then hold Ctrl. + * Backspace deletes currently selected points. In edit mode, holding Shift generally toggles the snapping behavior (makes it opposite + * of the current SnappingEnabled setting), though this is not yet implemented while the gizmo is being dragged. + * + * TODO: + * - Make it possible to open/close loop in edit mode + * - Improve display of occluded control points (checkerboard the material) + * - Allow deselection of vertices by clicking away? + * - Lump the point/line set components into PreviewGeometryActor. + */ +UCLASS() +class MODELINGCOMPONENTS_API UCurveControlPointsMechanic : public UInteractionMechanic, public IClickBehaviorTarget, public IHoverBehaviorTarget +{ + GENERATED_BODY() + +protected: + + // We want some way to store the control point sequence that lets us easily associate points with their renderable and hit-testable + // representations, since we need to alter all of these together as points get moved or added. We use FOrderedPoints for this, until + // we decide on how we want to store sequences of points in general. + // FOrderedPoints maintains a sequence of point ID's, the positions associated with each ID, and a mapping back from ID to position. + // The ID's can then be used to match to renderable points, hit-testable points, and segments going to the next point. + + /** + * A sequence of 3-component vectors that can be used to represent a polyline in 3d space, or + * some other sequence of control points in 3d space. + * + * The sequence is determined by a sequence of point IDs which can be used to look up the point + * coordinates. + */ + class FOrderedPoints + { + public: + + FOrderedPoints() {}; + FOrderedPoints(const FOrderedPoints& ToCopy); + FOrderedPoints(const TArray& PointSequence); + + /** @return number of points in the sequence. */ + inline int32 Num() + { + return Sequence.Num(); + } + + /** @return last point ID in the sequence. */ + inline int32 Last() + { + return Sequence.Last(); + } + + /** @return first point ID in the sequence. */ + inline int32 First() + { + return Sequence[0]; + } + + /** + * Appends a point with the given coordinates to the end of the sequence. + * + * @return the new point's ID. + */ + inline int32 AppendPoint(const FVector3d& PointCoordinates); + + /** + * Inserts a point with the given coordinates at the given position in the sequence. + * + * @param KnownPointID If not null, this parameter stores the PointID we want the new point + * to have. This is useful for undo/redo operations, where we want to make sure that the + * we don't end up giving a point a different ID than we did last time. If null, the + * class generates an ID. + * @return the new point's ID. + */ + int32 InsertPointAt(int32 SequencePosition, const FVector3d& PointCoordinates, const int32* KnownPointID = nullptr); + + /** + * Removes the point at a particular position in the sequence. + * + * @return point ID of the removed point. + */ + int32 RemovePointAt(int32 SequencePosition); + + /** + * @return index in the sequence of a given Point ID. + */ + int32 GetSequencePosition(int32 PointID) + { + return PointIDToSequencePosition[PointID]; + } + + /** + * @return point ID at the given position in the sequence. + */ + inline int32 GetPointIDAt(int32 SequencePosition) + { + return Sequence[SequencePosition]; + } + + /** + * @return coordinates of the point with the given point ID. + */ + inline FVector3d GetPointCoordinates(int32 PointID) const + { + check(IsValidPoint(PointID)); + return Vertices[PointID]; + } + + /** + * @return coordinates of the point at the given position in the sequence. + */ + inline FVector3d GetPointCoordinatesAt(int32 SequencePosition) const + { + return Vertices[Sequence[SequencePosition]]; + } + + /** + * Checks whether given point ID exists in the sequence. + */ + inline bool IsValidPoint(int32 PointID) const + { + return Vertices.IsValidIndex(PointID); + } + + /** + * Change the coordinates associated with a given point ID. + */ + inline void SetPointCoordinates(int32 PointID, const FVector3d& NewCoordinates) + { + checkSlow(VectorUtil::IsFinite(NewCoordinates)); + check(IsValidPoint(PointID)); + + Vertices[PointID] = NewCoordinates; + } + + /** + * Delete all points in the sequence. + */ + void Empty(); + + // TODO: We should have a proper iterable to iterate over point ID's (and maybe one to iterate over point coordinates + // too. We're temporarily taking a shortcut by using the actual sequence array as our iterable, but only until + // we've decided on the specifics of the class. + typedef const TArray& PointIDEnumerable; + + /** + * This function should only be used to iterate across the point id's in sequence in a range-based for-loop + * as in "for (int32 PointID : PointSequence->PointIDItr()) { ... }" + * The return type of this function is likely to change, but it will continue to work in range-based for-loops. + */ + inline PointIDEnumerable PointIDItr() + { + return Sequence; + } + + protected: + TSparseArray Vertices; + TArray Sequence; + TMap PointIDToSequencePosition; + + void ReInitialize(const TArray& PointSequence); + }; + //end FOrderedPoints + +public: + + // Behaviors used for moving points around and hovering them + UPROPERTY() + USingleClickInputBehavior* ClickBehavior = nullptr; + UPROPERTY() + UMouseHoverBehavior* HoverBehavior = nullptr; + + // This delegate is called every time the control point sequence is altered. + DECLARE_MULTICAST_DELEGATE(OnPointsChangedEvent); + OnPointsChangedEvent OnPointsChanged; + + // This delegate is called when the mode of the mechanic changes (i.e., we leave or re-enter interactive initialization) + DECLARE_MULTICAST_DELEGATE(OnModeChangedEvent); + OnModeChangedEvent OnModeChanged; + + // Functions used for initializing the mechanic + virtual void Initialize(const TArray& Points, bool bIsLoop); + int32 AppendPoint(const FVector3d& PointCoordinates); + + void SetInteractiveInitialization(bool bOn); + inline bool IsInInteractiveIntialization() + { + return bInteractiveInitializationMode; + } + + void SetIsLoop(bool bIsLoop); + + /** Returns whether the underlying sequence of control points is a loop. */ + inline bool GetIsLoop() + { + return bIsLoop; + } + + /** Sets the plane on which new points are added on the ends and in which the points are moved. */ + void SetPlane(const FFrame3d& DrawPlaneIn); + // TODO: It is simple to allow the points to be moved arbitrarily, not just inside the plane, if we ever + // want to use the mechanic somewhere where that is desirable. However, we'd need to do a little more work + // to allow new points to be added in arbitrary locations. + + void SetSnappingEnabled(bool bOn); + + // Adds additional line to snap points to. Useful, for instance, if the curve is a revolution profile curve + // and needs to be able to snap to the revolution axis. + void AddSnapLine(int32 LineID, const FLine3d& Line); + + void RemoveSnapLine(int32 LineID); + + /** Clears all points in the mechanic. */ + void ClearPoints(); + + /** + * Deletes currently selected points- can be called on a key press from the parent tool. + * + * Ideally, the mechanic would catch key presses itself, without the tool having to worry + * about it. However for now, we are limited to having to register the key handler in the + * tool. + */ + void DeleteSelectedPoints(); + + /** Expires any changes currently associated with the mechanic in the undo/redo stack. */ + inline void ExpireChanges() + { + ++CurrentChangeStamp; + } + + /** Outputs the positions of the points in the control point sequence. Does not clear PositionsOut before use. */ + void ExtractPointPositions(TArray &PositionsOut); + + /** Gives number of points currently managed by the mechanic. */ + inline int32 GetNumPoints() + { + return ControlPoints.Num(); + } + + + // Some other standard functions + virtual ~UCurveControlPointsMechanic(); + virtual void Setup(UInteractiveTool* ParentTool) override; + virtual void Shutdown() override; + void SetWorld(UWorld* World); + virtual void Render(IToolsContextRenderAPI* RenderAPI) override; + + // IClickBehaviorTarget implementation + virtual FInputRayHit IsHitByClick(const FInputDeviceRay& ClickPos) override; + virtual void OnClicked(const FInputDeviceRay& ClickPos) override; + + // IHoverBehaviorTarget implementation + virtual FInputRayHit BeginHoverSequenceHitTest(const FInputDeviceRay& PressPos) override; + virtual void OnBeginHover(const FInputDeviceRay& DevicePos) override; + virtual bool OnUpdateHover(const FInputDeviceRay& DevicePos) override; + virtual void OnEndHover() override; + + // IModifierToggleBehaviorTarget implementation, inherited through IClickBehaviorTarget + virtual void OnUpdateModifierState(int ModifierID, bool bIsOn) override; + +protected: + + // This actually stores the sequence of point IDs, and their coordinates. + FOrderedPoints ControlPoints; + + bool bIsLoop; + bool bInteractiveInitializationMode = false; + + bool bSnappingEnabled = true; + FPointPlanarSnapSolver SnapEngine; + + // Used for snapping to the start/end of the curve to get out of initialization mode + int32 FirstPointSnapID; + int32 LastPointSnapID; + int32 EndpointSnapPriority; + + // When storing user-defined lines to snap to, we add this to the user-provided id to avoid + // conflicting with any lines generated by the snap engine. + int32 LineSnapIDMin; + + int32 LineSnapPriority; + + // Used for spatial queries + FGeometrySet3 GeometrySet; + + /** Used for displaying points/segments */ + UPROPERTY() + APreviewGeometryActor* PreviewGeometryActor; + UPROPERTY() + UPointSetComponent* DrawnControlPoints; + UPROPERTY() + ULineSetComponent* DrawnControlSegments; + + // These get drawn separately because the other components have to be 1:1 with the control + // points structure, which would make it complicated to keep track of special id's. + UPROPERTY() + UPointSetComponent* PreviewPoint; + UPROPERTY() + ULineSetComponent* PreviewSegment; + + // Variables for drawing + FColor InitializationCurveColor; + FColor NormalCurveColor; + FColor CurrentSegmentsColor; + FColor CurrentPointsColor; + float SegmentsThickness; + float PointsSize; + float DepthBias; + FColor PreviewColor; + FColor HoverColor; + FColor SelectedColor; + FColor SnapLineColor; + + // Used for adding new points on the ends and for limiting point movement + FFrame3d DrawPlane; + + // Support for Shift and Ctrl toggles + bool bAddToSelectionToggle = false; + bool bSnapToggle = false; + int32 ShiftModifierId = 1; + bool bInsertPointToggle = false; + int32 CtrlModifierId = 2; + + // Support for gizmo. Since the points aren't individual components, we don't actually use UTransformProxy + // for the transform forwarding- we just use it for the callbacks. + UPROPERTY() + UTransformProxy* PointTransformProxy; + UPROPERTY() + UTransformGizmo* PointTransformGizmo; + + // Used to make it easy to tell whether the gizmo was moved by the user or by undo/redo or + // some other change that we shoulnd't respond to. Basing our movement undo/redo on the + // gizmo turns out to be quite a pain, though may someday be easier if the transform proxy + // is able to manage arbitrary objects. + bool bGizmoBeingDragged = false; + + // Callbacks we'll receive from the gizmo proxy + void GizmoTransformChanged(UTransformProxy* Proxy, FTransform Transform); + void GizmoTransformStarted(UTransformProxy* Proxy); + void GizmoTransformEnded(UTransformProxy* Proxy); + + // Support for hovering + FViewCameraState CameraState; + TFunction GeometrySetToleranceTest; + int32 HoveredPointID = -1; + void ClearHover(); + // Used to unhover a point, since this will differ depending on whether the point is selected. + FColor PreHoverPointColor; + void UpdateSnapTargetsForHover(); + void UpdateSnapHistoryPoint(int32 Index, FVector3d NewPosition); + + // Support for selection + TArray SelectedPointIDs; + // We need the selected point start positions so we can move multiple points appropriately. + TArray SelectedPointStartPositions; + // The starting point of the gizmo is needed to determine the offset by which to move the points. + FVector GizmoStartPosition; + + // These issue undo/redo change objects, and must therefore not be called in undo/redo code. + void ChangeSelection(int32 NewPointID, bool AddToSelection); + void ClearSelection(); + + // All of the following do not issue undo/redo change objects. + int32 InsertPointAt(int32 SequencePosition, const FVector3d& NewPointCoordinates, const int32* KnownPointID = nullptr); + int32 DeletePoint(int32 SequencePosition); + bool HitTest(const FInputDeviceRay& ClickPos, FInputRayHit& ResultOut); + void SelectPoint(int32 PointID); + bool DeselectPoint(int32 PointID); + void UpdateGizmoLocation(); + void UpdatePointLocation(int32 PointID, const FVector3d& NewLocation); + + // Used for expiring undo/redo changes, which compare this to their stored value and expire themselves if they do not match. + int32 CurrentChangeStamp = 0; + + friend class FCurveControlPointsMechanicSelectionChange; + friend class FCurveControlPointsMechanicInsertionChange; + friend class FCurveControlPointsMechanicMovementChange; + friend class FCurveControlPointsMechanicModeChange; +}; + + +// Undo/redo support: + +class MODELINGCOMPONENTS_API FCurveControlPointsMechanicSelectionChange : public FToolCommandChange +{ +public: + FCurveControlPointsMechanicSelectionChange(int32 SequencePositionIn, bool AddedIn, int32 ChangeStampIn); + + virtual void Apply(UObject* Object) override; + virtual void Revert(UObject* Object) override; + virtual bool HasExpired(UObject* Object) const override + { + return Cast(Object)->CurrentChangeStamp != ChangeStamp; + } + virtual FString ToString() const override; + +protected: + int32 PointID; + bool Added; + int32 ChangeStamp; +}; + +class MODELINGCOMPONENTS_API FCurveControlPointsMechanicInsertionChange : public FToolCommandChange +{ +public: + FCurveControlPointsMechanicInsertionChange(int32 SequencePositionIn, int32 PointID, + const FVector3d& CoordinatesIn, bool AddedIn, int32 ChangeStampIn); + + virtual void Apply(UObject* Object) override; + virtual void Revert(UObject* Object) override; + virtual bool HasExpired(UObject* Object) const override + { + return Cast(Object)->CurrentChangeStamp != ChangeStamp; + } + virtual FString ToString() const override; + +protected: + int32 SequencePosition; + int32 PointID; + FVector3d Coordinates; + bool Added; + int32 ChangeStamp; +}; + +class MODELINGCOMPONENTS_API FCurveControlPointsMechanicModeChange : public FToolCommandChange +{ +public: + FCurveControlPointsMechanicModeChange(bool bDoneWithInitializationIn, bool bIsLoopIn, int32 ChangeStampIn); + + virtual void Apply(UObject* Object) override; + virtual void Revert(UObject* Object) override; + virtual bool HasExpired(UObject* Object) const override + { + return Cast(Object)->CurrentChangeStamp != ChangeStamp; + } + virtual FString ToString() const override; + +protected: + bool bDoneWithInitialization; + bool bIsLoop; + int32 ChangeStamp; +}; + +class MODELINGCOMPONENTS_API FCurveControlPointsMechanicMovementChange : public FToolCommandChange +{ +public: + FCurveControlPointsMechanicMovementChange(int32 PointIDIn, const FVector3d& OriginalPositionIn, + const FVector3d& NewPositionIn, int32 ChangeStampIn); + + virtual void Apply(UObject* Object) override; + virtual void Revert(UObject* Object) override; + virtual bool HasExpired(UObject* Object) const override + { + return Cast(Object)->CurrentChangeStamp != ChangeStamp; + } + virtual FString ToString() const override; + +protected: + int32 PointID; + FVector3d OriginalPosition; + FVector3d NewPosition; + int32 ChangeStamp; +}; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/MeshOpPreviewHelpers.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/MeshOpPreviewHelpers.h index 3cecc687f213..caee2b78c12c 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/MeshOpPreviewHelpers.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/MeshOpPreviewHelpers.h @@ -94,6 +94,14 @@ public: bool HaveValidResult() const { return bResultValid; } + /** + * Read back a copy of current preview mesh. + * @param bOnlyIfValid if true, then only create mesh copy if HaveValidResult() == true + * @return true if MeshOut was initialized + */ + bool GetCurrentResultCopy(FDynamicMesh3& MeshOut, bool bOnlyIfValid = true); + + // // Optional configuration // diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Physics/CollisionGeometryConversion.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Physics/CollisionGeometryConversion.h new file mode 100644 index 000000000000..3351af9becc5 --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Physics/CollisionGeometryConversion.h @@ -0,0 +1,75 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "OrientedBoxTypes.h" +#include "SegmentTypes.h" +#include "CapsuleTypes.h" +#include "SphereTypes.h" +#include "DynamicMesh3.h" + + +#include "Engine/Classes/PhysicsEngine/AggregateGeom.h" + + +namespace UE +{ + namespace Geometry + { + /** + * Convert FSphere3d to FKSphereElem + */ + void GetFKElement(const FSphere3d& Sphere, FKSphereElem& ElemInOut) + { + ElemInOut.Center = (FVector)Sphere.Center; + ElemInOut.Radius = (float)Sphere.Radius; + } + + /** + * Convert FOrientedBox3d to FKBoxElem + */ + void GetFKElement(const FOrientedBox3d& Box, FKBoxElem& BoxInOut) + { + BoxInOut.X = 2 * (float)Box.Extents.X; + BoxInOut.Y = 2 * (float)Box.Extents.Y; + BoxInOut.Z = 2 * (float)Box.Extents.Z; + + BoxInOut.Center = (FVector)Box.Frame.Origin; + BoxInOut.Rotation = FRotator((FQuat)Box.Frame.Rotation); + } + + /** + * Convert FCapsule3d to FKSphylElem + */ + void GetFKElement(const FCapsule3d& Capsule, FKSphylElem& CapsuleInOut) + { + FFrame3d CapsuleFrame(Capsule.Center(), Capsule.Direction()); + + CapsuleInOut.Center = (FVector)CapsuleFrame.Origin; + CapsuleInOut.Rotation = FRotator((FQuat)CapsuleFrame.Rotation); + CapsuleInOut.Length = (float)Capsule.Length(); // Sphyl length is full length + CapsuleInOut.Radius = (float)Capsule.Radius; + } + + /** + * Convert FDynamicMesh3 to FKConvexElem + */ + void GetFKElement(const FDynamicMesh3& Mesh, FKConvexElem& ConvexInOut) + { + ConvexInOut.VertexData.Reset(); + + for (int32 vid : Mesh.VertexIndicesItr()) + { + FVector3d Pos = Mesh.GetVertex(vid); + ConvexInOut.VertexData.Add((FVector)Pos); + } + + // despite the name this actually computes the convex hull of the point set... + ConvexInOut.UpdateElemBox(); + } + + + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Physics/PhysicsDataCollection.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Physics/PhysicsDataCollection.h new file mode 100644 index 000000000000..ed0955b35e7d --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Physics/PhysicsDataCollection.h @@ -0,0 +1,78 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ShapeApproximation/SimpleShapeSet3.h" +#include "Engine/Classes/Engine/StaticMesh.h" +#include "Engine/Classes/PhysicsEngine/AggregateGeom.h" + +class UActorComponent; +class UBodySetup; + + +/** + * FPhysicsDataCollection holds onto physics-system data that is needed for various interactive tools and algorithms. + * Currently this is split into two parts, pointers to the owning physics state, and collision geometry data. + * + * The collision geometry data is stored in both the editable GeometryProcessing format and the + * physics-system representations (currently FKAggregateGeom but more are expected). + * This class provides a high-level API for transferring geometry between these two representations. + */ +class MODELINGCOMPONENTS_API FPhysicsDataCollection +{ +public: + // + // External State Data + // + + /** The Component this physics data came from / is for */ + TWeakObjectPtr SourceComponent; + /** The BodySetup in use by the SourceComponent */ + TWeakObjectPtr BodySetup; + /** Scaling factor applied to the SourceComponent, which should be transferred to Collision Geometry in some cases */ + FVector ExternalScale3D; + + + // + // Geometry data + // + + /** + * Stores representation of Collision geometry + */ + FSimpleShapeSet3d Geometry; + + /** + * Collision geometry in Physics-system format. Not necessarily in sync with Geometry member. + */ + FKAggregateGeom AggGeom; + + + /** + * Initialize from the given Component, and optionally initialize internal geometry members + */ + void InitializeFromComponent(const UActorComponent* Component, bool bInitializeAggGeom); + + /** + * Initialize + */ + void InitializeFromExisting(const FPhysicsDataCollection& Other); + + /** + * replace our geometry data with that from another FPhysicsDataCollection + */ + void CopyGeometryFromExisting(const FPhysicsDataCollection& Other); + + + /** + * Empty out our FKAggregateGeom + */ + void ClearAggregate(); + + /** + * Populate our FKAggregateGeom from our FSimpleShapeSet3d + */ + void CopyGeometryToAggregate(); + +}; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/PreviewMesh.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/PreviewMesh.h index 2a871b987799..cb23bb6d08c9 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/PreviewMesh.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/PreviewMesh.h @@ -77,12 +77,16 @@ public: void Disconnect(); + /** + * @return internal Actor created by this UPreviewMesh + */ + AActor* GetActor() const { return TemporaryParentActor; } + /** * @return internal Root Component of internal Actor */ UPrimitiveComponent* GetRootComponent() { return DynamicMeshComponent; } - // // visualization parameters // @@ -276,6 +280,11 @@ public: */ void UpdatePreview(const FDynamicMesh3* Mesh); + /** + * Update the internal mesh by moving in the given Mesh + */ + void UpdatePreview(FDynamicMesh3&& Mesh); + /** * Initialize the internal mesh based on the given MeshDescription */ diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Selection/GroupTopologySelector.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Selection/GroupTopologySelector.h index 326a03652578..5a57bcd25544 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Selection/GroupTopologySelector.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Selection/GroupTopologySelector.h @@ -10,10 +10,10 @@ class FToolDataVisualizer; struct FViewCameraState; - /** * FGroupTopologySelector implements selection behavior for a FGroupTopology mesh. - * Groups, Group Edges, and Corners can be selected (optionally configurable via UpdateEnableFlags). + * Groups, Group Edges, and Corners can be selected dependings on the settings passed in. + * * Internally a FGeometrySet3 is constructed to support ray-hit testing against the edges and corners. * * Note that to hit-test against the mesh you have to provide a your own FDynamicMeshAABBTree3. @@ -34,6 +34,28 @@ public: // Configuration variables // + /** + * Determines the behavior of a FindSelectedElement() call. + */ + struct FSelectionSettings + { + bool bEnableFaceHits = true; + bool bEnableEdgeHits = true; + bool bEnableCornerHits = true; + + // The following are mainly useful for ortho viewport selection: + + // Prefer an edge projected to a point rather than the point, and a face projected to an edge + // rather than the edge. + bool bPreferProjectedElement = false; + + // If the first element is valid, select all elements behind it that are aligned with it. + bool bSelectDownRay = false; + + //Do not check whether the closest element is occluded. + bool bIgnoreOcclusion = false; + }; + /** * This is the function we use to determine if a point on a corner/edge is close enough * to the hit-test ray to treat as a "hit". By default this is Euclidean distance with @@ -72,32 +94,33 @@ public: const FGeometrySet3& GetGeometrySet(); /** - * Configure whether faces, edges, and corners will be returned by hit-tests - */ - void UpdateEnableFlags(bool bFaceHits, bool bEdgeHits, bool bCornerHits); - - /** - * Configure the selection mode. + * Find which element was selected for a given ray. * - * @param bPreferProjectedElement Prefer an edge projected to a point rather than the point, and a face projected to an edge - * rather than the edge. - * @param bSelectDownRay If the first element is valid, select all elements behind it that are aligned with it. - * @param bIgnoreOcclusion Do not check whether the closest element is occluded. - */ - void UpdateSelectionModeFlags(bool bPreferProjectedElement, bool bSelectDownRay, bool bIgnoreOcclusion); - - /** - * Find which element was selected for a given ray + * @param Settings settings that determine what can be selected. * @param Ray hit-test ray - * @param ResultOut resulting selection. At most one of the Groups/Corners/Edges members will contain one element. + * @param ResultOut resulting selection. Will not be cleared before use. At most one of the Groups/Corners/Edges + * members will be added to. * @param SelectedPositionOut The point on the ray nearest to the selected element * @param SelectedNormalOut the normal at that point if ResultOut contains a selected face, otherwise uninitialized * @param EdgeSegmentIdOut When not null, if the selected element is a group edge, the segment id of the segment * that was actually clicked within the edge polyline will be stored here. + * + * @return true if something was selected */ - bool FindSelectedElement(const FRay3d& Ray, FGroupTopologySelection& ResultOut, + bool FindSelectedElement(const FSelectionSettings& Settings, const FRay3d& Ray, FGroupTopologySelection& ResultOut, FVector3d& SelectedPositionOut, FVector3d& SelectedNormalOut, int32* EdgeSegmentIdOut = nullptr); + /** + * Using the edges in the given selection as starting points, add any "edge loops" containing the edges. An + * edge loop is a sequence of edges that passes through valence-4 corners through the opposite edge, and may + * not actually form a complete loop if they hit a non-valence-4 corner. + * + * @param Selection Selection to expand. + * @param bool true if selection was modified (i.e., were the already selected edges part of any edge loops whose + * member edges were not yet all selected). + */ + bool ExpandSelectionByEdgeLoops(FGroupTopologySelection& Selection); + /** * Render the given selection with the default settings of the FToolDataVisualizer. * Selected edges are drawn as lines, and selected corners are drawn as small view-facing circles. @@ -121,16 +144,10 @@ protected: bool bGeometryUpToDate = false; FGeometrySet3 GeometrySet; - bool bEnableFaceHits = true; - bool bEnableEdgeHits = true; - bool bEnableCornerHits = true; - - bool bPreferAlignedElement = false; - bool bSelectDownRay = false; - bool bIgnoreOcclusion = false; - - bool DoCornerBasedSelection(const FRay3d& Ray, FDynamicMeshAABBTree3* Spatial, const FGeometrySet3& TopoSpatial, + bool DoCornerBasedSelection(const FSelectionSettings& Settings, const FRay3d& Ray, + FDynamicMeshAABBTree3* Spatial, const FGeometrySet3& TopoSpatial, FGroupTopologySelection& ResultOut, FVector3d& SelectedPositionOut, int32 *EdgeSegmentIdOut) const; - bool DoEdgeBasedSelection(const FRay3d& Ray, FDynamicMeshAABBTree3* Spatial, const FGeometrySet3& TopoSpatial, + bool DoEdgeBasedSelection(const FSelectionSettings& Settings, const FRay3d& Ray, + FDynamicMeshAABBTree3* Spatial, const FGeometrySet3& TopoSpatial, FGroupTopologySelection& ResultOut, FVector3d& SelectedPositionOut, int32 *EdgeSegmentIdOut) const; }; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Selection/PolygonSelectionMechanic.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Selection/PolygonSelectionMechanic.h index 78b08e60c92e..42784bc57608 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Selection/PolygonSelectionMechanic.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Selection/PolygonSelectionMechanic.h @@ -93,6 +93,11 @@ public: TFunction GetAddToSelectionModifierStateFunc = []() {return false; } ); + void SetShouldSelectEdgeLoopsFunc(TFunction Func) + { + ShouldSelectEdgeLoopsFunc = Func; + } + /** * Notify internal data structures that the associated MeshComponent has been modified. * @param bTopologyModified if true, the underlying mesh topology has been changed. This clears the current selection. @@ -106,9 +111,11 @@ public: * OutHit.ImpactPoint: closest point on the ray to the hit element (Note: not a point on the element!) * OutHit.Distance: distance along the ray to ImpactPoint * OutHit.Item: if hit item was an edge, index of the segment within the edge polyline. Otherwise undefined. + * + * @param bUseOrthoSettings If true, the ortho-relevant settings for selection are used (selecting down the view ray, etc) */ - bool TopologyHitTest(const FRay& WorldRay, FHitResult& OutHit, FGroupTopologySelection& OutSelection); - bool TopologyHitTest(const FRay& WorldRay, FHitResult& OutHit); + bool TopologyHitTest(const FRay& WorldRay, FHitResult& OutHit, FGroupTopologySelection& OutSelection, bool bUseOrthoSettings = false); + bool TopologyHitTest(const FRay& WorldRay, FHitResult& OutHit, bool bUseOrthoSettings = false); // // Hover API @@ -196,18 +203,19 @@ protected: TFunction GetSpatialFunc; TFunction GetAddToSelectionModifierStateFunc; + TFunction ShouldSelectEdgeLoopsFunc = []() {return false; }; FTransform3d TargetTransform; FGroupTopologySelector TopoSelector; - /** - * Update the topology selector given the current selection settings. - * + /** + * Get the topology selector settings to use given the current selection settings. + * * @param bUseOrthoSettings If true, the topology selector will be configured to use ortho settings, * which are generally different to allow for selection of projected elements, etc. */ - void UpdateTopoSelector(bool bUseOrthoSettings = false); + FGroupTopologySelector::FSelectionSettings GetTopoSelectorSettings(bool bUseOrthoSettings = false); FGroupTopologySelection HilightSelection; FGroupTopologySelection PersistentSelection; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Snapping/BasePositionSnapSolver3.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Snapping/BasePositionSnapSolver3.h index 7fbf54db1709..5f2868dfbf38 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Snapping/BasePositionSnapSolver3.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Snapping/BasePositionSnapSolver3.h @@ -227,6 +227,8 @@ protected: // snap measurement functions // + int32 FindIndexOfBestSnapInSet(const TArray& TestTargets, double& MinMetric, int& MinPriority, + const TFunction& GetSnapPointFromFunc); const FSnapTargetPoint* FindBestSnapInSet(const TArray& TestTargets, double& MinMetric, int& MinPriority, const TFunction& GetSnapPointFromFunc); diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Snapping/PointPlanarSnapSolver.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Snapping/PointPlanarSnapSolver.h index c07e892dc8fe..7cfd914c3753 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Snapping/PointPlanarSnapSolver.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/Snapping/PointPlanarSnapSolver.h @@ -4,6 +4,8 @@ #include "Snapping/BasePositionSnapSolver3.h" +class IToolsContextRenderAPI; + /** * FPointPlanarSnapSolver solves for a Point snap location on a plane, based * on an input Point and a set of target points and lines in the plane. @@ -24,34 +26,96 @@ public: bool bEnableSnapToKnownLengths = true; static constexpr int CardinalAxisTargetID = 10; - static constexpr int LastSegmentTargetID = 11; + static constexpr int LastSegmentTargetID = 11; + static constexpr int IntersectionTargetID = 12; int CardinalAxisPriority = 150; int LastSegmentPriority = 140; + + /** How much more important a known length is than its line's priority. */ int KnownLengthPriorityDelta = 10; - int MinInternalPriority() const { return LastSegmentPriority - KnownLengthPriorityDelta; } + + /** How much more important an intersection is than the more important of the intersecting lines. */ + int32 IntersectionPriorityDelta = 11; + + int MinInternalPriority() const { return FMath::Min(CardinalAxisPriority, LastSegmentPriority) - FMath::Max(IntersectionPriorityDelta, KnownLengthPriorityDelta); } public: FPointPlanarSnapSolver(); + /** + * Creates snap lines based on the last point in the point history. Calling the function + * with both parameters set to false clears the generated snap lines. + * + * @param bCardinalAxes If true, the generated lines are parallel to the axes of the plane + * and pass through the last point. + * @param bLastHistorySegment If true, adds a snap line through the last point that is 90 + * degrees to the last segment. + */ void RegenerateTargetLines(bool bCardinalAxes, bool bLastHistorySegment); - virtual void Reset() override; - virtual void ResetActiveSnap() override; + /** + * Sets the snapping lines to be based on the history points *adjacent* to the point with a + * given history index. The given index can be one beyond the ends of the current history + * (i.e., -1 or PointHistoryLength) to base the lines on the first/last points. + * The snap lines will be parallel to the plane axes. + * Useful for moving a point to be aligned with one of its neighbors. + * + * @param HistoryIndex Index in the range [-1, PointHistoryLength()]. The points adjacent + * to that index will be used for line generation. + * @param bWrapAround If true, the first point will be considered adjacent to the last + * when HistoryIndex is 0 or PointHistoryLength()-1. + * @param bGenerateIntersections If true, intersections will be generated as higher-priority + * points. Intersections are performed with generated and user-specified lines that + * lie in the plane. + */ + void RegenerateTargetLinesAround(int32 HistoryIndex, bool bWrapAround = false, bool bGenerateIntersections = true); + virtual void Reset() override; + + /** Point history manipulation functions. All of them remove the currently generated snap*/ void UpdatePointHistory(const TArray& Points); void UpdatePointHistory(const TArray& Points); + void AppendHistoryPoint(const FVector3d& Point); + void InsertHistoryPoint(const FVector3d& Point, int32 Index); + void RemoveHistoryPoint(int32 Index); + int32 PointHistoryLength() + { + return PointHistory.Num(); + } void UpdateSnappedPoint(const FVector3d& PointIn); + /** + * Returns true when the active snap represents an intersection of multiple target lines in the plane. In such a case, one line + * can be obtained by the usual GetActiveSnapLine(), and the second can be obtained from GetIntersectionSecondLine() + */ + bool HaveActiveSnapIntersection() { return HaveActiveSnap() && bActiveSnapIsIntersection; } + + /** + * When the active snap is an intersection, holds the second intersecting line (the first can be obtained with GetActiveSnapLine()) + */ + const FLine3d& GetIntersectionSecondLine() { return IntersectionSecondLine; } + + /** Draws the current snap targets (for debugging) */ + void DebugRender(IToolsContextRenderAPI* RenderAPI); + protected: TArray GeneratedLines; + TArray IntersectionPoints; + TArray IntersectionSecondLinePointers; // [1:1] with IntersectionPoints + bool bActiveSnapIsIntersection; + FLine3d IntersectionSecondLine; + TSet IgnoreTargets; TArray PointHistory; TArray GeneratedTargets; + + void ResetGenerated(); + void GenerateLineIntersectionTargets(); void GenerateTargets(const FVector3d& PointIn); }; diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/ToolSceneQueriesUtil.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/ToolSceneQueriesUtil.h index e801abf929c6..5bf6665ad446 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/ToolSceneQueriesUtil.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Public/ToolSceneQueriesUtil.h @@ -32,6 +32,14 @@ namespace ToolSceneQueriesUtil */ MODELINGCOMPONENTS_API bool PointSnapQuery(const FViewCameraState& CameraState, const FVector3d& Point1, const FVector3d& Point2, double VisualAngleThreshold = 0); + /** + * Get a measurement for testing whether two points can snap together, useful for choosing the best snap point among multiple + * options. For perspective mode, the returned metric is the "visual angle" between the points, which is the angle between the points + * to the camera scaled such that the visual angle between the horizontal bounds of the view is 90. For orthographic mode, it is a + * projected distance onto the view plane scaled such that the distance between the horizontal bounds of the view is 90. + * Thus, the metric is suitable for comparing against a visual angle snap threshold to determine if snapping should happen. + */ + MODELINGCOMPONENTS_API double PointSnapMetric(const FViewCameraState& CameraState, const FVector3d& Point1, const FVector3d& Point2); /** * @return visual angle between two 3D points, relative to the current camera position diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperators/Private/CuttingOps/EdgeLoopInsertionOp.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperators/Private/CuttingOps/EdgeLoopInsertionOp.cpp new file mode 100644 index 000000000000..4b647e844256 --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperators/Private/CuttingOps/EdgeLoopInsertionOp.cpp @@ -0,0 +1,56 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "CuttingOps/EdgeLoopInsertionOp.h" + +#include "Util/ProgressCancel.h" + +void FEdgeLoopInsertionOp::SetTransform(const FTransform& Transform) { + ResultTransform = (FTransform3d)Transform; +} + +void FEdgeLoopInsertionOp::GetLoopEdgeLocations(TArray>& EndPointPairsOut) const +{ + EndPointPairsOut.Reset(); + for (int32 Eid : LoopEids) + { + TPair Endpoints; + ResultMesh->GetEdgeV(Eid, Endpoints.Key, Endpoints.Value); + EndPointPairsOut.Add(MoveTemp(Endpoints)); + } +} + +void FEdgeLoopInsertionOp::CalculateResult(FProgressCancel* Progress) +{ + bSucceeded = false; + + if (Progress->Cancelled()) + { + return; + } + + ResultMesh->Copy(*OriginalMesh, true, true, true, true); + ResultTopology = MakeShared(); + *ResultTopology = *OriginalTopology; + ResultTopology->RetargetOnClonedMesh(ResultMesh.Get()); + + if (Progress->Cancelled()) + { + return; + } + + if (InputLengths.Num() > 0) + { + FGroupEdgeInserter::FEdgeLoopInsertionParams Params; + Params.Mesh = ResultMesh.Get(); + Params.Topology = ResultTopology.Get(); + Params.GroupEdgeID = GroupEdgeID; + Params.Mode = Mode; + Params.SortedInputLengths = &InputLengths; + Params.bInputsAreProportions = bInputsAreProportions; + Params.StartCornerID = StartCornerID; + Params.VertexTolerance = VertexTolerance; + + FGroupEdgeInserter Inserter; + bSucceeded = Inserter.InsertEdgeLoops(Params, &LoopEids, Progress); + } +} diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperators/Public/CuttingOps/EdgeLoopInsertionOp.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperators/Public/CuttingOps/EdgeLoopInsertionOp.h new file mode 100644 index 000000000000..b03cb46b3f9f --- /dev/null +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperators/Public/CuttingOps/EdgeLoopInsertionOp.h @@ -0,0 +1,42 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ModelingOperators.h" +#include "Operations/GroupEdgeInserter.h" + +class FProgressCancel; + +class MODELINGOPERATORS_API FEdgeLoopInsertionOp : public FDynamicMeshOperator +{ +public: + virtual ~FEdgeLoopInsertionOp() {} + + // Inputs: + TSharedPtr OriginalMesh; + TSharedPtr OriginalTopology; + FGroupEdgeInserter::EInsertionMode Mode; + double VertexTolerance; // TODO: Add some defaults + int32 GroupEdgeID = FDynamicMesh3::InvalidID; + TArray InputLengths; + bool bInputsAreProportions = false; + int32 StartCornerID = FDynamicMesh3::InvalidID; + + // Outputs: + TSet LoopEids; // These are edge ID's in the ResultMesh. + TSharedPtr ResultTopology; + bool bSucceeded = false; + + void SetTransform(const FTransform& Transform); + + /** + * Converts LoopEids into pairs of endpoints, since ResultMesh is inaccessible without + * extracting it. Can be used to render the added loops. + * Clears EndPointPairsOut before use. + */ + void GetLoopEdgeLocations(TArray>& EndPointPairsOut) const; + + // FDynamicMeshOperator implementation + virtual void CalculateResult(FProgressCancel* Progress) override; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/CompositionOps/VoxelBooleanMeshesOp.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/CompositionOps/VoxelBooleanMeshesOp.cpp index 02976e60eeee..c0fce134838b 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/CompositionOps/VoxelBooleanMeshesOp.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/CompositionOps/VoxelBooleanMeshesOp.cpp @@ -196,9 +196,9 @@ float FVoxelBooleanMeshesOp::ComputeVoxelSize() const FVector Extents(BBoxMax.X - BBoxMin.X, BBoxMax.Y - BBoxMin.Y, BBoxMax.Z - BBoxMin.Z); // Scale with the local space scale. - Extents.X = Extents.X * Scale.X; - Extents.Y = Extents.Y * Scale.Y; - Extents.Z = Extents.Z * Scale.Z; + Extents.X = Extents.X * FMath::Abs(Scale.X); + Extents.Y = Extents.Y * FMath::Abs(Scale.Y); + Extents.Z = Extents.Z * FMath::Abs(Scale.Z); float MajorAxisSize = FMath::Max3(Extents.X, Extents.Y, Extents.Z); diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/CompositionOps/VoxelMergeMeshesOp.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/CompositionOps/VoxelMergeMeshesOp.cpp index 2ead976544da..5b14db425363 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/CompositionOps/VoxelMergeMeshesOp.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/CompositionOps/VoxelMergeMeshesOp.cpp @@ -157,9 +157,9 @@ float FVoxelMergeMeshesOp::ComputeVoxelSize() const FVector Extents(BBoxMax.X - BBoxMin.X, BBoxMax.Y - BBoxMin.Y, BBoxMax.Z - BBoxMin.Z); // Scale with the local space scale. - Extents.X = Extents.X * Scale.X; - Extents.Y = Extents.Y * Scale.Y; - Extents.Z = Extents.Z * Scale.Z; + Extents.X = Extents.X * FMath::Abs(Scale.X); + Extents.Y = Extents.Y * FMath::Abs(Scale.Y); + Extents.Z = Extents.Z * FMath::Abs(Scale.Z); float MajorAxisSize = FMath::Max3(Extents.X, Extents.Y, Extents.Z); diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/ParameterizationOps/ParameterizeMeshOp.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/ParameterizationOps/ParameterizeMeshOp.cpp index 1b549b2300f1..7bb0e53950a2 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/ParameterizationOps/ParameterizeMeshOp.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/ParameterizationOps/ParameterizeMeshOp.cpp @@ -160,8 +160,19 @@ bool FParameterizeMeshOp::ComputeUVs(FDynamicMesh3& Mesh, TFunction UVVertexBuffer; @@ -205,7 +216,7 @@ bool FParameterizeMeshOp::ComputeUVs(FDynamicMesh3& Mesh, TFunctionSetTriangle(TriID, ElTri); diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/ParameterizationOps/UVLayoutOp.cpp b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/ParameterizationOps/UVLayoutOp.cpp index 584690e46b24..64b70fd87779 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/ParameterizationOps/UVLayoutOp.cpp +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Private/ParameterizationOps/UVLayoutOp.cpp @@ -10,6 +10,8 @@ #include "Selections/MeshConnectedComponents.h" +#include "Parameterization/MeshUVPacking.h" + void FUVLayoutOp::SetTransform(const FTransform& Transform) { ResultTransform = (FTransform3d)Transform; @@ -119,8 +121,7 @@ void FUVLayoutOp::CalculateResult(FProgressCancel* Progress) { return; } - bool bDiscardAttributes = false; - ResultMesh->Copy(*OriginalMesh, true, true, true, !bDiscardAttributes); + ResultMesh->Copy(*OriginalMesh, true, true, true, true); if (!ensureMsgf(ResultMesh->HasAttributes(), TEXT("Attributes not found on mesh? Conversion should always create them, so this operator should not need to do so."))) { @@ -134,95 +135,57 @@ void FUVLayoutOp::CalculateResult(FProgressCancel* Progress) int UVLayerInput = 0, UVLayerOutput = 0; - ResultMesh->Attributes()->GetUVLayer(UVLayerInput)->SplitBowties(); + FDynamicMeshUVOverlay* UVLayer = ResultMesh->Attributes()->GetUVLayer(UVLayerInput); + + + bool bWillRepackIslands = (UVLayoutMode != EUVLayoutOpLayoutModes::TransformOnly); + + // split bowties so that we can process islands independently + if (bWillRepackIslands || bAlwaysSplitBowties) + { + UVLayer->SplitBowties(); + } if (Progress && Progress->Cancelled()) { return; } - if (!bSeparateUVIslands) + FDynamicMeshUVPacker Packer(UVLayer); + Packer.TextureResolution = this->TextureResolution; + Packer.GutterSize = this->GutterSize; + Packer.bAllowFlips = this->bAllowFlips; + + if (UVLayoutMode == EUVLayoutOpLayoutModes::RepackToUnitRect) { - // The FLayoutUV class doesn't let us access the charts so this code path just finds them directly - - FDynamicMeshUVOverlay* UVLayer = ResultMesh->Attributes()->GetUVLayer(UVLayerOutput); - FMeshConnectedComponents UVComponents(ResultMesh.Get()); - UVComponents.FindConnectedTriangles([&UVLayer](int32 Triangle0, int32 Triangle1) { - return UVLayer->AreTrianglesConnected(Triangle0, Triangle1); - }); - TArray ComponentBounds; ComponentBounds.SetNum(UVComponents.Num()); - TArray ElToComponent; ElToComponent.SetNum(UVLayer->ElementCount()); - for (int32 ComponentIdx = 0; ComponentIdx < UVComponents.Num(); ComponentIdx++) + if (Packer.StandardPack() == false) { - FMeshConnectedComponents::FComponent& Component = UVComponents.GetComponent(ComponentIdx); - FAxisAlignedBox2f& BoundsUV = ComponentBounds[ComponentIdx]; - BoundsUV = FAxisAlignedBox2f::Empty(); - - for (int TID : Component.Indices) - { - if (UVLayer->IsSetTriangle(TID)) - { - FIndex3i TriEls = UVLayer->GetTriangle(TID); - for (int SubIdx = 0; SubIdx < 3; SubIdx++) - { - int ElID = TriEls[SubIdx]; - BoundsUV.Contain(UVLayer->GetElement(ElID)); - ElToComponent[ElID] = ComponentIdx; - } - } - } - } - float MaxDim = 0; - for (FAxisAlignedBox2f& BoundsUV : ComponentBounds) - { - MaxDim = FMath::Max(MaxDim, BoundsUV.MaxDim()); - } - float Scale = 1; - if (MaxDim >= FLT_MIN) - { - Scale = 1.0 / MaxDim; - } - - Scale *= UVScaleFactor; // apply global scale factor - for (int ElID : UVLayer->ElementIndicesItr()) - { - UVLayer->SetElement(ElID, (UVLayer->GetElement(ElID) - ComponentBounds[ElToComponent[ElID]].Min) * Scale); + // failed... what to do? + return; } } - else + else if (UVLayoutMode == EUVLayoutOpLayoutModes::StackInUnitRect) { - // use FLayoutUV to do the layout - - FCompactDynamicMeshWithAttributesLayoutView MeshView(ResultMesh.Get(), UVLayerInput, UVLayerOutput); - FLayoutUV LayoutUV(MeshView); - FOverlappingCorners Overlaps = OverlappingCornersFromUVs(ResultMesh.Get(), UVLayerInput); - if (Progress && Progress->Cancelled()) + if (Packer.StackPack() == false) { + // failed... what to do? return; } - - LayoutUV.FindCharts(Overlaps); - if (Progress && Progress->Cancelled()) - { - return; - } - - LayoutUV.FindBestPacking(TextureResolution); - if (Progress && Progress->Cancelled()) - { - return; - } - - LayoutUV.CommitPackedUVs(); - - // Add global scaling as a postprocess - if (UVScaleFactor != 1.0) - { - FDynamicMeshUVOverlay* UVLayer = ResultMesh->Attributes()->GetUVLayer(UVLayerOutput); - for (int ElID : UVLayer->ElementIndicesItr()) - { - UVLayer->SetElement(ElID, UVLayer->GetElement(ElID) * UVScaleFactor); - } - } } + + if (Progress && Progress->Cancelled()) + { + return; + } + + if (UVScaleFactor != 1.0 || UVTranslation != FVector2f::Zero() ) + { + for (int ElementID : UVLayer->ElementIndicesItr()) + { + FVector2f UV = UVLayer->GetElement(ElementID); + UV = (UV * UVScaleFactor) + UVTranslation; + UVLayer->SetElement(ElementID, UV); + } + } + } diff --git a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Public/ParameterizationOps/UVLayoutOp.h b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Public/ParameterizationOps/UVLayoutOp.h index 0ff9692b1467..600dd8658906 100644 --- a/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Public/ParameterizationOps/UVLayoutOp.h +++ b/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Public/ParameterizationOps/UVLayoutOp.h @@ -7,6 +7,13 @@ #include "ModelingOperators.h" +enum class EUVLayoutOpLayoutModes +{ + TransformOnly = 0, + RepackToUnitRect = 1, + StackInUnitRect = 2 +}; + class MODELINGOPERATORSEDITORONLY_API FUVLayoutOp : public FDynamicMeshOperator { @@ -16,10 +23,14 @@ public: // inputs TSharedPtr OriginalMesh; - bool bSeparateUVIslands = true; - int TextureResolution = 128; - float UVScaleFactor = 1.0; + EUVLayoutOpLayoutModes UVLayoutMode = EUVLayoutOpLayoutModes::RepackToUnitRect; + int TextureResolution = 128; + bool bAllowFlips = false; + bool bAlwaysSplitBowties = true; + float UVScaleFactor = 1.0; + float GutterSize = 1.0; + FVector2f UVTranslation = FVector2f::Zero(); void SetTransform(const FTransform& Transform); diff --git a/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsActions.cpp b/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsActions.cpp index fe8cabba81da..88a3cdbe0b16 100644 --- a/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsActions.cpp +++ b/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsActions.cpp @@ -13,6 +13,7 @@ #include "TransformMeshesTool.h" #include "PlaneCutTool.h" #include "EditMeshPolygonsTool.h" +#include "DrawAndRevolveTool.h" #define LOCTEXT_NAMESPACE "ModelingToolsCommands" @@ -85,6 +86,7 @@ void FModelingToolActionCommands::RegisterAllToolActions() FMeshSelectionToolActionCommands::Register(); FMeshPlaneCutToolActionCommands::Register(); FEditMeshPolygonsToolActionCommands::Register(); + FDrawAndRevolveToolActionCommands::Register(); } void FModelingToolActionCommands::UnregisterAllToolActions() @@ -97,6 +99,7 @@ void FModelingToolActionCommands::UnregisterAllToolActions() FMeshSelectionToolActionCommands::Unregister(); FMeshPlaneCutToolActionCommands::Unregister(); FEditMeshPolygonsToolActionCommands::Unregister(); + FDrawAndRevolveToolActionCommands::Unregister(); } @@ -134,6 +137,10 @@ void FModelingToolActionCommands::UpdateToolCommandBinding(UInteractiveTool* Too { UPDATE_BINDING(FEditMeshPolygonsToolActionCommands); } + else if (Cast(Tool) != nullptr) + { + UPDATE_BINDING(FDrawAndRevolveToolActionCommands); + } else { UPDATE_BINDING(FModelingToolActionCommands); @@ -162,6 +169,7 @@ DEFINE_TOOL_ACTION_COMMANDS(FDrawPolygonToolActionCommands, "ModelingToolsDrawPo DEFINE_TOOL_ACTION_COMMANDS(FMeshSelectionToolActionCommands, "ModelingToolsMeshSelectionTool", "Modeling Tools - Mesh Selection Tool", UMeshSelectionTool); DEFINE_TOOL_ACTION_COMMANDS(FMeshPlaneCutToolActionCommands, "ModelingToolsMeshPlaneCutTool", "Modeling Tools - Mesh Plane Cut Tool", UPlaneCutTool); DEFINE_TOOL_ACTION_COMMANDS(FEditMeshPolygonsToolActionCommands, "ModelingToolsEditMeshPolygonsTool", "Modeling Tools - Edit Mesh Polygons Tool", UEditMeshPolygonsTool); +DEFINE_TOOL_ACTION_COMMANDS(FDrawAndRevolveToolActionCommands, "ModelingToolsDrawAndRevolveTool", "Modeling Tools - Draw-and-Revolve Tool", UDrawAndRevolveTool); diff --git a/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsEditorMode.cpp b/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsEditorMode.cpp index a85a63fbfab3..aabc4681d44f 100644 --- a/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsEditorMode.cpp +++ b/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsEditorMode.cpp @@ -1,6 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "ModelingToolsEditorMode.h" +#include "InteractiveTool.h" #include "ModelingToolsEditorModeToolkit.h" #include "Toolkits/ToolkitManager.h" #include "Framework/Commands/UICommandList.h" @@ -15,6 +16,7 @@ #include "MeshVertexSculptTool.h" #include "EditMeshPolygonsTool.h" #include "DeformMeshPolygonsTool.h" +#include "EdgeLoopInsertionTool.h" #include "ConvertToPolygonsTool.h" #include "AddPrimitiveTool.h" #include "AddPatchTool.h" @@ -64,6 +66,11 @@ #include "MeshTangentsTool.h" #include "ProjectToTargetTool.h" +#include "Physics/PhysicsInspectorTool.h" +#include "Physics/SetCollisionGeometryTool.h" +#include "Physics/ExtractCollisionGeometryTool.h" +//#include "Physics/EditCollisionGeometryTool.h" + #include "EditorModeManager.h" // stylus support @@ -462,7 +469,18 @@ void FModelingToolsEditorMode::Enter() FIsActionButtonVisible::CreateLambda([this]() {return ToolsContext->CanCompleteActiveTool(); }), EUIActionRepeatMode::RepeatDisabled ); - + CommandList->MapAction( + ToolManagerCommands.CancelOrCompleteActiveTool, + FExecuteAction::CreateLambda([this]() { + const EToolShutdownType ShutdownType = ToolsContext->CanCancelActiveTool() ? EToolShutdownType::Cancel : EToolShutdownType::Completed; + ToolsContext->EndTool(ShutdownType); + }), + FCanExecuteAction::CreateLambda([this]() { + return ToolsContext->CanCompleteActiveTool() || ToolsContext->CanCancelActiveTool(); + }), + FGetActionCheckState(), + FIsActionButtonVisible::CreateLambda([this]() { return ToolsContext->CanCompleteActiveTool() || ToolsContext->CanCancelActiveTool();}), + EUIActionRepeatMode::RepeatDisabled); } const FModelingToolsManagerCommands& ToolManagerCommands = FModelingToolsManagerCommands::Get(); @@ -598,6 +616,9 @@ void FModelingToolsEditorMode::Enter() RegisterToolFunc(ToolManagerCommands.BeginProjectToTargetTool, TEXT("ProjectToTargetTool"), NewObject()); RegisterToolFunc(ToolManagerCommands.BeginSimplifyMeshTool, TEXT("SimplifyMeshTool"), NewObject()); + auto EdgeLoopInsertionToolBuilder = NewObject(); + EdgeLoopInsertionToolBuilder->AssetAPI = ToolsContext->GetAssetAPI(); + RegisterToolFunc(ToolManagerCommands.BeginEdgeLoopInsertionTool, TEXT("EdgeLoopInsertionTool"), EdgeLoopInsertionToolBuilder); auto EditNormalsToolBuilder = NewObject(); EditNormalsToolBuilder->AssetAPI = ToolsContext->GetAssetAPI(); @@ -703,6 +724,19 @@ void FModelingToolsEditorMode::Enter() RegisterToolFunc(ToolManagerCommands.BeginPolyGroupsTool, TEXT("ConvertToPolygonsTool"), NewObject()); RegisterToolFunc(ToolManagerCommands.BeginAttributeEditorTool, TEXT("AttributeEditorTool"), NewObject()); + + // Physics Tools + + RegisterToolFunc(ToolManagerCommands.BeginPhysicsInspectorTool, TEXT("PhysicsInspectorTool"), NewObject()); + RegisterToolFunc(ToolManagerCommands.BeginSetCollisionGeometryTool, TEXT("SetCollisionGeoTool"), NewObject()); + //RegisterToolFunc(ToolManagerCommands.BeginEditCollisionGeometryTool, TEXT("EditCollisionGeoTool"), NewObject()); + + auto ExtractCollisionGeoToolBuilder = NewObject(); + ExtractCollisionGeoToolBuilder->AssetAPI = ToolsContext->GetAssetAPI(); + RegisterToolFunc(ToolManagerCommands.BeginExtractCollisionGeometryTool, TEXT("ExtractCollisionGeoTool"), ExtractCollisionGeoToolBuilder); + + + ToolsContext->ToolManager->SelectActiveToolType(EToolSide::Left, TEXT("DynaSculptTool")); // register modeling mode hotkeys @@ -743,6 +777,7 @@ void FModelingToolsEditorMode::Exit() const TSharedRef& ToolkitCommandList = Toolkit->GetToolkitCommands(); ToolkitCommandList->UnmapAction(ToolManagerCommands.AcceptActiveTool); ToolkitCommandList->UnmapAction(ToolManagerCommands.CancelActiveTool); + ToolkitCommandList->UnmapAction(ToolManagerCommands.CancelOrCompleteActiveTool); ToolkitCommandList->UnmapAction(ToolManagerCommands.CompleteActiveTool); FToolkitManager::Get().CloseToolkit(Toolkit.ToSharedRef()); diff --git a/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsEditorModeToolkit.cpp b/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsEditorModeToolkit.cpp index 4fa315801151..b3d0956e0f48 100644 --- a/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsEditorModeToolkit.cpp +++ b/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsEditorModeToolkit.cpp @@ -464,6 +464,8 @@ void FModelingToolsEditorModeToolkit::BuildToolPalette_Standard(FName PaletteInd ToolbarBuilder.AddToolBarButton(Commands.BeginPolyEditTool); ToolbarBuilder.AddToolBarButton(Commands.BeginPolyDeformTool); ToolbarBuilder.AddSeparator(); + ToolbarBuilder.AddToolBarButton(Commands.BeginEdgeLoopInsertionTool); + ToolbarBuilder.AddSeparator(); ToolbarBuilder.AddToolBarButton(Commands.BeginAttributeEditorTool); ToolbarBuilder.AddToolBarButton(Commands.BeginMeshInspectorTool); } @@ -491,6 +493,11 @@ void FModelingToolsEditorModeToolkit::BuildToolPalette_Standard(FName PaletteInd ToolbarBuilder.AddToolBarButton(Commands.BeginMeshToVolumeTool); ToolbarBuilder.AddSeparator(); ToolbarBuilder.AddToolBarButton(Commands.BeginBspConversionTool); + ToolbarBuilder.AddSeparator(); + ToolbarBuilder.AddToolBarButton(Commands.BeginPhysicsInspectorTool); + ToolbarBuilder.AddToolBarButton(Commands.BeginSetCollisionGeometryTool); + ToolbarBuilder.AddToolBarButton(Commands.BeginEditCollisionGeometryTool); + ToolbarBuilder.AddToolBarButton(Commands.BeginExtractCollisionGeometryTool); } } @@ -597,6 +604,8 @@ void FModelingToolsEditorModeToolkit::BuildToolPalette_Experimental(FName Palett ToolbarBuilder.AddToolBarButton(Commands.BeginPolyEditTool); ToolbarBuilder.AddToolBarButton(Commands.BeginPolyDeformTool); ToolbarBuilder.AddSeparator(); + ToolbarBuilder.AddToolBarButton(Commands.BeginEdgeLoopInsertionTool); + ToolbarBuilder.AddSeparator(); ToolbarBuilder.AddToolBarButton(Commands.BeginAttributeEditorTool); ToolbarBuilder.AddToolBarButton(Commands.BeginMeshInspectorTool); } @@ -624,6 +633,11 @@ void FModelingToolsEditorModeToolkit::BuildToolPalette_Experimental(FName Palett ToolbarBuilder.AddToolBarButton(Commands.BeginMeshToVolumeTool); ToolbarBuilder.AddSeparator(); ToolbarBuilder.AddToolBarButton(Commands.BeginBspConversionTool); + ToolbarBuilder.AddSeparator(); + ToolbarBuilder.AddToolBarButton(Commands.BeginPhysicsInspectorTool); + ToolbarBuilder.AddToolBarButton(Commands.BeginSetCollisionGeometryTool); + //ToolbarBuilder.AddToolBarButton(Commands.BeginEditCollisionGeometryTool); + ToolbarBuilder.AddToolBarButton(Commands.BeginExtractCollisionGeometryTool); } else if (PaletteIndex == PrototypesTabName) { diff --git a/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsManagerActions.cpp b/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsManagerActions.cpp index 7afeb8939d12..c7ce6d205460 100644 --- a/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsManagerActions.cpp +++ b/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Private/ModelingToolsManagerActions.cpp @@ -2,6 +2,7 @@ #include "ModelingToolsManagerActions.h" #include "EditorStyleSet.h" +#include "InputCoreTypes.h" #include "ModelingToolsEditorModeStyle.h" #define LOCTEXT_NAMESPACE "ModelingToolsManagerCommands" @@ -42,6 +43,7 @@ void FModelingToolsManagerCommands::RegisterCommands() UI_COMMAND(BeginSculptMeshTool, "Sculpt", "Start the Sculpt Mesh Tool", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(BeginPolyEditTool, "PolyEdit", "Start the PolyEdit Tool", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(BeginEdgeLoopInsertionTool, "EdgeLoopInsert", "Start the Edge Loop Insertion Tool", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(BeginTriEditTool, "TriEdit", "Start the Triangle Edit Tool", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(BeginPolyDeformTool, "PolyDeform", "Start the PolyDeform Tool", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(BeginSmoothMeshTool, "Smooth", "Start the Smooth Mesh Tool", EUserInterfaceActionType::Button, FInputChord()); @@ -82,6 +84,11 @@ void FModelingToolsManagerCommands::RegisterCommands() UI_COMMAND(BeginGroupUVGenerateTool, "GroupUnwrap", "Stat the PolyGroup UV Unwrap Tool", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(BeginMeshSelectionTool, "Select", "Start the Mesh Selection Tool", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(BeginPhysicsInspectorTool, "PhysInspect", "Inspect Physics Geometry for selected Meshes", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(BeginSetCollisionGeometryTool, "MeshToCollision", "Convert selected Meshes to Collision Geometry", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(BeginEditCollisionGeometryTool, "EditPhys", "Edit Collision Geometry for selected Mesh", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(BeginExtractCollisionGeometryTool, "CollisionToMesh", "Convert Collision Geometry to a Mesh", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(BeginMeshInspectorTool, "Inspector", "Start the Mesh Inspector Tool", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(BeginWeldEdgesTool, "Weld Edges", "Start the Weld Edges Tool", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(BeginPolyGroupsTool, "PolyGroups", "Start the PolyGroups Tool", EUserInterfaceActionType::Button, FInputChord()); @@ -94,6 +101,7 @@ void FModelingToolsManagerCommands::RegisterCommands() UI_COMMAND(AcceptActiveTool, "Accept", "Accept the active tool", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(CancelActiveTool, "Cancel", "Cancel the active tool", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(CompleteActiveTool, "Complete", "Complete the active tool", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(CancelOrCompleteActiveTool, "Cancel or Complete", "Cancel or complete the active tool", EUserInterfaceActionType::Button, FInputChord(EKeys::Escape)); } diff --git a/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Public/ModelingToolsActions.h b/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Public/ModelingToolsActions.h index 1e43eb0eb120..e7b02ebee295 100644 --- a/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Public/ModelingToolsActions.h +++ b/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Public/ModelingToolsActions.h @@ -82,4 +82,5 @@ DECLARE_TOOL_ACTION_COMMANDS(FDrawPolygonToolActionCommands); DECLARE_TOOL_ACTION_COMMANDS(FMeshSelectionToolActionCommands); DECLARE_TOOL_ACTION_COMMANDS(FMeshPlaneCutToolActionCommands); DECLARE_TOOL_ACTION_COMMANDS(FEditMeshPolygonsToolActionCommands); +DECLARE_TOOL_ACTION_COMMANDS(FDrawAndRevolveToolActionCommands); diff --git a/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Public/ModelingToolsManagerActions.h b/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Public/ModelingToolsManagerActions.h index 8649ac56abd3..17bb04598fa5 100644 --- a/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Public/ModelingToolsManagerActions.h +++ b/Engine/Plugins/Experimental/ModelingToolsEditorMode/Source/ModelingToolsEditorMode/Public/ModelingToolsManagerActions.h @@ -9,7 +9,7 @@ /** * TInteractiveToolCommands implementation for this module that provides standard Editor hotkey support */ -class FModelingToolsManagerCommands : public TCommands +class MODELINGTOOLSEDITORMODE_API FModelingToolsManagerCommands : public TCommands { public: FModelingToolsManagerCommands(); @@ -36,6 +36,7 @@ public: TSharedPtr BeginSculptMeshTool; TSharedPtr BeginPolyEditTool; + TSharedPtr BeginEdgeLoopInsertionTool; TSharedPtr BeginTriEditTool; TSharedPtr BeginPolyDeformTool; TSharedPtr BeginSmoothMeshTool; @@ -74,6 +75,11 @@ public: TSharedPtr BeginMeshToVolumeTool; TSharedPtr BeginVolumeToMeshTool; + TSharedPtr BeginPhysicsInspectorTool; + TSharedPtr BeginSetCollisionGeometryTool; + TSharedPtr BeginEditCollisionGeometryTool; + TSharedPtr BeginExtractCollisionGeometryTool; + TSharedPtr BeginMeshInspectorTool; TSharedPtr BeginGlobalUVGenerateTool; TSharedPtr BeginGroupUVGenerateTool; @@ -88,7 +94,7 @@ public: TSharedPtr AcceptActiveTool; TSharedPtr CancelActiveTool; TSharedPtr CompleteActiveTool; - + TSharedPtr CancelOrCompleteActiveTool; /** * Initialize commands diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/MotionTrailEditorMode.uplugin b/Engine/Plugins/Experimental/MotionTrailEditorMode/MotionTrailEditorMode.uplugin new file mode 100644 index 000000000000..4dd435f5d0e0 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/MotionTrailEditorMode.uplugin @@ -0,0 +1,30 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "MotionTrailEditorMode", + "Description": "Editor for motion trails", + "Category": "Other", + "CreatedBy": "Epic Games, Inc", + "CreatedByURL": "http://epicgames.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": false, + "IsBetaVersion": true, + "IsExperimentalVersion": false, + "Installed": false, + "Plugins": [ + { + "Name": "ControlRig", + "Enabled": true + } + ], + "Modules": [ + { + "Name": "MotionTrailEditorMode", + "Type": "Editor", + "LoadingPhase": "Default" + } + ] +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/MotionTrailEditorMode.Build.cs b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/MotionTrailEditorMode.Build.cs new file mode 100644 index 000000000000..318ee68616a9 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/MotionTrailEditorMode.Build.cs @@ -0,0 +1,48 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class MotionTrailEditorMode : ModuleRules +{ + public MotionTrailEditorMode(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "Slate", + "SlateCore", + "EditorStyle" + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "InputCore", + "UnrealEd", + "LevelEditor", + + "EditorFramework", + "EditorInteractiveToolsFramework", + "InteractiveToolsFramework", + "ViewportInteraction", + + "MovieScene", + "MovieSceneTracks", + "MovieSceneTools", + "Sequencer", + "LevelSequence", + + "ControlRig" + } + ); + } +} diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorMode.cpp b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorMode.cpp new file mode 100644 index 000000000000..4af6ecc0f4b8 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorMode.cpp @@ -0,0 +1,201 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MotionTrailEditorMode.h" +#include "MotionTrailEditorModeToolkit.h" +#include "MotionTrailEditorModeCommands.h" +#include "Toolkits/ToolkitManager.h" +#include "EditorModeManager.h" +#include "EdModeInteractiveToolsContext.h" + +#include "LevelEditorSequencerIntegration.h" +#include "ISequencer.h" +#include "SequencerTrailHierarchy.h" + +#include "MovieSceneTransformTrail.h" + + +#define LOCTEXT_NAMESPACE "MotionTrailEditorMode" + +DEFINE_LOG_CATEGORY(LogMotionTrailEditorMode); + +FName UMotionTrailEditorMode::MotionTrailEditorMode_Default = FName(TEXT("Default")); + +FString UMotionTrailEditorMode::DefaultToolName = TEXT("DefaultTool"); + +UMotionTrailEditorMode::UMotionTrailEditorMode() +{ + SettingsClass = UMotionTrailOptions::StaticClass(); + + Info = FEditorModeInfo( + FName(TEXT("MotionTrailEditorMode")), + LOCTEXT("ModeName", "Motion Trail Editor"), + FSlateIcon(), + false + ); +} + +UMotionTrailEditorMode::~UMotionTrailEditorMode() +{ + +} + +void UMotionTrailEditorMode::Enter() +{ + UEdMode::Enter(); + + TrailOptions = Cast(SettingsObject); + + // Add default tool + FMotionTrailEditorModeCommands::Register(); + TrailTools.Add(UMotionTrailEditorMode::DefaultToolName); + UTrailToolManagerBuilder* DefaultTrailToolManagerBuilder = NewObject(); + DefaultTrailToolManagerBuilder->SetMotionTrailEditorMode(this); + DefaultTrailToolManagerBuilder->SetTrailToolName(UMotionTrailEditorMode::DefaultToolName); + RegisterTool(FMotionTrailEditorModeCommands::Get().Default, UMotionTrailEditorMode::DefaultToolName, DefaultTrailToolManagerBuilder); + + OnSequencersChangedHandle = FLevelEditorSequencerIntegration::Get().GetOnSequencersChanged().AddLambda([this] { + TrailHierarchies.Reset(); + TrailTools[DefaultToolName].Reset(); + // TODO: kind of cheap for now, later should check with member TMap TrackedSequencers + for (TWeakPtr WeakSequencer : FLevelEditorSequencerIntegration::Get().GetSequencers()) + { + TrailHierarchies.Add_GetRef(MakeUnique(this, WeakSequencer))->Initialize(); + } + }); + + for (TWeakPtr WeakSequencer : FLevelEditorSequencerIntegration::Get().GetSequencers()) + { + TrailHierarchies.Add_GetRef(MakeUnique(this, WeakSequencer))->Initialize(); + } + + GetToolManager()->ConfigureChangeTrackingMode(EToolChangeTrackingMode::NoChangeTracking); + + ActivateDefaultTool(); +} + +void UMotionTrailEditorMode::Exit() +{ + for (TUniquePtr& TrailHierarchy : TrailHierarchies) + { + // TODO: just use dtor? + TrailHierarchy->Destroy(); + } + TrailHierarchies.Reset(); + TrailTools.Reset(); + + TrailOptions->OnDisplayPropertyChanged.Clear(); + + FLevelEditorSequencerIntegration::Get().GetOnSequencersChanged().Remove(OnSequencersChangedHandle); + + // Call base Exit method to ensure proper cleanup + Super::Exit(); +} + +void UMotionTrailEditorMode::CreateToolkit() +{ + if (!Toolkit.IsValid()) + { + FMotionTrailEditorModeToolkit* MotionTrailToolkit = new FMotionTrailEditorModeToolkit; + Toolkit = MakeShareable(MotionTrailToolkit); + Toolkit->Init(Owner->GetToolkitHost()); + } + Super::CreateToolkit(); +} + + +void UMotionTrailEditorMode::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) +{ + if (!TrailOptions->bShowTrails) + { + return; + } + + for (TUniquePtr& TrailHierarchy : TrailHierarchies) + { + TrailHierarchy->Update(); + } + + for (TUniquePtr& TrailHierarchy : TrailHierarchies) + { + TrailHierarchy->GetRenderer()->Render(View, Viewport, PDI); + } + + Super::Render(View, Viewport, PDI); +} + +void UMotionTrailEditorMode::DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) +{ + for (TUniquePtr& TrailHierarchy : TrailHierarchies) + { + TrailHierarchy->GetRenderer()->DrawHUD(ViewportClient, Viewport, View, Canvas); + } + + Super::DrawHUD(ViewportClient, Viewport, View, Canvas); +} + +bool UMotionTrailEditorMode::UsesToolkits() const +{ + return true; +} + +TMap>> UMotionTrailEditorMode::GetModeCommands() const +{ + const TMap>>& Commands = FMotionTrailEditorModeCommands::Get().GetCommands(); + if (Commands.Num() > 1) + { + return Commands; + } + + return TMap>>(); +} + +void UMotionTrailEditorMode::AddTrailTool(const FString& ToolType, FInteractiveTrailTool* TrailTool) +{ + checkf(ToolType.Equals(DefaultToolName), TEXT("Only default tool supported for now")) + + TrailTools[ToolType].Add(TrailTool); + if (UTrailToolManager* ToolManager = Cast(GetToolManager()->GetActiveTool(EToolSide::Mouse))) + { + TrailTool->SetMotionTrailEditorMode(this); + TrailTool->Setup(); + } +} + +void UMotionTrailEditorMode::RemoveTrailTool(const FString& ToolType, FInteractiveTrailTool* TrailTool) +{ + TrailTools[ToolType].Remove(TrailTool); +} + +void UMotionTrailEditorMode::RefreshNonDefaultToolset() +{ + TArray> NewNonDefaultCommands; + for (const TPair>& ToolPair : TrailTools) + { + if (ToolPair.Key.Equals(UMotionTrailEditorMode::DefaultToolName)) + { + continue; + } + + TSharedPtr NewUICommand = (*ToolPair.Value.CreateConstIterator())->GetStaticUICommandInfo(); + NewNonDefaultCommands.Add(NewUICommand); + + UTrailToolManagerBuilder* NewTrailToolManagerBuilder = NewObject(); + NewTrailToolManagerBuilder->SetMotionTrailEditorMode(this); + NewTrailToolManagerBuilder->SetTrailToolName(ToolPair.Key); + RegisterTool(NewUICommand, ToolPair.Key, NewTrailToolManagerBuilder); + + } + FMotionTrailEditorModeCommands::RegisterDynamic("Curve Specific Tools", NewNonDefaultCommands); +} + +void UMotionTrailEditorMode::ActivateDefaultTool() +{ + ToolsContext->StartTool(UMotionTrailEditorMode::DefaultToolName); +} + +bool UMotionTrailEditorMode::IsCompatibleWith(FEditorModeID OtherModeID) const +{ + return OtherModeID == TEXT("EM_SequencerMode") || OtherModeID == TEXT("EditMode.ControlRig"); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorModeCommands.cpp b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorModeCommands.cpp new file mode 100644 index 000000000000..0d33a8c6f2a3 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorModeCommands.cpp @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MotionTrailEditorModeCommands.h" +#include "MotionTrailEditorMode.h" + + +#define LOCTEXT_NAMESPACE "MotionTrailEditorModeCommands" + +void FMotionTrailEditorModeCommands::RegisterCommands() +{ + UI_COMMAND(Default, "Default", "Default trail editing tool", EUserInterfaceActionType::ToggleButton, FInputChord()); + Commands.Add(UMotionTrailEditorMode::MotionTrailEditorMode_Default, { Default }); +} + +void FMotionTrailEditorModeCommands::RegisterDynamic(const FName InName, const TArray>& InCommands) +{ + Instance.Pin()->Commands.Add(InName, InCommands); + Instance.Pin()->CommandsChanged.Broadcast(*(Instance.Pin())); +} + +void FMotionTrailEditorModeCommands::UnRegisterDynamic(const FName InName) +{ + Instance.Pin()->Commands.Remove(InName); + Instance.Pin()->CommandsChanged.Broadcast(*(Instance.Pin())); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorModeModule.cpp b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorModeModule.cpp new file mode 100644 index 000000000000..df844865ff6d --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorModeModule.cpp @@ -0,0 +1,48 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MotionTrailEditorModeModule.h" +#include "MotionTrailEditorMode.h" +#include "EditorModeManager.h" + +#include "ISequencerModule.h" +#include "Tracks/MovieScene3DTransformTrack.h" +#include "UnrealEdGlobals.h" +#include "Editor.h" +#include "Sequencer/MovieSceneControlRigParameterTrack.h" + +#define LOCTEXT_NAMESPACE "FMotionTrailEditorModeModule" + +void FMotionTrailEditorModeModule::StartupModule() +{ + ISequencerModule& SequencerModule = FModuleManager::Get().LoadModuleChecked("Sequencer"); + OnSequencerCreatedHandle = SequencerModule.RegisterOnSequencerCreated(FOnSequencerCreated::FDelegate::CreateRaw(this, &FMotionTrailEditorModeModule::OnSequencerCreated)); +} + +void FMotionTrailEditorModeModule::ShutdownModule() +{ + ISequencerModule* SequencerModule = FModuleManager::GetModulePtr("Sequencer"); + if (SequencerModule) + { + SequencerModule->UnregisterOnSequencerCreated(OnSequencerCreatedHandle); + } +} + +void FMotionTrailEditorModeModule::OnSequencerCreated(TSharedRef Sequencer) +{ + Sequencer->GetSelectionChangedTracks().AddLambda([](TArray SelectedTracks) { + for (UMovieSceneTrack* Track : SelectedTracks) + { + if (Track->GetClass() == UMovieScene3DTransformTrack::StaticClass() || Track->GetClass() == UMovieSceneControlRigParameterTrack::StaticClass()) + { + if (!GLevelEditorModeTools().IsModeActive(FName(TEXT("MotionTrailEditorMode")))) + { + //GLevelEditorModeTools().ActivateMode(FName(TEXT("MotionTrailEditorMode"))); + } + } + } + }); +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FMotionTrailEditorModeModule, MotionTrailEditorMode) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorModeToolkit.cpp b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorModeToolkit.cpp new file mode 100644 index 000000000000..678375672f03 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorModeToolkit.cpp @@ -0,0 +1,41 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MotionTrailEditorModeToolkit.h" +#include "MotionTrailEditorMode.h" +#include "Engine/Selection.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Text/STextBlock.h" +#include "EditorModeManager.h" + +#include "Modules/ModuleManager.h" +#include "PropertyEditorModule.h" +#include "IDetailsView.h" +#include "IDetailRootObjectCustomization.h" + +#define LOCTEXT_NAMESPACE "FMotionTrailEditorModeEdModeToolkit" + +FMotionTrailEditorModeToolkit::FMotionTrailEditorModeToolkit() +{ +} + +void FMotionTrailEditorModeToolkit::Init(const TSharedPtr& InitToolkitHost) +{ + FModeToolkit::Init(InitToolkitHost); +} + +FName FMotionTrailEditorModeToolkit::GetToolkitFName() const +{ + return FName("MotionTrailEditorMode"); +} + +FText FMotionTrailEditorModeToolkit::GetBaseToolkitName() const +{ + return NSLOCTEXT("MotionTrailEditorModeToolkit", "DisplayName", "Motion Trail Editor Tool"); +} + +class UEdMode* FMotionTrailEditorModeToolkit::GetScriptableEditorMode() const +{ + return GLevelEditorModeTools().GetActiveScriptableMode("MotionTrailEditorMode"); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorToolset.cpp b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorToolset.cpp new file mode 100644 index 000000000000..2e4d552b5bda --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MotionTrailEditorToolset.cpp @@ -0,0 +1,148 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MotionTrailEditorToolset.h" +#include "MotionTrailEditorMode.h" + +#include "BaseGizmos/TransformGizmo.h" +#include "InteractiveGizmoManager.h" + +#include "BaseBehaviors/SingleClickBehavior.h" +#include "BaseBehaviors/ClickDragBehavior.h" + +#define LOCTEXT_NAMESPACE "MotionTrailEditorToolset" + +UInteractiveTool* UTrailToolManagerBuilder::BuildTool(const FToolBuilderState& SceneState) const +{ + UTrailToolManager* NewTool = NewObject(SceneState.ToolManager); + NewTool->SetTrailToolName(TrailToolName); + NewTool->SetMotionTrailEditorMode(EditorMode); + NewTool->SetWorld(SceneState.World, SceneState.GizmoManager); + return NewTool; +} + +FString UTrailToolManager::TrailKeyTransformGizmoInstanceIdentifier = TEXT("TrailKeyTransformGizmoInstanceIdentifier"); + +FInputRayHit UTrailToolManager::IsHitByClick(const FInputDeviceRay& ClickPos) +{ + FInputRayHit ReturnHit = FInputRayHit(); + for (FInteractiveTrailTool* TrailTool : EditorMode->GetTrailTools()[TrailToolName]) + { + FInputRayHit TestHit = TrailTool->IsHitByClick(ClickPos); + if (TestHit.bHit) + { + ReturnHit = TestHit; + } + } + + return ReturnHit; +} + +void UTrailToolManager::OnClicked(const FInputDeviceRay& ClickPos) +{ + for (FInteractiveTrailTool* TrailTool : EditorMode->GetTrailTools()[TrailToolName]) + { + TrailTool->OnClicked(ClickPos); + } +} + +FInputRayHit UTrailToolManager::CanBeginClickDragSequence(const FInputDeviceRay& PressPos) +{ + FInputRayHit ReturnHit = FInputRayHit(); + for (FInteractiveTrailTool* TrailTool : EditorMode->GetTrailTools()[TrailToolName]) + { + FInputRayHit TestHit = TrailTool->CanBeginClickDragSequence(PressPos); + if (TestHit.bHit) + { + ReturnHit = TestHit; + } + } + return ReturnHit; +} + +void UTrailToolManager::OnClickPress(const FInputDeviceRay& PressPos) +{ + for (FInteractiveTrailTool* TrailTool : EditorMode->GetTrailTools()[TrailToolName]) + { + TrailTool->OnClickPress(PressPos); + } +} + +void UTrailToolManager::OnClickDrag(const FInputDeviceRay& DragPos) +{ + for (FInteractiveTrailTool* TrailTool : EditorMode->GetTrailTools()[TrailToolName]) + { + TrailTool->OnClickDrag(DragPos); + } +} + +void UTrailToolManager::OnClickRelease(const FInputDeviceRay& ReleasePos) +{ + for (FInteractiveTrailTool* TrailTool : EditorMode->GetTrailTools()[TrailToolName]) + { + TrailTool->OnClickRelease(ReleasePos); + } +} + +void UTrailToolManager::OnTerminateDragSequence() +{ + for (FInteractiveTrailTool* TrailTool : EditorMode->GetTrailTools()[TrailToolName]) + { + TrailTool->OnTerminateDragSequence(); + } +} + +void UTrailToolManager::Setup() +{ + UInteractiveTool::Setup(); + + // add default button input behaviors for devices + USingleClickInputBehavior* MouseBehavior = NewObject(this); + MouseBehavior->Initialize(this); + AddInputBehavior(MouseBehavior); + + UClickDragInputBehavior* ClickDragBehavior = NewObject(this); + ClickDragBehavior->Initialize(this); + AddInputBehavior(ClickDragBehavior); + + for (FInteractiveTrailTool* TrailTool : EditorMode->GetTrailTools()[TrailToolName]) + { + TrailTool->SetMotionTrailEditorMode(EditorMode); + TrailTool->Setup(); + } + + if (EditorMode->GetTrailTools()[TrailToolName].Num() > 0) + { + ToolProperties = (*EditorMode->GetTrailTools()[TrailToolName].CreateConstIterator())->GetStaticToolProperties(); + } +} + +void UTrailToolManager::Shutdown(EToolShutdownType ShutdownType) +{ + for (FInteractiveTrailTool* TrailTool : EditorMode->GetTrailTools()[TrailToolName]) + { + TrailTool->SetMotionTrailEditorMode(nullptr); + } +} + +void UTrailToolManager::Render(IToolsContextRenderAPI* RenderAPI) +{ + for (FInteractiveTrailTool* TrailTool : EditorMode->GetTrailTools()[TrailToolName]) + { + TrailTool->Render(RenderAPI); + } +} + +void UTrailToolManager::OnTick(float DeltaTime) +{ + for (FInteractiveTrailTool* TrailTool : EditorMode->GetTrailTools()[TrailToolName]) + { + TrailTool->Tick(DeltaTime); + } +} + +TArray UTrailToolManager::GetToolProperties(bool bEnabledOnly) const +{ + return EditorMode->GetTrailTools()[TrailToolName].Num() > 0 ? (*EditorMode->GetTrailTools()[TrailToolName].CreateConstIterator())->GetStaticToolProperties() : TArray(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MovieSceneTransformTrail.cpp b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MovieSceneTransformTrail.cpp new file mode 100644 index 000000000000..a676d2a4898f --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MovieSceneTransformTrail.cpp @@ -0,0 +1,588 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MovieSceneTransformTrail.h" +#include "TrailHierarchy.h" +#include "MotionTrailEditorMode.h" + +#include "ISequencer.h" +#include "Tracks/MovieScene3DTransformTrack.h" +#include "Sections/MovieScene3DTransformSection.h" +#include "Channels/MovieSceneFloatChannel.h" +#include "Channels/MovieSceneChannelProxy.h" +#include "EntitySystem/Interrogation/MovieSceneInterrogationLinker.h" +#include "Systems/MovieSceneComponentTransformSystem.h" +#include "MovieSceneSequence.h" + +#include "BaseGizmos/GizmoComponents.h" +#include "BaseGizmos/TransformGizmo.h" + +#include "ViewportWorldInteraction.h" +#include "GameFramework/Actor.h" +#include "Components/SceneComponent.h" + +UMSTrailKeyProperties* FDefaultMovieSceneTransformTrailTool::KeyProps = nullptr; + +void FDefaultMovieSceneTransformTrailTool::Setup() +{ + if (!KeyProps) + { + KeyProps = NewObject(); + } + BuildKeys(); +} + +void FDefaultMovieSceneTransformTrailTool::Render(IToolsContextRenderAPI* RenderAPI) +{ + if (!OwningTrail->GetDrawInfo()->IsVisible()) + { + if (ActiveTransformGizmo.IsValid() && Cast(ActiveTransformGizmo->ActiveTarget)) + { + ClearSelection(); + } + return; + } + + FEditorViewportClient* EditorViewportClient = StaticCast(GEditor->GetActiveViewport()->GetClient()); + if (!EditorViewportClient) + { + return; + } + + FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(EditorViewportClient->Viewport, EditorViewportClient->GetScene(), EditorViewportClient->EngineShowFlags)); + FSceneView* SceneView = EditorViewportClient->CalcSceneView(&ViewFamily); + FTrailScreenSpaceTransform ScreenSpaceTransform = FTrailScreenSpaceTransform(SceneView, GEditor->GetActiveViewport(), EditorViewportClient->GetDPIScale()); + + for (const TPair>& FrameKeyPair : Keys) + { + if (OwningTrail->GetDrawInfo()->GetCachedViewRange().Contains(OwningTrail->WeakSequencer.Pin()->GetFocusedTickResolution().AsSeconds(FrameKeyPair.Value->FrameNumber))) + { + RenderAPI->GetPrimitiveDrawInterface()->DrawPoint(FrameKeyPair.Value->SceneComponent->GetComponentLocation(), FLinearColor::Gray, KeyProps->KeySize, SDPG_Foreground); + } + } +} + +FInputRayHit FDefaultMovieSceneTransformTrailTool::IsHitByClick(const FInputDeviceRay& ClickPos) +{ + if (!OwningTrail->GetDrawInfo()->IsVisible()) + { + return FInputRayHit(); + } + + FEditorViewportClient* EditorViewportClient = StaticCast(GEditor->GetActiveViewport()->GetClient()); + if (!EditorViewportClient) + { + return FInputRayHit(); + } + + FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(EditorViewportClient->Viewport, EditorViewportClient->GetScene(), EditorViewportClient->EngineShowFlags)); + FSceneView* SceneView = EditorViewportClient->CalcSceneView(&ViewFamily); + FTrailScreenSpaceTransform ScreenSpaceTransform = FTrailScreenSpaceTransform(SceneView, GEditor->GetActiveViewport(), EditorViewportClient->GetDPIScale()); + + const FVector2D RayProjectedPos = ScreenSpaceTransform.ProjectPoint(ClickPos.WorldRay.PointAt(1.0f)).GetValue(); + + CachedSelected = nullptr; + float MinHitDistance = TNumericLimits::Max(); + for (const TPair>& FrameKeyPair : Keys) + { + if (OwningTrail->GetDrawInfo()->GetCachedViewRange().Contains(OwningTrail->WeakSequencer.Pin()->GetFocusedTickResolution().AsSeconds(FrameKeyPair.Value->FrameNumber))) + { + TOptional KeyProjectedPos = ScreenSpaceTransform.ProjectPoint(FrameKeyPair.Value->SceneComponent->GetComponentLocation()); + + if (KeyProjectedPos && FVector2D::Distance(KeyProjectedPos.GetValue(), RayProjectedPos) < KeyProps->KeySize) + { + const float HitDistance = ClickPos.WorldRay.GetParameter(FrameKeyPair.Value->SceneComponent->GetComponentLocation()); + if (HitDistance < MinHitDistance) + { + MinHitDistance = HitDistance; + CachedSelected = FrameKeyPair.Value.Get(); + } + } + } + } + + return (MinHitDistance < TNumericLimits::Max()) ? FInputRayHit(MinHitDistance) : FInputRayHit(); +} + +void FDefaultMovieSceneTransformTrailTool::OnClicked(const FInputDeviceRay& ClickPos) +{ + UTrailToolManager* TrailToolManager = Cast(WeakEditorMode->GetToolManager()->GetActiveTool(EToolSide::Mouse)); + if (!OwningTrail->GetDrawInfo()->IsVisible() || !TrailToolManager) + { + return; + } + + if (CachedSelected) + { + ActiveTransformGizmo = Cast(TrailToolManager->GetGizmoManager()->FindGizmoByInstanceIdentifier(UTrailToolManager::TrailKeyTransformGizmoInstanceIdentifier)); + + UMSTrailTransformProxy* MSTrailTransformProxy; + if (ActiveTransformGizmo.IsValid() && Cast(Cast(ActiveTransformGizmo)->ActiveTarget)) + { + MSTrailTransformProxy = Cast(ActiveTransformGizmo->ActiveTarget); + + if (!FSlateApplication::Get().GetModifierKeys().IsShiftDown()) + { + MSTrailTransformProxy = NewObject(TrailToolManager); + } + } + else + { + MSTrailTransformProxy = NewObject(TrailToolManager); + MSTrailTransformProxy->bRotatePerObject = true; + + ETransformGizmoSubElements GizmoElements = ETransformGizmoSubElements::TranslateRotateUniformScale; + ActiveTransformGizmo = TrailToolManager->GetGizmoManager()->CreateCustomTransformGizmo(GizmoElements, TrailToolManager, UTrailToolManager::TrailKeyTransformGizmoInstanceIdentifier); + } + + if (MSTrailTransformProxy->GetKeysTracked().Contains(CachedSelected)) + { + MSTrailTransformProxy->RemoveKey(CachedSelected); + } + else + { + MSTrailTransformProxy->AddKey(CachedSelected); + } + + if (MSTrailTransformProxy->IsEmpty()) + { + TrailToolManager->GetGizmoManager()->DestroyGizmo(ActiveTransformGizmo.Get()); + return; + } + + // re-create actor, TODO: re-initialize actor? ActiveTransformGizmo->Initialize() + for (const TPair& SelectedKeyInfo : MSTrailTransformProxy->GetKeysTracked()) + { + UpdateGizmoActorComponents(SelectedKeyInfo.Key, ActiveTransformGizmo.Get()); + } + + ActiveTransformGizmo->SetActiveTarget(MSTrailTransformProxy); + } +} + +void FDefaultMovieSceneTransformTrailTool::OnSectionChanged() +{ + if (ShouldRebuildKeys()) + { + ClearSelection(); + BuildKeys(); + } + + DirtyKeyTransforms(); +} + +void FDefaultMovieSceneTransformTrailTool::BuildKeys() +{ + check(WeakEditorMode.IsValid()); + UTrailToolManager* TrailToolManager = Cast(WeakEditorMode->GetToolManager()->GetActiveTool(EToolSide::Mouse)); + if (!TrailToolManager) + { + return; + } + + Keys.Reset(); + + UMovieScene3DTransformSection* AbsoluteTransformSection = OwningTrail->GetTransformSection(); + TArrayView FloatChannels = AbsoluteTransformSection->GetChannelProxy().GetChannels(); + for (FMovieSceneFloatChannel* FloatChannel : FloatChannels) + { + for (int32 Idx = 0; Idx < FloatChannel->GetNumKeys(); Idx++) + { + const FFrameNumber CurTime = FloatChannel->GetTimes()[Idx]; + + if (!Keys.Contains(CurTime)) + { + TUniquePtr TempKeyInfo = MakeUnique(CurTime, AbsoluteTransformSection, OwningTrail); + Keys.Add(CurTime, MoveTemp(TempKeyInfo)); + } + } + } +} + +bool FDefaultMovieSceneTransformTrailTool::ShouldRebuildKeys() +{ + TMap> KeyTimes; + TArrayView FloatChannels = OwningTrail->GetTransformSection()->GetChannelProxy().GetChannels(); + + for (uint8 ChannelIdx = 0; ChannelIdx <= uint8(EMSTrailTransformChannel::MaxChannel); ChannelIdx++) + { + FMovieSceneFloatChannel* FloatChannel = FloatChannels[ChannelIdx]; + for (int32 Idx = 0; Idx < FloatChannel->GetNumKeys(); Idx++) + { + const FFrameNumber CurTime = FloatChannel->GetTimes()[Idx]; + KeyTimes.FindOrAdd(CurTime).Add(EMSTrailTransformChannel(ChannelIdx)); + } + } + + if (KeyTimes.Num() != Keys.Num()) + { + return true; + } + + for (const TPair>& TimeKeyPair : KeyTimes) + { + if (!Keys.Contains(TimeKeyPair.Key)) + { + return true; + } + for (uint8 ChannelIdx = 0; ChannelIdx <= uint8(EMSTrailTransformChannel::MaxChannel); ChannelIdx++) + { + const EMSTrailTransformChannel TransformChannel = EMSTrailTransformChannel(ChannelIdx); + if ((!TimeKeyPair.Value.Contains(TransformChannel) && Keys[TimeKeyPair.Key]->IdxMap.Contains(TransformChannel)) || + (TimeKeyPair.Value.Contains(TransformChannel) && !Keys[TimeKeyPair.Key]->IdxMap.Contains(TransformChannel))) + { + return true; + } + } + } + + return false; +} + +void FDefaultMovieSceneTransformTrailTool::ClearSelection() +{ + check(WeakEditorMode.IsValid()); + UTrailToolManager* TrailToolManager = Cast(WeakEditorMode->GetToolManager()->GetActiveTool(EToolSide::Mouse)); + if (ActiveTransformGizmo.IsValid() && TrailToolManager) + { + UMSTrailTransformProxy* MSTrailTransformProxy = Cast(ActiveTransformGizmo->ActiveTarget); + if (MSTrailTransformProxy) + { + for (const TPair>& KeyInfoPair : Keys) + { + if (MSTrailTransformProxy->GetKeysTracked().Contains(KeyInfoPair.Value.Get())) + { + MSTrailTransformProxy->RemoveKey(KeyInfoPair.Value.Get()); + } + } + + if (MSTrailTransformProxy->IsEmpty()) + { + TrailToolManager->GetGizmoManager()->DestroyGizmo(ActiveTransformGizmo.Get()); + } + } + } + + ActiveTransformGizmo = nullptr; +} + +void FDefaultMovieSceneTransformTrailTool::DirtyKeyTransforms() +{ + for (const TPair>& FrameKeyPair : Keys) + { + FrameKeyPair.Value->bDirty = true; + } +} + +void FDefaultMovieSceneTransformTrailTool::UpdateKeysInRange(FTrajectoryCache* ParentTrajectoryCache, const TRange& ViewRange) +{ + for (const TPair>& FrameKeyPair : Keys) + { + const double EvalTime = OwningTrail->GetSequencer()->GetFocusedTickResolution().AsSeconds(FrameKeyPair.Value->FrameNumber); + if (FrameKeyPair.Value->bDirty && ViewRange.Contains(EvalTime)) + { + FrameKeyPair.Value->UpdateKeyTransform(EKeyUpdateType::FromTrailCache, ParentTrajectoryCache); + } + } +} + +FDefaultMovieSceneTransformTrailTool::FKeyInfo::FKeyInfo(const FFrameNumber InFrameNumber, UMovieScene3DTransformSection* InTrackSection, FMovieSceneTransformTrail* InOwningTrail) + : SceneComponent(NewObject()) + , ParentSceneComponent(NewObject()) + , IdxMap() + , DragStartTransform() + , FrameNumber(InFrameNumber) + , bDirty(true) + , TrackSection(InTrackSection) + , OwningTrail(InOwningTrail) +{ + TArrayView Channels = InTrackSection->GetChannelProxy().GetChannels(); + for (uint8 Idx = 0; Idx <= uint8(EMSTrailTransformChannel::MaxChannel); Idx++) + { + const int32 FoundIdx = Channels[Idx]->GetData().FindKey(InFrameNumber); + if (FoundIdx != INDEX_NONE) + { + IdxMap.Add(EMSTrailTransformChannel(Idx), Channels[Idx]->GetData().GetHandle(FoundIdx)); + } + } + + SceneComponent->AttachToComponent(ParentSceneComponent,FAttachmentTransformRules::KeepRelativeTransform); +} + +void FDefaultMovieSceneTransformTrailTool::FKeyInfo::OnDragStart(class UTransformProxy*) +{ + TArrayView Channels = TrackSection->GetChannelProxy().GetChannels(); + for(const TPair& ChannelHandlePair : IdxMap) + { + const int32 KeyIdx = Channels[uint8(ChannelHandlePair.Key)]->GetData().GetIndex(ChannelHandlePair.Value); + DragStartTransform.Add(ChannelHandlePair.Key, Channels[uint8(ChannelHandlePair.Key)]->GetData().GetValues()[KeyIdx].Value); + } + DragStartCompTransform = UE::MovieScene::FIntermediate3DTransform(SceneComponent->GetRelativeLocation(), SceneComponent->GetRelativeRotation(), SceneComponent->GetRelativeScale3D()); +} + +void FDefaultMovieSceneTransformTrailTool::FKeyInfo::UpdateKeyTransform(EKeyUpdateType UpdateType, FTrajectoryCache* ParentTrajectoryCache) +{ + bDirty = false; + TArrayView Channels = TrackSection->GetChannelProxy().GetChannels(); + if (UpdateType == EKeyUpdateType::FromComponentDelta) + { + UE::MovieScene::FIntermediate3DTransform RelativeTransform = UE::MovieScene::FIntermediate3DTransform( + SceneComponent->GetRelativeLocation() - DragStartCompTransform->GetTranslation(), + SceneComponent->GetRelativeRotation() - DragStartCompTransform->GetRotation(), + SceneComponent->GetRelativeScale3D() / DragStartCompTransform->GetScale() + ); + + OwningTrail->ForceEvaluateNextTick(); + TrackSection->Modify(); + + auto TryUpdateChannel = [this, &RelativeTransform, &Channels](const EMSTrailTransformChannel Channel) { + if (IdxMap.Contains(Channel)) + { + const int32 KeyIdx = Channels[uint8(Channel)]->GetData().GetIndex(IdxMap[Channel]); + Channels[uint8(Channel)]->GetData().GetValues()[KeyIdx].Value = + DragStartTransform[Channel] + RelativeTransform[uint8(Channel)]; + } + }; + + auto TryUpdateScaleChannel = [this, &RelativeTransform, &Channels](const EMSTrailTransformChannel Channel) { + if (IdxMap.Contains(Channel)) + { + const int32 KeyIdx = Channels[uint8(Channel)]->GetData().GetIndex(IdxMap[Channel]); + Channels[uint8(Channel)]->GetData().GetValues()[KeyIdx].Value = + DragStartTransform[Channel] * RelativeTransform[uint8(Channel)]; + } + }; + + TryUpdateChannel(EMSTrailTransformChannel::TranslateX); + TryUpdateChannel(EMSTrailTransformChannel::TranslateY); + TryUpdateChannel(EMSTrailTransformChannel::TranslateZ); + TryUpdateChannel(EMSTrailTransformChannel::RotateX); + TryUpdateChannel(EMSTrailTransformChannel::RotateY); + TryUpdateChannel(EMSTrailTransformChannel::RotateZ); + TryUpdateScaleChannel(EMSTrailTransformChannel::ScaleX); + TryUpdateScaleChannel(EMSTrailTransformChannel::ScaleY); + TryUpdateScaleChannel(EMSTrailTransformChannel::ScaleZ); + } + else if(UpdateType == EKeyUpdateType::FromTrailCache) + { + const double EvalTime = OwningTrail->GetSequencer()->GetFocusedTickResolution().AsSeconds(FrameNumber); + + if (ParentTrajectoryCache) + { + const FTransform ParentTransform = ParentTrajectoryCache->GetInterp(EvalTime); + ParentSceneComponent->SetWorldTransform(ParentTransform); + } + + const FTransform TempTransform = OwningTrail->GetTrajectoryTransforms()->GetInterp(EvalTime); + SceneComponent->SetWorldTransform(TempTransform); + SceneComponent->SetWorldRotation(FQuat::Identity); + SceneComponent->SetWorldScale3D(FVector::OneVector); + } +} + +void FDefaultMovieSceneTransformTrailTool::UpdateGizmoActorComponents(FKeyInfo* KeyInfo, UTransformGizmo* TransformGizmo) +{ + if (!KeyInfo->IdxMap.Contains(EMSTrailTransformChannel::TranslateX)) + { + TransformGizmo->GetGizmoActor()->TranslateX = nullptr; + TransformGizmo->GetGizmoActor()->TranslateXY = nullptr; + TransformGizmo->GetGizmoActor()->TranslateXZ = nullptr; + } + if (!KeyInfo->IdxMap.Contains(EMSTrailTransformChannel::TranslateY)) + { + TransformGizmo->GetGizmoActor()->TranslateY = nullptr; + TransformGizmo->GetGizmoActor()->TranslateXY = nullptr; + TransformGizmo->GetGizmoActor()->TranslateYZ = nullptr; + } + if (!KeyInfo->IdxMap.Contains(EMSTrailTransformChannel::TranslateZ)) + { + TransformGizmo->GetGizmoActor()->TranslateZ = nullptr; + TransformGizmo->GetGizmoActor()->TranslateXZ = nullptr; + TransformGizmo->GetGizmoActor()->TranslateYZ = nullptr; + } + + if (!KeyInfo->IdxMap.Contains(EMSTrailTransformChannel::RotateX)) + { + TransformGizmo->GetGizmoActor()->RotateX = nullptr; + } + if (!KeyInfo->IdxMap.Contains(EMSTrailTransformChannel::RotateY)) + { + TransformGizmo->GetGizmoActor()->RotateY = nullptr; + } + if (!KeyInfo->IdxMap.Contains(EMSTrailTransformChannel::RotateZ)) + { + TransformGizmo->GetGizmoActor()->RotateZ = nullptr; + } + + if (!KeyInfo->IdxMap.Contains(EMSTrailTransformChannel::ScaleX)) + { + TransformGizmo->GetGizmoActor()->AxisScaleX = nullptr; + } + if (!KeyInfo->IdxMap.Contains(EMSTrailTransformChannel::ScaleY)) + { + TransformGizmo->GetGizmoActor()->AxisScaleY = nullptr; + } + if (!KeyInfo->IdxMap.Contains(EMSTrailTransformChannel::ScaleZ)) + { + TransformGizmo->GetGizmoActor()->AxisScaleZ = nullptr; + } +} + +FMovieSceneTransformTrail::FMovieSceneTransformTrail(const FLinearColor& InColor, const bool bInIsVisible, TWeakObjectPtr InWeakTrack, TSharedPtr InSequencer) + : FTrail() + , CachedEffectiveRange(TRange::Empty()) + , DefaultTrailTool() + , DrawInfo() + , TrajectoryCache() + , LastTransformTrackSig(InWeakTrack->GetSignature()) + , WeakTrack(InWeakTrack) + , WeakSequencer(InSequencer) +{ + DefaultTrailTool = MakeUnique(this); + TrajectoryCache = MakeUnique(0.01, GetEffectiveTrackRange()); + DrawInfo = MakeUnique(InColor, bInIsVisible, TrajectoryCache.Get()); + + Interrogator = MakeUnique(); + Interrogator->ImportTrack(WeakTrack.Get(), UE::MovieScene::FInterrogationChannel::Default()); +} + +ETrailCacheState FMovieSceneTransformTrail::UpdateTrail(const FSceneContext& InSceneContext) +{ + UMovieScene3DTransformTrack* Track = WeakTrack.Get(); + TSharedPtr Sequencer = WeakSequencer.Pin(); + + FGuid SequencerBinding; + if (Sequencer) + { // TODO: expensive, but for some reason Track stays alive even after it is deleted + Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene()->FindTrackBinding(*Track, SequencerBinding); + } + + checkf(InSceneContext.TrailHierarchy->GetHierarchy()[InSceneContext.YourNode].Parents.Num() == 1, TEXT("MovieSceneTransformTrails only support one parent")); + const FGuid ParentGuid = InSceneContext.TrailHierarchy->GetHierarchy()[InSceneContext.YourNode].Parents[0]; + const TUniquePtr& Parent = InSceneContext.TrailHierarchy->GetAllTrails()[ParentGuid]; + + ETrailCacheState ParentCacheState = InSceneContext.ParentCacheStates[ParentGuid]; + + if (!Sequencer || !SequencerBinding.IsValid() || (ParentCacheState == ETrailCacheState::Dead)) + { + return ETrailCacheState::Dead; + } + + const bool bTrackUnchanged = Track->GetSignature() == LastTransformTrackSig; + const bool bParentChanged = ParentCacheState != ETrailCacheState::UpToDate; + + ETrailCacheState CacheState; + FTrailEvaluateTimes TempEvalTimes = InSceneContext.EvalTimes; + TArrayView Channels = GetTransformSection()->GetChannelProxy().GetChannels(); + if (!bTrackUnchanged || bParentChanged || bForceEvaluateNextTick) + { + if (DefaultTrailTool->IsActive()) + { + DefaultTrailTool->OnSectionChanged(); + } + + const double Spacing = InSceneContext.EvalTimes.Spacing.Get(InSceneContext.TrailHierarchy->GetEditorMode()->GetTrailOptions()->SecondsPerSegment); + CachedEffectiveRange = TRange::Hull({ Parent->GetEffectiveRange(), GetEffectiveTrackRange() }); + *TrajectoryCache = FArrayTrajectoryCache(Spacing, CachedEffectiveRange, FTransform::Identity * Parent->GetTrajectoryTransforms()->GetDefault()); // TODO:: Get channel default values + TrajectoryCache->UpdateCacheTimes(TempEvalTimes); + + CacheState = ETrailCacheState::Stale; + bForceEvaluateNextTick = false; + LastTransformTrackSig = Track->GetSignature(); + } + else + { + TrajectoryCache->UpdateCacheTimes(TempEvalTimes); + + CacheState = ETrailCacheState::UpToDate; + } + + if (TempEvalTimes.EvalTimes.Num() > 0) + { + // TODO: re-populating the interrogator every frame is kind of inefficient + Interrogator->ImportTrack(WeakTrack.Get(), UE::MovieScene::FInterrogationChannel::Default()); + + for (const double Time : TempEvalTimes.EvalTimes) + { + const FFrameTime TickTime = Time * Sequencer->GetFocusedTickResolution(); + Interrogator->AddInterrogation(TickTime); + } + + Interrogator->Update(); + + TArray TempLocalTransforms; + Interrogator->QueryLocalSpaceTransforms(UE::MovieScene::FInterrogationChannel::Default(), TempLocalTransforms); + + for (int32 Idx = 0; Idx < TempEvalTimes.EvalTimes.Num(); Idx++) + { + const FTransform TempLocalTransform = FTransform(TempLocalTransforms[Idx].GetRotation(), TempLocalTransforms[Idx].GetTranslation(), TempLocalTransforms[Idx].GetScale()); + FTransform TempWorldTransform = TempLocalTransform * Parent->GetTrajectoryTransforms()->Get(TempEvalTimes.EvalTimes[Idx]); + TempWorldTransform.NormalizeRotation(); + TrajectoryCache->Set(TempEvalTimes.EvalTimes[Idx] + KINDA_SMALL_NUMBER, TempWorldTransform); + } + + Interrogator->Reset(); + } + + if (DefaultTrailTool->IsActive()) + { + DefaultTrailTool->UpdateKeysInRange(Parent->GetTrajectoryTransforms(), InSceneContext.EvalTimes.Range); + } + + return CacheState; +} + +TMap FMovieSceneTransformTrail::GetTools() +{ + TMap TempToolMap; + TempToolMap.Add(UMotionTrailEditorMode::DefaultToolName, DefaultTrailTool.Get()); + return TempToolMap; +} + +void FMovieSceneTransformTrail::AddReferencedObjects(FReferenceCollector & Collector) +{ + TArray ToolKeys = DefaultTrailTool->GetKeySceneComponents(); + Collector.AddReferencedObjects(ToolKeys); +} + +UMovieScene3DTransformSection* FMovieSceneTransformTrail::GetTransformSection() const +{ + UMovieScene3DTransformTrack* TransformTrack = WeakTrack.Get(); + check(TransformTrack); + + UMovieScene3DTransformSection* AbsoluteTransformSection = nullptr; + for (UMovieSceneSection* Section : TransformTrack->GetAllSections()) + { + UMovieScene3DTransformSection* TransformSection = Cast(Section); + check(Section); + + if (!TransformSection->GetBlendType().IsValid() || TransformSection->GetBlendType().Get() == EMovieSceneBlendType::Absolute) + { + AbsoluteTransformSection = TransformSection; + break; + } + } + + check(AbsoluteTransformSection); + + return AbsoluteTransformSection; +} + +TRange FMovieSceneTransformTrail::GetEffectiveTrackRange() const +{ + UMovieScene3DTransformTrack* TransformTrack = WeakTrack.Get(); + TSharedPtr Sequencer = WeakSequencer.Pin(); + check(TransformTrack && Sequencer); + + TRange EffectiveTrackRange = TRange::Empty(); + for (UMovieSceneSection* Section : TransformTrack->GetAllSections()) + { + TRange EffectiveRange = Section->ComputeEffectiveRange(); + TRange SectionRangeSeconds = TRange( + Sequencer->GetFocusedTickResolution().AsSeconds(EffectiveRange.GetLowerBoundValue()), + Sequencer->GetFocusedTickResolution().AsSeconds(EffectiveRange.GetUpperBoundValue()) + ); + EffectiveTrackRange = TRange::Hull(TArray>{ EffectiveTrackRange, SectionRangeSeconds }); + } + + return EffectiveTrackRange; +} diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MovieSceneTransformTrail.h b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MovieSceneTransformTrail.h new file mode 100644 index 000000000000..6b93adb60ee5 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/MovieSceneTransformTrail.h @@ -0,0 +1,228 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Trail.h" +#include "MotionTrailEditorToolset.h" +#include "TrajectoryDrawInfo.h" + +#include "BaseGizmos/TransformProxy.h" + +#include "MovieSceneTracksComponentTypes.h" +#include "EntitySystem/Interrogation/MovieSceneInterrogationLinker.h" + +#include "UObject/GCObject.h" + +#include "MovieSceneTransformTrail.generated.h" + +class FMovieSceneTransformTrail; + +// TODO: split tool stuff into a different file, operate on IEditableMovieSceneTrail or something +enum class EMSTrailTransformChannel : uint8 +{ + TranslateX = 0, + TranslateY = 1, + TranslateZ = 2, + RotateX = 3, + RotateY = 4, + RotateZ = 5, + ScaleX = 6, + ScaleY = 7, + ScaleZ = 8, + MaxChannel = 8 +}; + +UCLASS() +class UMSTrailKeyProperties : public UObject +{ + GENERATED_BODY() +public: + UPROPERTY(EditAnywhere, Category = ToolShowOptions) + float KeySize = 10.0f; +}; + +class FDefaultMovieSceneTransformTrailTool : public FInteractiveTrailTool +{ +public: + FDefaultMovieSceneTransformTrailTool(FMovieSceneTransformTrail* InOwningTrail) + : OwningTrail(InOwningTrail) + {} + + virtual void Setup() override; + virtual void Render(IToolsContextRenderAPI* RenderAPI) override; + + virtual FInputRayHit IsHitByClick(const FInputDeviceRay& ClickPos) override; + virtual void OnClicked(const FInputDeviceRay& ClickPos) override; + + virtual TArray GetStaticToolProperties() const override { return TArray{KeyProps}; } + + TArray GetKeySceneComponents() + { + TArray SceneComponents; + SceneComponents.Reserve(Keys.Num() * 2); + for (const TPair>& FrameKeyPair : Keys) + { + SceneComponents.Add(FrameKeyPair.Value->SceneComponent); + SceneComponents.Add(FrameKeyPair.Value->ParentSceneComponent); + } + return SceneComponents; + } + + void OnSectionChanged(); + void UpdateKeysInRange(FTrajectoryCache* ParentTrajectoryCache, const TRange& ViewRange); +private: + + void BuildKeys(); + bool ShouldRebuildKeys(); + void ClearSelection(); + void DirtyKeyTransforms(); + + enum class EKeyUpdateType + { + FromComponent, + FromComponentDelta, + FromTrailCache + }; + + // TODO: support world/local transform, must find way to get reference to parent node and call SceneComponent->AttachTo(DummyParentCommponent) + struct FKeyInfo + { + FKeyInfo(const FFrameNumber InFrameNumber, class UMovieScene3DTransformSection* InTrackSection, FMovieSceneTransformTrail* InOwningTrail); + + void OnKeyTransformChanged(class UTransformProxy*, FTransform NewTransform) + { + if (DragStartCompTransform) + { + UpdateKeyTransform(EKeyUpdateType::FromComponentDelta); + } + } + + void OnDragStart(class UTransformProxy*); + + void OnDragEnd(class UTransformProxy*) + { + DragStartTransform.Reset(); + DragStartCompTransform = TOptional(); + } + + // Re-eval transform or use given one + void UpdateKeyTransform(EKeyUpdateType UpdateType, FTrajectoryCache* ParentTrajectoryCache = nullptr); + + // Key Specific info + class USceneComponent* SceneComponent; + class USceneComponent* ParentSceneComponent; + TMap IdxMap; + TOptional DragStartCompTransform; + TMap DragStartTransform; + FFrameNumber FrameNumber; + bool bDirty; + + // General curve info + UMovieScene3DTransformSection* TrackSection; + class FMovieSceneTransformTrail* OwningTrail; + }; + + void UpdateGizmoActorComponents(FKeyInfo* KeyInfo, UTransformGizmo* TransformGizmo); + + static UMSTrailKeyProperties* KeyProps; + + TMap> Keys; + + FKeyInfo* CachedSelected; + TWeakObjectPtr ActiveTransformGizmo; + + FMovieSceneTransformTrail* OwningTrail; + + friend class UMSTrailTransformProxy; +}; + +UCLASS() +class UMSTrailTransformProxy : public UTransformProxy +{ + GENERATED_BODY() +public: + struct FKeyDelegateHandles + { + FDelegateHandle OnTransformChangedHandle; + FDelegateHandle OnBeginTransformEditSequenceHandle; + FDelegateHandle OnEndTransformEditSequenceHandle; + }; + + virtual void AddKey(FDefaultMovieSceneTransformTrailTool::FKeyInfo* KeyInfo) + { + FKeyDelegateHandles KeyDelegateHandles; + KeyDelegateHandles.OnTransformChangedHandle = OnTransformChanged.AddRaw(KeyInfo, &FDefaultMovieSceneTransformTrailTool::FKeyInfo::OnKeyTransformChanged); + KeyDelegateHandles.OnBeginTransformEditSequenceHandle = OnBeginTransformEdit.AddRaw(KeyInfo, &FDefaultMovieSceneTransformTrailTool::FKeyInfo::OnDragStart); + KeyDelegateHandles.OnEndTransformEditSequenceHandle = OnEndTransformEdit.AddRaw(KeyInfo, &FDefaultMovieSceneTransformTrailTool::FKeyInfo::OnDragEnd); + KeysTracked.Add(KeyInfo, KeyDelegateHandles); + AddComponent(KeyInfo->SceneComponent); + } + + virtual void RemoveKey(FDefaultMovieSceneTransformTrailTool::FKeyInfo* KeyInfo) + { + OnTransformChanged.Remove(KeysTracked[KeyInfo].OnTransformChangedHandle); + OnBeginTransformEdit.Remove(KeysTracked[KeyInfo].OnBeginTransformEditSequenceHandle); + OnEndTransformEdit.Remove(KeysTracked[KeyInfo].OnEndTransformEditSequenceHandle); + KeysTracked.Remove(KeyInfo); + RemoveComponent(KeyInfo->SceneComponent); + } + + virtual void RemoveComponent(USceneComponent* Component) + { + for (int32 Idx = 0; Idx < Objects.Num(); Idx++) + { + if (Objects[Idx].Component == Component) + { + Objects.RemoveAt(Idx); + UpdateSharedTransform(); + OnTransformChanged.Broadcast(this, SharedTransform); + return; + } + } + return; + } + + const TMap& GetKeysTracked() const { return KeysTracked; } + + bool IsEmpty() const { return KeysTracked.Num() == 0; } + +protected: + TMap KeysTracked; +}; + +// TODO: make trails per-section, not per track +// TODO: add hierarchy reference +class FMovieSceneTransformTrail : public FTrail, public FGCObject +{ +public: + FMovieSceneTransformTrail(const FLinearColor& InColor, const bool bInIsVisible, TWeakObjectPtr InWeakTrack, TSharedPtr InSequencer); + + // FTrail interface + virtual ETrailCacheState UpdateTrail(const FSceneContext& InSceneContext) override; + virtual FTrajectoryCache* GetTrajectoryTransforms() override { return TrajectoryCache.Get(); } + virtual FTrajectoryDrawInfo* GetDrawInfo() override { return DrawInfo.Get(); } + virtual TMap GetTools() override; + virtual TRange GetEffectiveRange() const override { return CachedEffectiveRange; } + // End FTrail interface + + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + + TSharedPtr GetSequencer() { return WeakSequencer.Pin(); } + + friend class FDefaultMovieSceneTransformTrailTool; + +private: + class UMovieScene3DTransformSection* GetTransformSection() const; + TRange GetEffectiveTrackRange() const; + + TRange CachedEffectiveRange; + + TUniquePtr DefaultTrailTool; + TUniquePtr DrawInfo; + TUniquePtr TrajectoryCache; + + FGuid LastTransformTrackSig; + TWeakObjectPtr WeakTrack; + TWeakPtr WeakSequencer; + TUniquePtr Interrogator; +}; diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/SequencerTrailHierarchy.cpp b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/SequencerTrailHierarchy.cpp new file mode 100644 index 000000000000..5cf0f23e1605 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/SequencerTrailHierarchy.cpp @@ -0,0 +1,303 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SequencerTrailHierarchy.h" +#include "MotionTrailEditorMode.h" + +#include "MovieSceneSequence.h" +#include "ISequencer.h" +#include "Tracks/MovieScene3DTransformTrack.h" +#include "MovieSceneSection.h" + +#include "ControlRig.h" +#include "Sequencer/MovieSceneControlRigParameterTrack.h" +#include "Sequencer/MovieSceneControlRigParameterSection.h" +#include "Sequencer/ControlRigSortedControls.h" +//#include "ControlRigTransformTrail.h" + +#include "MovieSceneTransformTrail.h" + +void FSequencerTrailHierarchy::Initialize() +{ + TSharedPtr Sequencer = WeakSequencer.Pin(); + if (!Sequencer) + { + return; + } + + OnActorAddedToSequencerHandle = Sequencer->OnActorAddedToSequencer().AddLambda([this](AActor* InActor, const FGuid InGuid) { + UMovieScene3DTransformTrack* TransformTrack = WeakSequencer.Pin()->GetFocusedMovieSceneSequence()->GetMovieScene()->FindTrack(InGuid); + if (TransformTrack) + { + AddComponentToHierarchy(InActor->GetRootComponent(), TransformTrack); + } + }); + + OnLevelActorAttachedHandle = GEngine ? GEngine->OnLevelActorAttached().AddLambda([this](AActor* InActor, const AActor* InParent) { + USceneComponent* RootComponent = InActor->GetRootComponent(); + if (!ObjectsTracked.Contains(RootComponent)) + { + return; + } + + for (const FGuid& ParentGuid : Hierarchy[ObjectsTracked[RootComponent]].Parents) + { + Hierarchy[ParentGuid].Children.Remove(ObjectsTracked[RootComponent]); + } + Hierarchy[ObjectsTracked[RootComponent]].Parents.Reset(); + + ResolveComponentToRoot(RootComponent); + + AllTrails[ObjectsTracked[RootComponent]]->ForceEvaluateNextTick(); + }) : FDelegateHandle(); + + OnLevelActorDetachedHandle = GEngine ? GEngine->OnLevelActorDetached().AddLambda([this](AActor* InActor, const AActor* InParent) { + USceneComponent* RootComponent = InActor->GetRootComponent(); + USceneComponent* ParentRootComponent = InParent->GetRootComponent(); + if (!ObjectsTracked.Contains(RootComponent)) + { + return; + } + + if (ObjectsTracked.Contains(ParentRootComponent)) + { + Hierarchy[ObjectsTracked[ParentRootComponent]].Children.Remove(ObjectsTracked[RootComponent]); + } + + Hierarchy[RootTrailGuid].Children.Add(ObjectsTracked[RootComponent]); + Hierarchy[ObjectsTracked[RootComponent]].Parents.Reset(); + Hierarchy[ObjectsTracked[RootComponent]].Parents.Add(RootTrailGuid); + + AllTrails[ObjectsTracked[RootComponent]]->ForceEvaluateNextTick(); + }) : FDelegateHandle(); + + OnSelectionChangedHandle = Sequencer->GetSelectionChangedObjectGuids().AddLambda([this](TArray NewSelection) { + TSharedPtr Sequencer = WeakSequencer.Pin(); + check(Sequencer); + + for (TPair>& GuidTrailPair : AllTrails) + { + if (GuidTrailPair.Value->GetDrawInfo()) GuidTrailPair.Value->GetDrawInfo()->SetIsVisible(false); + } + + auto SetVisibleFunc = [this](FTrail* TrailPtr) { + FTrajectoryDrawInfo* DrawInfo = TrailPtr->GetDrawInfo(); + if (DrawInfo && !DrawInfo->IsVisible()) + { + DrawInfo->SetIsVisible(true); + } + }; + + UpdateSequencerBindings(NewSelection, SetVisibleFunc); + }); + + OnViewOptionsChangedHandle = WeakEditorMode->GetTrailOptions()->OnDisplayPropertyChanged.AddLambda([this](FName) { + AllTrails[RootTrailGuid]->ForceEvaluateNextTick(); + }); + + UpdateViewRange(); + + RootTrailGuid = FGuid::NewGuid(); + TUniquePtr RootTrail = MakeUnique(); + AllTrails.Add(RootTrailGuid, MoveTemp(RootTrail)); + Hierarchy.Add(RootTrailGuid, FTrailHierarchyNode()); + + TArray SequencerSelectedObjects; + Sequencer->GetSelectedObjects(SequencerSelectedObjects); + UpdateSequencerBindings(SequencerSelectedObjects, + [this](FTrail* TrailPtr) { + FTrajectoryDrawInfo* DrawInfo = TrailPtr->GetDrawInfo(); + if (DrawInfo && !DrawInfo->IsVisible()) + { + DrawInfo->SetIsVisible(true); + } + } + ); +} + +void FSequencerTrailHierarchy::Destroy() +{ + TSharedPtr Sequencer = WeakSequencer.Pin(); + if (Sequencer) + { + Sequencer->OnActorAddedToSequencer().Remove(OnActorAddedToSequencerHandle); + Sequencer->GetSelectionChangedObjectGuids().Remove(OnSelectionChangedHandle); + Sequencer->OnGlobalTimeChanged().Remove(OnGlobalTimeChangedHandle); + WeakEditorMode->GetTrailOptions()->OnDisplayPropertyChanged.Remove(OnViewOptionsChangedHandle); + } + + if (GEngine) + { + GEngine->OnLevelActorAttached().Remove(OnLevelActorAttachedHandle); + GEngine->OnLevelActorDetached().Remove(OnLevelActorDetachedHandle); + } + + for (TPair>& GuidTrailPair : AllTrails) + { + const TMap& ToolsForTrail = GuidTrailPair.Value->GetTools(); + for (const TPair& NameToolPair : ToolsForTrail) + { + WeakEditorMode->RemoveTrailTool(NameToolPair.Key, NameToolPair.Value); + } + } + + Hierarchy.Reset(); + ObjectsTracked.Reset(); + AllTrails.Reset(); + RootTrailGuid = FGuid(); +} + +void FSequencerTrailHierarchy::RemoveTrail(const FGuid& Key) +{ + FTrailHierarchy::RemoveTrail(Key); + if (UObject* const* FoundObject = ObjectsTracked.FindKey(Key)) + { + ObjectsTracked.Remove(*FoundObject); + } + else if (const FName* FoundControl = ControlsTracked.FindKey(Key)) + { + ControlsTracked.Remove(*FoundControl); + } +} + +void FSequencerTrailHierarchy::UpdateSequencerBindings(const TArray& SequencerBindings, TFunctionRef OnUpdated) +{ + TSharedPtr Sequencer = WeakSequencer.Pin(); + check(Sequencer); + + for (FGuid BindingGuid : SequencerBindings) + { + if (UMovieScene3DTransformTrack* TransformTrack = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene()->FindTrack(BindingGuid)) + { + for (TWeakObjectPtr<> BoundObject : Sequencer->FindBoundObjects(BindingGuid, Sequencer->GetFocusedTemplateID())) + { + USceneComponent* BoundComponent = Cast(BoundObject.Get()); + if (AActor* BoundActor = Cast(BoundObject.Get())) + { + BoundComponent = BoundActor->GetRootComponent(); + } + + if (!ObjectsTracked.Contains(BoundComponent)) + { + AddComponentToHierarchy(BoundComponent, TransformTrack); + } + + if (!ObjectsTracked.Contains(BoundComponent)) + { + continue; + } + + OnUpdated(AllTrails[ObjectsTracked[BoundComponent]].Get()); + + } + } // if TransformTrack + else if (UMovieSceneControlRigParameterTrack* CRParameterTrack = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene()->FindTrack(BindingGuid)) + { + + } // if ControlRigParameterTrack + } +} + +void FSequencerTrailHierarchy::UpdateObjectsTracked() +{ + TSharedPtr Sequencer = WeakSequencer.Pin(); + if (!Sequencer) + { + return; + } + + // Clear hierarchy + Hierarchy.Reset(); + ObjectsTracked.Reset(); + + // Then re-build + RootTrailGuid = FGuid::NewGuid(); + TUniquePtr RootTrail = MakeUnique(); + AllTrails.Add(RootTrailGuid, MoveTemp(RootTrail)); + Hierarchy.Add(RootTrailGuid, FTrailHierarchyNode()); + + TArray SequencerBoundGuids; + for (const FMovieSceneBinding& Binding : Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene()->GetBindings()) + { + SequencerBoundGuids.Add(Binding.GetObjectGuid()); + } + + UpdateSequencerBindings(SequencerBoundGuids, [](FTrail*) {}); +} + +void FSequencerTrailHierarchy::UpdateViewRange() +{ + TSharedPtr Sequencer = WeakSequencer.Pin(); + check(Sequencer); + + FFrameRate TickResolution = Sequencer->GetFocusedTickResolution(); + FFrameRate DisplayRate = Sequencer->GetFocusedDisplayRate(); + + TRange TickViewRange; + if (!WeakEditorMode->GetTrailOptions()->bShowFullTrail) + { + FFrameTime SequenceTime = Sequencer->GetLocalTime().Time; + const FFrameNumber TicksBefore = FFrameRate::TransformTime(FFrameNumber(WeakEditorMode->GetTrailOptions()->FramesBefore), DisplayRate, TickResolution).FloorToFrame(); + const FFrameNumber TicksAfter = FFrameRate::TransformTime(FFrameNumber(WeakEditorMode->GetTrailOptions()->FramesAfter), DisplayRate, TickResolution).FloorToFrame(); + TickViewRange = TRange(SequenceTime.GetFrame() - TicksBefore, SequenceTime.GetFrame() + TicksAfter); + } + else + { + TickViewRange = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene()->GetPlaybackRange(); + } + + const double StartSeconds = TickResolution.AsSeconds(FFrameTime(TickViewRange.GetLowerBoundValue())); + const double EndSeconds = TickResolution.AsSeconds(FFrameTime(TickViewRange.GetUpperBoundValue())); + + // snap view range to ticks per segment + const double TicksBetween = FMath::Fmod(StartSeconds, WeakEditorMode->GetTrailOptions()->SecondsPerSegment); + ViewRange = TRange(StartSeconds - TicksBetween, EndSeconds - TicksBetween); +} + +void FSequencerTrailHierarchy::ResolveComponentToRoot(USceneComponent* Component) +{ + TSharedPtr Sequencer = WeakSequencer.Pin(); + check(Sequencer); + + const FGuid CurTrailGuid = ObjectsTracked.FindOrAdd(Component, FGuid::NewGuid()); + FTrailHierarchyNode& CurTrailNode = Hierarchy.FindOrAdd(CurTrailGuid); + + if (!ObjectsTracked.Contains(Component->GetAttachParent()) || !CurTrailNode.Parents.Contains(ObjectsTracked[Component->GetAttachParent()])) + { + USceneComponent* ChildItr = Component; + FTrailHierarchyNode* ChildNode = &CurTrailNode; + while (ChildItr->GetAttachParent() != nullptr) + { + FGuid ChildGuid = ObjectsTracked[ChildItr]; + FGuid ParentGuid = ObjectsTracked.FindOrAdd(ChildItr->GetAttachParent(), FGuid::NewGuid()); + FTrailHierarchyNode& ParentNode = Hierarchy.FindOrAdd(ParentGuid); + + if (!AllTrails.Contains(ParentGuid)) + { + AllTrails.Add(ParentGuid, MakeUnique(ChildItr->GetAttachParent())); + } + + if (!ParentNode.Children.Contains(ChildGuid)) ParentNode.Children.Add(ChildGuid); + if (!ChildNode->Parents.Contains(ParentGuid)) ChildNode->Parents.Add(ParentGuid); + + ChildItr = ChildItr->GetAttachParent(); + ChildNode = &ParentNode; + } + + if (!Hierarchy[RootTrailGuid].Children.Contains(ObjectsTracked[ChildItr])) Hierarchy[RootTrailGuid].Children.Add(ObjectsTracked[ChildItr]); + if (!ChildNode->Parents.Contains(RootTrailGuid)) ChildNode->Parents.Add(RootTrailGuid); + } +} + +void FSequencerTrailHierarchy::AddComponentToHierarchy(USceneComponent* CompToAdd, UMovieScene3DTransformTrack* TransformTrack) +{ + TSharedPtr Sequencer = WeakSequencer.Pin(); + check(Sequencer); + + ResolveComponentToRoot(CompToAdd); + + TUniquePtr CurTrail = MakeUnique(FLinearColor::White, false, TransformTrack, Sequencer); + if (AllTrails.Contains(ObjectsTracked[CompToAdd])) AllTrails.Remove(ObjectsTracked[CompToAdd]); + CurTrail->ForceEvaluateNextTick(); + + AddTrail(ObjectsTracked[CompToAdd], Hierarchy[ObjectsTracked[CompToAdd]], MoveTemp(CurTrail)); +} diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/SequencerTrailHierarchy.h b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/SequencerTrailHierarchy.h new file mode 100644 index 000000000000..0f85180ee076 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/SequencerTrailHierarchy.h @@ -0,0 +1,60 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TrailHierarchy.h" + +#include "ISequencer.h" + +class ISequencer; + +class FSequencerTrailHierarchy : public FTrailHierarchy +{ +public: + FSequencerTrailHierarchy(TWeakObjectPtr InWeakEditorMode, TWeakPtr InWeakSequencer) + : FTrailHierarchy(InWeakEditorMode) + , WeakSequencer(InWeakSequencer) + , ObjectsTracked() + , ControlsTracked() + , HierarchyRenderer(MakeUnique(this)) + , OnActorAddedToSequencerHandle() + , OnSelectionChangedHandle() + , OnGlobalTimeChangedHandle() + , OnViewOptionsChangedHandle() + {} + + // FTrailHierarchy interface + virtual void Initialize() override; + virtual void Destroy() override; + virtual ITrailHierarchyRenderer* GetRenderer() const override { return HierarchyRenderer.Get(); } + virtual double GetSecondsPerFrame() const override { return 1.0 / WeakSequencer.Pin()->GetFocusedDisplayRate().AsDecimal(); } + + virtual void RemoveTrail(const FGuid& Key); + + virtual void Update() override + { + UpdateViewRange(); + FTrailHierarchy::Update(); + } + +private: + void UpdateSequencerBindings(const TArray& SequencerBindings, TFunctionRef OnUpdated); + void UpdateObjectsTracked(); + void UpdateViewRange(); + + void ResolveComponentToRoot(class USceneComponent* Component); + void AddComponentToHierarchy(class USceneComponent* CompToAdd, class UMovieScene3DTransformTrack* TransformTrack); + + TWeakPtr WeakSequencer; + TMap ObjectsTracked; + TMap ControlsTracked; + + TUniquePtr HierarchyRenderer; + + FDelegateHandle OnActorAddedToSequencerHandle; + FDelegateHandle OnLevelActorAttachedHandle; + FDelegateHandle OnLevelActorDetachedHandle; + FDelegateHandle OnSelectionChangedHandle; + FDelegateHandle OnGlobalTimeChangedHandle; + FDelegateHandle OnViewOptionsChangedHandle; +}; diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/Trail.cpp b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/Trail.cpp new file mode 100644 index 000000000000..6c3ce16fe843 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/Trail.cpp @@ -0,0 +1,72 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Trail.h" +#include "TrailHierarchy.h" +#include "MotionTrailEditorMode.h" + +ETrailCacheState FRootTrail::UpdateTrail(const FSceneContext& InSceneContext) +{ + if (bForceEvaluateNextTick) + { + bForceEvaluateNextTick = false; + return ETrailCacheState::Stale; + } + else + { + return ETrailCacheState::UpToDate; + } +} + + +ETrailCacheState FConstantComponentTrail::UpdateTrail(const FTrail::FSceneContext& InSceneContext) +{ + const TUniquePtr& Parent = InSceneContext.TrailHierarchy->GetAllTrails()[InSceneContext.TrailHierarchy->GetHierarchy()[InSceneContext.YourNode].Parents[0]]; + + ETrailCacheState CombinedParentStates = ETrailCacheState::UpToDate; + for (const TPair& GuidStatePair : InSceneContext.ParentCacheStates) + { + if (GuidStatePair.Value == ETrailCacheState::Dead) + { + CombinedParentStates = ETrailCacheState::Dead; + break; + } + else if (GuidStatePair.Value == ETrailCacheState::Stale) + { + CombinedParentStates = ETrailCacheState::Stale; + } + } + + if (!WeakComponent.IsValid() || CombinedParentStates == ETrailCacheState::Dead) + { + return ETrailCacheState::Dead; + } + + const FTransform CurLocalTransform = WeakComponent->GetRelativeTransform(); + const bool bParentChanged = CombinedParentStates != ETrailCacheState::Stale; + const bool bLocalTransformChanged = !CurLocalTransform.Equals(LastLocalTransform); + const bool bEvalRangeCached = TrajectoryCache->GetTrackRange().Contains(InSceneContext.EvalTimes.Range); + + FTrailEvaluateTimes TempEvalTimes = InSceneContext.EvalTimes; + if (bLocalTransformChanged || bParentChanged || bForceEvaluateNextTick) + { + double Spacing = InSceneContext.EvalTimes.Spacing.Get(InSceneContext.TrailHierarchy->GetEditorMode()->GetTrailOptions()->SecondsPerSegment); + CachedEffectiveRange = Parent->GetEffectiveRange(); + *TrajectoryCache = FArrayTrajectoryCache(Spacing, CachedEffectiveRange, CurLocalTransform * Parent->GetTrajectoryTransforms()->GetDefault()); + TrajectoryCache->UpdateCacheTimes(TempEvalTimes); + LastLocalTransform = CurLocalTransform; + + bForceEvaluateNextTick = false; + + for (const double Time : TempEvalTimes.EvalTimes) + { + const FTransform TempWorldTransform = LastLocalTransform * Parent->GetTrajectoryTransforms()->Get(Time); + TrajectoryCache->Set(Time, TempWorldTransform); + } + + return ETrailCacheState::Stale; + } + else + { + return ETrailCacheState::UpToDate; + } +} diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/TrailHierarchy.cpp b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/TrailHierarchy.cpp new file mode 100644 index 000000000000..2485f705c19b --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/TrailHierarchy.cpp @@ -0,0 +1,185 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "TrailHierarchy.h" +#include "MotionTrailEditorMode.h" +#include "TrajectoryDrawInfo.h" +#include "Containers/Queue.h" +#include "CanvasItem.h" +#include "CanvasTypes.h" + +void FTrailHierarchyRenderer::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) +{ + const int32 NumEvalTimes = int32(OwningHierarchy->GetViewRange().Size() / OwningHierarchy->GetEditorMode()->GetTrailOptions()->SecondsPerSegment); + const int32 NumLinesReserveSize = int32(NumEvalTimes * OwningHierarchy->GetAllTrails().Num() * 1.5); + PDI->AddReserveLines(SDPG_Foreground, NumLinesReserveSize); + + // + TQueue BFSQueue; + BFSQueue.Enqueue(OwningHierarchy->GetRootTrailGuid()); + while (!BFSQueue.IsEmpty()) + { + FGuid CurGuid; + BFSQueue.Dequeue(CurGuid); + + FTrajectoryDrawInfo* CurDrawInfo = OwningHierarchy->GetAllTrails()[CurGuid]->GetDrawInfo(); + if (CurDrawInfo && CurDrawInfo->IsVisible()) + { + FTrajectoryDrawInfo::FDisplayContext DisplayContext = { + CurGuid, + FTrailScreenSpaceTransform(View, Viewport), + OwningHierarchy->GetEditorMode()->GetTrailOptions()->SecondsPerTick, + OwningHierarchy->GetViewRange(), + OwningHierarchy + }; + + TArray PointsToDraw = CurDrawInfo->GetTrajectoryPointsForDisplay(DisplayContext); + + if (PointsToDraw.Num() <= 1) + { + continue; + } + + FVector LastPoint = PointsToDraw[0]; + for (int32 Idx = 1; Idx < PointsToDraw.Num(); Idx++) + { + const FVector CurPoint = PointsToDraw[Idx]; + PDI->DrawLine(LastPoint, CurPoint, CurDrawInfo->GetColor(), SDPG_Foreground); + LastPoint = CurPoint; + } + } + + for (const FGuid& ChildGuid : OwningHierarchy->GetHierarchy()[CurGuid].Children) + { + BFSQueue.Enqueue(ChildGuid); + } + } +} + +void FTrailHierarchyRenderer::DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) +{ + TQueue BFSQueue; + BFSQueue.Enqueue(OwningHierarchy->GetRootTrailGuid()); + while (!BFSQueue.IsEmpty()) + { + FGuid CurGuid; + BFSQueue.Dequeue(CurGuid); + + FTrajectoryDrawInfo* CurDrawInfo = OwningHierarchy->GetAllTrails()[CurGuid]->GetDrawInfo(); + if (CurDrawInfo && CurDrawInfo->IsVisible()) + { + FTrajectoryDrawInfo::FDisplayContext DisplayContext = { + CurGuid, + FTrailScreenSpaceTransform(View, Viewport, ViewportClient->GetDPIScale()), + OwningHierarchy->GetEditorMode()->GetTrailOptions()->bLockTicksToFrames ? OwningHierarchy->GetSecondsPerFrame() : OwningHierarchy->GetEditorMode()->GetTrailOptions()->SecondsPerTick, + OwningHierarchy->GetViewRange(), + OwningHierarchy + }; + + TArray Ticks, TickNormals; + CurDrawInfo->GetTickPointsForDisplay(DisplayContext, Ticks, TickNormals); + + for (int32 Idx = 0; Idx < Ticks.Num(); Idx++) + { + const FVector2D StartPoint = Ticks[Idx] - TickNormals[Idx] * OwningHierarchy->GetEditorMode()->GetTrailOptions()->TickSize; + const FVector2D EndPoint = Ticks[Idx] + TickNormals[Idx] * OwningHierarchy->GetEditorMode()->GetTrailOptions()->TickSize; + FCanvasLineItem LineItem = FCanvasLineItem(StartPoint, EndPoint); + Canvas->DrawItem(LineItem); + } + } + + for (const FGuid& ChildGuid : OwningHierarchy->GetHierarchy()[CurGuid].Children) + { + BFSQueue.Enqueue(ChildGuid); + } + } +} + +void FTrailHierarchy::Update() +{ + //bHasDeadTrails = false; + + TArray EvalTimesArr; + const double Spacing = WeakEditorMode->GetTrailOptions()->SecondsPerSegment; + for (double SecondsItr = ViewRange.GetLowerBoundValue(); SecondsItr < ViewRange.GetUpperBoundValue() + Spacing; SecondsItr += Spacing) + { + EvalTimesArr.Add(SecondsItr); + } + + FTrailEvaluateTimes EvalTimes = FTrailEvaluateTimes(EvalTimesArr, Spacing); + + TArray DeadTrails; + + // + TMap> Visited; + TQueue BFSQueue; + BFSQueue.Enqueue(RootTrailGuid); + Visited.Add(RootTrailGuid); + while (!BFSQueue.IsEmpty()) + { + FGuid CurGuid; + BFSQueue.Dequeue(CurGuid); + + FTrail::FSceneContext SceneContext = { + CurGuid, + EvalTimes, + this, + Visited[CurGuid] + }; + + ETrailCacheState CurCacheState = AllTrails[CurGuid]->UpdateTrail(SceneContext); + if (CurCacheState == ETrailCacheState::Dead) + { + DeadTrails.Add(CurGuid); + } + + for (const FGuid& ChildGuid : Hierarchy[CurGuid].Children) + { + if (!Visited.Contains(ChildGuid)) + { + BFSQueue.Enqueue(ChildGuid); + } + + Visited.FindOrAdd(ChildGuid).Add(CurGuid, CurCacheState); + } + } + + for (const FGuid& TrailGuid : DeadTrails) + { + RemoveTrail(TrailGuid); + } +} + +void FTrailHierarchy::AddTrail(const FGuid& Key, const FTrailHierarchyNode& Node, TUniquePtr&& TrailPtr) +{ + TMap ToolsForTrail = TrailPtr->GetTools(); + for (TPair& NameToolPair : ToolsForTrail) + { + WeakEditorMode->AddTrailTool(NameToolPair.Key, NameToolPair.Value); + } + + AllTrails.Add(Key, MoveTemp(TrailPtr)); + Hierarchy.Add(Key, Node); +} + +void FTrailHierarchy::RemoveTrail(const FGuid& Key) +{ + TMap ToolsForTrail = AllTrails[Key]->GetTools(); + for (TPair& NameToolPair : ToolsForTrail) + { + WeakEditorMode->RemoveTrailTool(NameToolPair.Key, NameToolPair.Value); + } + + FTrailHierarchyNode& TrailNode = Hierarchy[Key]; + for (const FGuid& ParentGuid : TrailNode.Parents) + { + Hierarchy[ParentGuid].Children.Remove(Key); + } + + for (const FGuid& ChildGuid : TrailNode.Children) + { + Hierarchy[ChildGuid].Parents.Remove(Key); + } + + AllTrails.Remove(Key); + Hierarchy.Remove(Key); +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/TrajectoryCache.cpp b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/TrajectoryCache.cpp new file mode 100644 index 000000000000..64bf7a97e6fc --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/TrajectoryCache.cpp @@ -0,0 +1,101 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "TrajectoryCache.h" + +namespace UE +{ + bool RangesContain(const TSet>& Ranges, const double Time) + { + for (const TRange& Range : Ranges) + { + if (Range.Contains(Time)) + { + return true; + } + } + return false; + } +} + +void FTrajectoryCache::UpdateCacheTimes(FTrailEvaluateTimes& InOutEvaluateTimes) +{ + if (!InOutEvaluateTimes.Spacing) + { + return; + } + + const double Spacing = InOutEvaluateTimes.Spacing.GetValue(); + + if (CoveredRanges.Num() == 0) + { + CoveredRanges.Add(InOutEvaluateTimes.Range); + return; + } + + TArray> RangesToRemove; + TRange EvalRange = InOutEvaluateTimes.Range; + TRange HullRange = InOutEvaluateTimes.Range; + for (const TRange& CoveredRange : CoveredRanges) + { + if (EvalRange.Contains(CoveredRange.GetLowerBoundValue())) + { + RangesToRemove.Remove(CoveredRange); + EvalRange.SetUpperBoundValue(CoveredRange.GetLowerBoundValue()); + HullRange.SetUpperBoundValue(CoveredRange.GetUpperBoundValue()); + } + else if (EvalRange.Contains(CoveredRange.GetUpperBoundValue())) + { + RangesToRemove.Add(CoveredRange); + EvalRange.SetLowerBoundValue(CoveredRange.GetUpperBoundValue()); + HullRange.SetLowerBoundValue(CoveredRange.GetLowerBoundValue()); + } + } + + if (RangesToRemove.Num() > 0) + { + CoveredRanges.Add(HullRange); + } + + for (const TRange& RangeToRemove : RangesToRemove) + { + CoveredRanges.Remove(RangeToRemove); + } + + const int32 BeginOff = int32((EvalRange.GetLowerBoundValue() - InOutEvaluateTimes.Range.GetLowerBoundValue()) / Spacing); + const int32 EndOff = FMath::Max(int32((InOutEvaluateTimes.Range.GetUpperBoundValue() - EvalRange.GetUpperBoundValue()) / Spacing), 0); + + TArrayView NewEvalTimes = InOutEvaluateTimes.EvalTimes.Slice(BeginOff, InOutEvaluateTimes.EvalTimes.Num() - EndOff - BeginOff); + + InOutEvaluateTimes = FTrailEvaluateTimes(NewEvalTimes, Spacing); +} + +FTransform FArrayTrajectoryCache::GetInterp(const double InTime) const +{ + if (TrajectoryCache.Num() == 0) + { + return Default; + } + + const double T = (InTime/Spacing) - FMath::FloorToDouble(InTime / Spacing); + const int32 LowIdx = FMath::Clamp(int32((InTime - TrackRange.GetLowerBoundValue()) / Spacing), 0, TrajectoryCache.Num() - 1); + const int32 HighIdx = FMath::Clamp(LowIdx + 1, 0, TrajectoryCache.Num() - 1); + + FTransform TempBlended; + TempBlended.Blend(TrajectoryCache[LowIdx], TrajectoryCache[HighIdx], T); + return TempBlended; +} + +TArray FArrayTrajectoryCache::GetAllTimesInRange(const TRange& InRange) const +{ + TRange GenRange = TRange::Intersection({ TrackRange, InRange }); + + TArray AllTimesInRange; + AllTimesInRange.Reserve(int(GenRange.Size() / Spacing) + 1); + const double FirstTick = FMath::FloorToDouble((GenRange.GetLowerBoundValue()) / Spacing) * Spacing; + for (double TickItr = FirstTick; TickItr < GenRange.GetUpperBoundValue(); TickItr += Spacing) + { + AllTimesInRange.Add(TickItr); + } + + return AllTimesInRange; +} diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/TrajectoryDrawInfo.cpp b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/TrajectoryDrawInfo.cpp new file mode 100644 index 000000000000..427e55f14b4b --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Private/TrajectoryDrawInfo.cpp @@ -0,0 +1,64 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "TrajectoryDrawInfo.h" + +#include "MotionTrailEditorMode.h" +#include "TrailHierarchy.h" +#include "TrajectoryCache.h" + +FORCENOINLINE TOptional FTrailScreenSpaceTransform::ProjectPoint(const FVector& Point) const +{ + const FPlane Projection = View->Project(Point); + if (Projection.W > 0.0f) + { + const float XPos = HalfScreenSize.X + (HalfScreenSize.X * Projection.X * 1.0f); + const float YPos = HalfScreenSize.Y + (HalfScreenSize.Y * Projection.Y * -1.0f); + return FVector2D(XPos, YPos); + } + + return TOptional(); +} + +TArray FCachedTrajectoryDrawInfo::GetTrajectoryPointsForDisplay(const FDisplayContext& InDisplayContext) +{ + // TODO: return all points in range + TArray TrajectoryPoints; + CachedViewRange = InDisplayContext.TimeRange; + + const double Spacing = InDisplayContext.TrailHierarchy->GetEditorMode()->GetTrailOptions()->SecondsPerSegment; + TArray TrajectoryTimes = TrajectoryCache->GetAllTimesInRange(InDisplayContext.TimeRange); + TrajectoryPoints.Reserve(TrajectoryTimes.Num()); + + for (const double Time : TrajectoryTimes) + { + TrajectoryPoints.Add(TrajectoryCache->Get(Time).GetTranslation()); + } + + TrajectoryPoints.Add(TrajectoryCache->GetInterp(CachedViewRange.GetUpperBoundValue()).GetTranslation()); + + return TrajectoryPoints; +} + +void FCachedTrajectoryDrawInfo::GetTickPointsForDisplay(const FDisplayContext& InDisplayContext, TArray& Ticks, TArray& TickNormals) +{ + const double FirstTick = FMath::FloorToDouble(InDisplayContext.TimeRange.GetLowerBoundValue() / InDisplayContext.SecondsPerTick) * InDisplayContext.SecondsPerTick; + const double Spacing = InDisplayContext.TrailHierarchy->GetEditorMode()->GetTrailOptions()->SecondsPerSegment; + + for (double TickItr = FirstTick + InDisplayContext.SecondsPerTick; TickItr < InDisplayContext.TimeRange.GetUpperBoundValue(); TickItr += InDisplayContext.SecondsPerTick) + { + FVector Interpolated = TrajectoryCache->GetInterp(TickItr).GetTranslation(); + TOptional Projected = InDisplayContext.ScreenSpaceTransform.ProjectPoint(Interpolated); + const double PrevSampleTime = TickItr + Spacing < InDisplayContext.TimeRange.GetUpperBoundValue() ? TickItr + Spacing : TickItr - Spacing; + TOptional PrevProjected = InDisplayContext.ScreenSpaceTransform.ProjectPoint(TrajectoryCache->GetInterp(PrevSampleTime).GetTranslation()); + + if (Projected && PrevProjected) + { + Ticks.Add(Projected.GetValue()); + + FVector2D Diff = Projected.GetValue() - PrevProjected.GetValue(); + Diff.Normalize(); + + TickNormals.Add(FVector2D(-Diff.Y, Diff.X)); + } + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorMode.h b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorMode.h new file mode 100644 index 000000000000..104c4799ca02 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorMode.h @@ -0,0 +1,115 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Tools/UEdMode.h" + +#include "TrailHierarchy.h" + +#include "MotionTrailEditorMode.generated.h" + +class FInteractiveTrailTool; + +DECLARE_LOG_CATEGORY_EXTERN(LogMotionTrailEditorMode, Log, All); + +UCLASS() +class UMotionTrailOptions : public UObject +{ + GENERATED_BODY() + +public: + UMotionTrailOptions() + : bShowTrails(true) + , bShowFullTrail(true) + , FramesBefore(10) + , FramesAfter(10) + , SecondsPerSegment(0.1) + , bLockTicksToFrames(true) + , SecondsPerTick(0.1) + , TickSize(4.0) + {} + + UPROPERTY(EditAnywhere, Category = ShowOptions) + bool bShowTrails; + + UPROPERTY(EditAnywhere, Category = ShowOptions, Meta = (EditCondition = "bShowTrails")) + bool bShowFullTrail; + + UPROPERTY(EditAnywhere, Category = ShowOptions, Meta = (EditCondition = "!bShowFullTrail && bShowTrails", ClampMin = "0")) + int32 FramesBefore; + + UPROPERTY(EditAnywhere, Category = ShowOptions, Meta = (EditCondition = "!bShowFullTrail && bShowTrails", ClampMin = "0")) + int32 FramesAfter; + + UPROPERTY(EditAnywhere, Category = ShowOptions, Meta = (EditCondition = "bShowTrails", ClampMin = "0.0001")) + double SecondsPerSegment; + + UPROPERTY(EditAnywhere, Category = ShowOptions, Meta = (EditCondition = "bShowTrails")) + bool bLockTicksToFrames; + + UPROPERTY(EditAnywhere, Category = ShowOptions, Meta = (EditCondition = "bShowTrails && !bLockTicksToFrames", ClampMin = "0.0001")) + double SecondsPerTick; + + UPROPERTY(EditAnywhere, Category = ShowOptions, Meta = (EditCondition = "bShowTrails", ClampMin = "0.0")) + double TickSize; + + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override + { + FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None; + OnDisplayPropertyChanged.Broadcast(PropertyName); + } + + DECLARE_MULTICAST_DELEGATE_OneParam(FOnDisplayPropertyChanged, FName); + FOnDisplayPropertyChanged OnDisplayPropertyChanged; +}; + +UCLASS() +class UMotionTrailEditorMode : public UEdMode +{ + GENERATED_BODY() +public: + const static FEditorModeID EM_MotionTrailEditorModeId; + + + static FName MotionTrailEditorMode_Default; // Palette name + static FString DefaultToolName; + + UMotionTrailEditorMode(); + virtual ~UMotionTrailEditorMode(); + + // UEdMode interface + virtual void Enter() override; + virtual void Exit() override; + void CreateToolkit() override; + + virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override; + virtual void DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) override; + + bool UsesToolkits() const override; + + virtual TMap>> GetModeCommands() const override; + virtual void ActivateDefaultTool() override; + + virtual bool IsCompatibleWith(FEditorModeID OtherModeID) const override; + // End of FEdMode interface + + void AddTrailTool(const FString& ToolType, FInteractiveTrailTool* TrailTool); + void RemoveTrailTool(const FString& ToolType, FInteractiveTrailTool* TrailTool); + + const TMap>& GetTrailTools() const { return TrailTools; } + UMotionTrailOptions* GetTrailOptions() const { return TrailOptions; } + +private: + + void RefreshNonDefaultToolset(); + + UPROPERTY(Transient) + UMotionTrailOptions* TrailOptions; + + TMap> TrailTools; + + TArray> TrailHierarchies; + + FDelegateHandle OnSequencersChangedHandle; +}; diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorModeCommands.h b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorModeCommands.h new file mode 100644 index 000000000000..da118d1a6416 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorModeCommands.h @@ -0,0 +1,38 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Framework/Commands/UICommandInfo.h" +#include "Framework/Commands/Commands.h" +#include "EditorStyleSet.h" + +class FMotionTrailEditorModeCommands : public TCommands +{ +public: + FMotionTrailEditorModeCommands() + : TCommands( + "MotionTrail", + NSLOCTEXT("MotionTrailEditorMode", "MotionTrailEditingModeCommands", "Motion Trail Editing Mode"), + NAME_None, + FEditorStyle::GetStyleSetName() + ) + {} + + /** + * Initialize commands + */ + virtual void RegisterCommands() override; + + static void RegisterDynamic(const FName InName, const TArray>& InCommands); + static void UnRegisterDynamic(const FName InName); + + static const TMap>>& GetCommands() + { + return FMotionTrailEditorModeCommands::Get().Commands; + } + +public: + TSharedPtr Default; + + TMap>> Commands; +}; diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorModeModule.h b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorModeModule.h new file mode 100644 index 000000000000..27c3476e5e34 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorModeModule.h @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FMotionTrailEditorModeModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + +private: + void OnSequencerCreated(TSharedRef Sequencer); + + FDelegateHandle OnSequencerCreatedHandle; +}; diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorModeToolkit.h b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorModeToolkit.h new file mode 100644 index 000000000000..58e8348b5506 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorModeToolkit.h @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Toolkits/BaseToolkit.h" + +class FMotionTrailEditorModeToolkit : public FModeToolkit +{ +public: + + FMotionTrailEditorModeToolkit(); + + /** FModeToolkit interface */ + virtual void Init(const TSharedPtr& InitToolkitHost) override; + + /** IToolkit interface */ + virtual FName GetToolkitFName() const override; + virtual FText GetBaseToolkitName() const override; + virtual class UEdMode* GetScriptableEditorMode() const override; + +private: + +}; diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorToolset.h b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorToolset.h new file mode 100644 index 000000000000..437aeeaf6622 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/MotionTrailEditorToolset.h @@ -0,0 +1,120 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" + +#include "InteractiveTool.h" +#include "InteractiveToolBuilder.h" +#include "MultiSelectionTool.h" +#include "BaseBehaviors/BehaviorTargetInterfaces.h" +#include "Framework/Commands/UICommandInfo.h" + +#include "MotionTrailEditorToolset.generated.h" + +class UMotionTrailEditorMode; +class UTrailToolEditor; + +class FInteractiveTrailTool +{ +public: + + virtual ~FInteractiveTrailTool() {} + + virtual void Setup() {} + virtual void Render(IToolsContextRenderAPI* RenderAPI) {} + virtual void Tick(float DeltaTime) {} + + + virtual FInputRayHit IsHitByClick(const FInputDeviceRay& ClickPos) { return FInputRayHit(); } + virtual void OnClicked(const FInputDeviceRay& ClickPos) {}; + + virtual FInputRayHit CanBeginClickDragSequence(const FInputDeviceRay& PressPos) { return FInputRayHit(); } + virtual void OnClickPress(const FInputDeviceRay& PressPos) {} + virtual void OnClickDrag(const FInputDeviceRay& DragPos) {} + virtual void OnClickRelease(const FInputDeviceRay& ReleasePos) {} + virtual void OnTerminateDragSequence() {} + + virtual TArray GetStaticToolProperties() const { return TArray(); } + virtual TSharedPtr GetStaticUICommandInfo() { return nullptr; } // null to mark as default tool + + void SetMotionTrailEditorMode(TWeakObjectPtr InWeakEditorMode) { WeakEditorMode = InWeakEditorMode; } + bool IsActive() const { return WeakEditorMode.IsValid(); } + +protected: + TWeakObjectPtr WeakEditorMode; +}; + +/** + * Builder for UTrailToolManager + */ +UCLASS() +class UTrailToolManagerBuilder : public UInteractiveToolBuilder +{ + GENERATED_BODY() + +public: + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override { return true; } + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; + + void SetTrailToolName(const FString& InTrailToolName) { TrailToolName = InTrailToolName; } + void SetMotionTrailEditorMode(const TWeakObjectPtr InEditorMode) { EditorMode = InEditorMode; } + +private: + FString TrailToolName; + TWeakObjectPtr EditorMode; +}; + +UCLASS() +class UTrailToolManager : public UMultiSelectionTool, public IClickDragBehaviorTarget, public IClickBehaviorTarget +{ + GENERATED_BODY() + +public: + + // IClickBehaviorTarget interface + virtual FInputRayHit IsHitByClick(const FInputDeviceRay& ClickPos); + virtual void OnClicked(const FInputDeviceRay& ClickPos); + + // IClickDragBehaviorTarget interface + virtual FInputRayHit CanBeginClickDragSequence(const FInputDeviceRay& PressPos) override; + virtual void OnClickPress(const FInputDeviceRay& PressPos) override; + virtual void OnClickDrag(const FInputDeviceRay& DragPos) override; + virtual void OnClickRelease(const FInputDeviceRay& ReleasePos) override; + virtual void OnTerminateDragSequence() override; + + // UInteractiveTool overrides + virtual void Setup() override; + virtual void Shutdown(EToolShutdownType ShutdownType) override; + virtual void Render(IToolsContextRenderAPI* RenderAPI) override; + virtual void OnTick(float DeltaTime) override; + + virtual TArray GetToolProperties(bool bEnabledOnly = true) const override; + // End interfaces + + void SetTrailToolName(const FString& InTrailToolName) { TrailToolName = InTrailToolName; } + void SetMotionTrailEditorMode(const TWeakObjectPtr InEditorMode) { EditorMode = InEditorMode; } + void SetWorld(UWorld* InTargetWorld, UInteractiveGizmoManager* InGizmoManager) + { + TargetWorld = InTargetWorld; + GizmoManager = InGizmoManager; + } + + UWorld* GetWorld() const { return TargetWorld; } + UInteractiveGizmoManager* GetGizmoManager() const { return GizmoManager; } + const TArray>& GetSelection() const { return ComponentTargets; } + + + static FString TrailKeyTransformGizmoInstanceIdentifier; + +protected: + UPROPERTY(Transient) + TArray ToolProperties; + + UWorld* TargetWorld; + UInteractiveGizmoManager* GizmoManager; + + FString TrailToolName; + TWeakObjectPtr EditorMode; +}; diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/Trail.h b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/Trail.h new file mode 100644 index 000000000000..695f629085ff --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/Trail.h @@ -0,0 +1,96 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Misc/Guid.h" +#include "Containers/Map.h" +#include "Containers/Array.h" +#include "Math/Range.h" +#include "Math/Color.h" +#include "UObject/WeakObjectPtrTemplates.h" +#include "GameFramework/Actor.h" + +#include "TrajectoryCache.h" + +class FTrajectoryDrawInfo; +class FInteractiveTrailTool; +struct FTrailHierarchyNode; + +enum class ETrailCacheState +{ + UpToDate, + Stale, + Dead +}; + +class FTrail +{ +public: + + FTrail() + : bForceEvaluateNextTick(true) + {} + + virtual ~FTrail() {} + + // Public interface methods + struct FSceneContext + { + FGuid YourNode; + const class FTrailEvaluateTimes& EvalTimes; + class FTrailHierarchy* TrailHierarchy; + TMap ParentCacheStates; + }; + + virtual ETrailCacheState UpdateTrail(const FSceneContext& InSceneContext) = 0; + virtual FTrajectoryCache* GetTrajectoryTransforms() = 0; + + // Optionally implemented methods + virtual FTrajectoryDrawInfo* GetDrawInfo() { return nullptr; } + virtual TMap GetTools() { return TMap(); } + virtual TRange GetEffectiveRange() const { return TRange::Empty(); } + + void ForceEvaluateNextTick() { bForceEvaluateNextTick = true; } + +protected: + bool bForceEvaluateNextTick; +}; + +class FRootTrail : public FTrail +{ +public: + FRootTrail() + : FTrail() + , TrajectoryCache(MakeUnique()) + {} + + virtual ETrailCacheState UpdateTrail(const FSceneContext& InSceneContext) override; + virtual FTrajectoryCache* GetTrajectoryTransforms() override { return TrajectoryCache.Get(); } + +private: + TUniquePtr TrajectoryCache; +}; + +class FConstantComponentTrail : public FTrail +{ +public: + + FConstantComponentTrail(TWeakObjectPtr InWeakComponent) + : FTrail() + , CachedEffectiveRange(TRange::Empty()) + , WeakComponent(InWeakComponent) + , LastLocalTransform(InWeakComponent->GetRelativeTransform()) + , TrajectoryCache(MakeUnique(0.01, TRange::Empty())) + {} + + virtual ETrailCacheState UpdateTrail(const FSceneContext& InSceneContext) override; + virtual FTrajectoryCache* GetTrajectoryTransforms() override { return TrajectoryCache.Get(); } + virtual TRange GetEffectiveRange() const override { return CachedEffectiveRange; } + +private: + TRange CachedEffectiveRange; + TWeakObjectPtr WeakComponent; + FTransform LastLocalTransform; + + TUniquePtr TrajectoryCache; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/TrailHierarchy.h b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/TrailHierarchy.h new file mode 100644 index 000000000000..a21236db925c --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/TrailHierarchy.h @@ -0,0 +1,84 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Trail.h" +#include "EditorViewportClient.h" + +#include "SceneView.h" +#include "UnrealClient.h" + +class ITrailHierarchyRenderer +{ +public: + virtual ~ITrailHierarchyRenderer() {} + virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) = 0; + virtual void DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) = 0; +}; + +class FTrailHierarchyRenderer : public ITrailHierarchyRenderer +{ +public: + FTrailHierarchyRenderer(class FTrailHierarchy* InOwningHierarchy) + : OwningHierarchy(InOwningHierarchy) + {} + + virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override; + virtual void DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) override; + +private: + class FTrailHierarchy* OwningHierarchy; +}; + +struct FTrailHierarchyNode +{ + FTrailHierarchyNode() + : Parents() + , Children() + {} + + TArray Parents; + TArray Children; +}; + +class FTrailHierarchy +{ +public: + + FTrailHierarchy(TWeakObjectPtr InWeakEditorMode) + : ViewRange(TRange::All()) + , RootTrailGuid(FGuid()) + , AllTrails() + , Hierarchy() + , WeakEditorMode(InWeakEditorMode) + {} + + virtual ~FTrailHierarchy() {} + + virtual void Initialize() = 0; + virtual void Destroy() = 0; // TODO: make dtor? + virtual ITrailHierarchyRenderer* GetRenderer() const = 0; + virtual double GetSecondsPerFrame() const = 0; + + // Optionally implemented methods + virtual void Update(); + + virtual void AddTrail(const FGuid& Key, const FTrailHierarchyNode& Node, TUniquePtr&& TrailPtr); + virtual void RemoveTrail(const FGuid& Key); + + const TRange& GetViewRange() const { return ViewRange; } + FGuid GetRootTrailGuid() const { return RootTrailGuid; } + const TMap>& GetAllTrails() const { return AllTrails; } + const TMap& GetHierarchy() const { return Hierarchy; } + const class UMotionTrailEditorMode* GetEditorMode() const { return WeakEditorMode.Get(); } + +protected: + + TRange ViewRange; + + FGuid RootTrailGuid; + TMap> AllTrails; + TMap Hierarchy; + + TWeakObjectPtr WeakEditorMode; +}; diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/TrajectoryCache.h b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/TrajectoryCache.h new file mode 100644 index 000000000000..930b032ae63d --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/TrajectoryCache.h @@ -0,0 +1,108 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Containers/ArrayView.h" +#include "Containers/Map.h" +#include "UObject/WeakObjectPtrTemplates.h" +#include "Misc/Optional.h" +#include "Math/Range.h" +#include "Math/Transform.h" + +namespace UE +{ + bool RangesContain(const TSet>& Ranges, const double Time); +} + +// Intermediate range reprsentation structure +class FTrailEvaluateTimes +{ +public: + FTrailEvaluateTimes(TArrayView InEvalTimes, TOptional InSpacing) + : EvalTimes(InEvalTimes) + , Spacing(InSpacing) + , Range(TRange(InEvalTimes[0], InEvalTimes.Last())) + {} + + TArrayView EvalTimes; + TOptional Spacing; + TRange Range; +}; + +class FTrajectoryCache +{ +public: + FTrajectoryCache() + : CoveredRanges() + , Default(FTransform::Identity) + {} + + FTrajectoryCache(const FTransform& InDefault) + : CoveredRanges() + , Default(InDefault) + {} + + virtual ~FTrajectoryCache() {} + + // alternatively, GetEvaluateTimes(const FTrailEvaluateTimes&) and GetInterpEvaluateTimes(const FTrailEvaluateTimes&) + virtual const FTransform& Get(const double InTime) const = 0; + virtual FTransform GetInterp(const double InTime) const = 0; + virtual TArray GetAllTimesInRange(const TRange& InRange) const = 0; + + virtual void Set(const double InTime, const FTransform& InValue) = 0; + + // Optionally implemented + virtual const FTransform& GetDefault() const { return Default; }; + virtual void UpdateCacheTimes(FTrailEvaluateTimes& InOutEvaluateTimes); + +protected: + TSet> CoveredRanges; + FTransform Default; +}; + +class FArrayTrajectoryCache : public FTrajectoryCache +{ +public: + + // for FRootTrail + FArrayTrajectoryCache() + : FTrajectoryCache() + , TrajectoryCache() + , TrackRange() + , Spacing() + {} + + FArrayTrajectoryCache(const double InSpacing, const TRange& InTrackRange, FTransform InDefault = FTransform::Identity) + : FTrajectoryCache(InDefault) + , TrajectoryCache() + , TrackRange(TRange(FMath::FloorToDouble(InTrackRange.GetLowerBoundValue() / InSpacing) * InSpacing, FMath::FloorToDouble(InTrackRange.GetUpperBoundValue() / InSpacing) * InSpacing)) // snap to spacing + , Spacing(InSpacing) + { + TrajectoryCache.SetNumUninitialized(int32(InTrackRange.Size() / InSpacing) + 1); + } + + virtual const FTransform& Get(const double InTime) const override + { + return TrajectoryCache.Num() > 0 ? TrajectoryCache[FMath::Clamp(int32((InTime - TrackRange.GetLowerBoundValue()) / Spacing), 0, TrajectoryCache.Num() - 1)] : Default; + } + + virtual FTransform GetInterp(const double InTime) const override; + virtual TArray GetAllTimesInRange(const TRange& InRange) const override; + + virtual void Set(const double InTime, const FTransform& InValue) override + { + if (TrajectoryCache.Num() > 0) + { + TrajectoryCache[FMath::Clamp(int32((InTime - TrackRange.GetLowerBoundValue()) / Spacing), 0, TrajectoryCache.Num() - 1)] = InValue; + } + } + + const FTransform& GetDefault() const { return Default; } + const TRange& GetTrackRange() const { return TrackRange; } + +private: + TArray TrajectoryCache; + FTransform Default; + TRange TrackRange; + double Spacing; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/TrajectoryDrawInfo.h b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/TrajectoryDrawInfo.h new file mode 100644 index 000000000000..99c6b843ad71 --- /dev/null +++ b/Engine/Plugins/Experimental/MotionTrailEditorMode/Source/MotionTrailEditorMode/Public/TrajectoryDrawInfo.h @@ -0,0 +1,88 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Misc/Guid.h" +#include "Containers/Map.h" +#include "Containers/Array.h" +#include "Math/Color.h" +#include "Math/Range.h" +#include "Math/Vector.h" +#include "Misc/Optional.h" + +#include "SceneView.h" +#include "UnrealClient.h" + +class FTrail; +struct FTrailHierarchyNode; +class FMapTrajectoryCache; +class FTrajectoryCache; + +class FTrailScreenSpaceTransform +{ +public: + FTrailScreenSpaceTransform(const FSceneView* InView, FViewport* InViewport, const float InDPIScale = 1.0f) + : View(InView) + , HalfScreenSize((InViewport->GetSizeXY().X / InDPIScale) * 0.5, (InViewport->GetSizeXY().Y / InDPIScale) * 0.5) + {} + + TOptional ProjectPoint(const FVector& Point) const; + +private: + const FSceneView* View; + FVector2D HalfScreenSize; +}; + +class FTrajectoryDrawInfo +{ +public: + + FTrajectoryDrawInfo(const FLinearColor& InColor, const bool bInIsVisible) + : Color(InColor) + , bIsVisible(bInIsVisible) + , CachedViewRange(TRange::Empty()) + {} + + virtual ~FTrajectoryDrawInfo() {} + + struct FDisplayContext + { + FGuid YourNode; + const FTrailScreenSpaceTransform& ScreenSpaceTransform; + double SecondsPerTick; + const TRange& TimeRange; + class FTrailHierarchy* TrailHierarchy; + }; + + virtual TArray GetTrajectoryPointsForDisplay(const FDisplayContext& InDisplayContext) = 0; + virtual void GetTickPointsForDisplay(const FDisplayContext& InDisplayContext, TArray& Ticks, TArray& TickTangents) = 0; + + // Optionally implemented methods + void SetColor(const FLinearColor& InColor) { Color = InColor; } + FLinearColor GetColor() const { return Color; } + + void SetIsVisible(bool bInIsVisible) { bIsVisible = bInIsVisible; } + bool IsVisible() const { return bIsVisible; } + + virtual const TRange& GetCachedViewRange() const { return CachedViewRange; } +protected: + FLinearColor Color; + bool bIsVisible; + TRange CachedViewRange; +}; + +class FCachedTrajectoryDrawInfo : public FTrajectoryDrawInfo +{ +public: + + FCachedTrajectoryDrawInfo(const FLinearColor& InColor, const bool bInIsVisible, FTrajectoryCache* InTrajectoryCache) + : FTrajectoryDrawInfo(InColor, bInIsVisible) + , TrajectoryCache(InTrajectoryCache) + {} + + virtual TArray GetTrajectoryPointsForDisplay(const FDisplayContext& InDisplayContext) override; + virtual void GetTickPointsForDisplay(const FDisplayContext& InDisplayContext, TArray& Ticks, TArray& TickNormals) override; + +private: + FTrajectoryCache* TrajectoryCache; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCryptoOpenSSL/Private/PlatformCryptoAesEncryptorsOpenSSL.cpp b/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCryptoOpenSSL/Private/PlatformCryptoAesEncryptorsOpenSSL.cpp index c3261abd0535..93eddd7e77f8 100644 --- a/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCryptoOpenSSL/Private/PlatformCryptoAesEncryptorsOpenSSL.cpp +++ b/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCryptoOpenSSL/Private/PlatformCryptoAesEncryptorsOpenSSL.cpp @@ -59,15 +59,19 @@ int32 FPlatformCryptoEncryptor_AES_Base_OpenSSL::GetCipherInitializationVectorSi EPlatformCryptoResult FPlatformCryptoEncryptor_AES_Base_OpenSSL::GenerateAuthTag(const TArrayView OutAuthTag, int32& OutAuthTagBytesWritten) const { + OutAuthTagBytesWritten = 0; + if (State != EEncryptorState::Finalized) { UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FPlatformCryptoEncryptor_AES_Base_OpenSSL::GenerateAuthTag: Invalid state. Was %s, but should be Finalized"), LexToString(State)); return EPlatformCryptoResult::Failure; } - if (OutAuthTag.Num() < GetCipherAuthTagSizeBytes()) + const int32 AuthTagSizeBytes = GetCipherAuthTagSizeBytes(); + + if (OutAuthTag.Num() < AuthTagSizeBytes) { - UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FPlatformCryptoDecryptor_AES_Base_OpenSSL::GenerateAuthTag: Invalid AuthTag Size. TagSize=[%d] Expected=[%d]"), OutAuthTag.Num(), GetCipherAuthTagSizeBytes()); + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FPlatformCryptoDecryptor_AES_Base_OpenSSL::GenerateAuthTag: Invalid AuthTag Size. TagSize=[%d] Expected=[%d]"), OutAuthTag.Num(), AuthTagSizeBytes); return EPlatformCryptoResult::Failure; } @@ -78,6 +82,8 @@ EPlatformCryptoResult FPlatformCryptoEncryptor_AES_Base_OpenSSL::GenerateAuthTag return EPlatformCryptoResult::Failure; } + OutAuthTagBytesWritten = AuthTagSizeBytes; + return EPlatformCryptoResult::Success; } @@ -285,7 +291,7 @@ int32 FPlatformCryptoEncryptor_AES_256_GCM_OpenSSL::GetUpdateBufferSizeBytes(con int32 FPlatformCryptoEncryptor_AES_256_GCM_OpenSSL::GetFinalizeBufferSizeBytes() const { - return 0; + return GetCipherBlockSizeBytes(); } // Undef what we defined above diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp index 3398ab312542..ebabcf47a534 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp @@ -326,6 +326,7 @@ public: // NB: rounding errors in the inverse may have resulted in error in this col. // openvdb explicitly checks this matrix row to insure the tranform is affine and will throw VDBMatDouble.setCol(3, openvdb::Vec4R(0, 0, 0, 1)); + OpenVDBTransform::Ptr LocalXForm = OpenVDBTransform::createLinearTransform(VDBMatDouble); // Create a wrapper with OpenVDB semantics. @@ -450,7 +451,12 @@ private: TransformMatrix = LocalToVoxel * TransformMatrix; float* data = &TransformMatrix.M[0][0]; openvdb::math::Mat4 VDBMatFloat(data); - return openvdb::Mat4R(VDBMatFloat); + + openvdb::Mat4R VDBMatDouble(VDBMatFloat); + // NB: rounding errors in the inverse may have resulted in error in this col. + // openvdb explicitly checks this matrix row to insure the transform is affine and will throw + VDBMatDouble.setCol(3, openvdb::Vec4R(0, 0, 0, 1)); + return VDBMatDouble; }; openvdb::Mat4R XFormA = TransformGenerator(PlacedMeshA); diff --git a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PythonScriptPlugin.cpp b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PythonScriptPlugin.cpp index 58f9dc1c4683..3592e0eb6173 100644 --- a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PythonScriptPlugin.cpp +++ b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PythonScriptPlugin.cpp @@ -28,6 +28,7 @@ #include "Containers/Ticker.h" #include "Features/IModularFeatures.h" #include "ProfilingDebugging/ScopedTimers.h" +#include "Stats/Stats.h" #if WITH_EDITOR #include "EditorSupportDelegates.h" @@ -763,6 +764,7 @@ void FPythonScriptPlugin::InitializePython() // Initialize the tick handler TickHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([this](float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FPythonScriptPlugin_Tick); Tick(DeltaTime); return true; })); @@ -958,6 +960,8 @@ void FPythonScriptPlugin::RequestStubCodeGeneration() ModuleDelayedHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda( [this](float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FPythonScriptPlugin_ModuleDelayed); + // Once ticked, the delegate will be removed so reset the handle to indicate that it isn't set. ModuleDelayedHandle.Reset(); diff --git a/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalMeshReductionPlugin.cpp b/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalMeshReductionPlugin.cpp index 1d8bb2acbe0f..3df19d11ac51 100644 --- a/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalMeshReductionPlugin.cpp +++ b/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalMeshReductionPlugin.cpp @@ -1068,6 +1068,13 @@ float FQuadricSkeletalMeshReduction::SimplifyMesh( const FSkeletalMeshOptimizati Mesh.Empty(); + float PercentNMEdges = FMath::FloorToInt(10000.f * Simplifier.FractionNonManifoldEdges())/100.f; + FString PrettyPercentNMEdges = FString::SanitizeFloat(PercentNMEdges); + + if (PercentNMEdges != 0.f) + { + UE_LOG(LogSkeletalMeshReduction, Log, TEXT("%s-percent of edges are shared by more than two triangles, these may cause problems."), *PrettyPercentNMEdges); + } // Add additional control parameters to the simplifier. { diff --git a/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifier.h b/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifier.h index 303b1cb40fd2..0da720b5e9b1 100644 --- a/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifier.h +++ b/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifier.h @@ -197,6 +197,11 @@ namespace SkeletalSimplifier */ int GetNumTris() const { return MeshManager.ReducedNumTris; } + /** + * The fraction of non-manifold edges (edges with more than 2 adjacent tris). + */ + float FractionNonManifoldEdges() { return MeshManager.FractionNonManifoldEdges(); } + /** * Export a copy of the simplified mesh. * diff --git a/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifierMeshManager.cpp b/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifierMeshManager.cpp index 9092ead77be7..f9694c6e35e0 100644 --- a/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifierMeshManager.cpp +++ b/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifierMeshManager.cpp @@ -737,6 +737,111 @@ void SkeletalSimplifier::FSimplifierMeshManager::RebuildEdgeLinkLists(EdgePtrArr } +void SkeletalSimplifier::FSimplifierMeshManager::VisitEdges(TFunctionRef EdgeVisitor) +{ + + TArray< SimpVertType*, TInlineAllocator<64> > adjVerts; + if (NumSrcVerts == 0 || NumSrcTris == 0) + { + //Avoid trying to compute an empty mesh + return; + } + + // clear the mark2 flags. We use these to determine if we have visited a vert group. + for (int32 i = 0; i < NumSrcVerts; ++i) + { + SimpVertType* v0 = &VertArray[i]; + v0->DisableFlags(SIMP_MARK2); + } + + for (int i = 0; i < NumSrcVerts; i++) + { + + SimpVertType* v0 = &VertArray[i]; + checkSlow(v0 != NULL); + check(v0->adjTris.Num() > 0); + + // we have already visited this vertex group + if (v0->TestFlags(SIMP_MARK2)) + { + continue; + } + + if (v0->TestFlags(SIMP_REMOVED)) + { + continue; + } + + //Find all the verts that are adjacent to any vert in this group. + adjVerts.Reset(); + SimpVertType* v0Smallest = v0; + { + SimpVertType* v = v0; + do { + for (TriIterator triIter = v->adjTris.Begin(); triIter != v->adjTris.End(); ++triIter) + { + for (int j = 0; j < 3; j++) + { + SimpVertType* TriVert = (*triIter)->verts[j]; + if (TriVert != v) + { + adjVerts.AddUnique(TriVert); + } + } + } + v = v->next; + if (v0Smallest > v) + { + v0Smallest = v; + } + } while (v != v0); + } + + for (SimpVertType* v1 : adjVerts) + { + // visit edges that are incoming to this vertex group + // note, we may end up visiting a few edges twice. + if (v0Smallest < v1) + { + + // set if this edge is boundary + // find faces that share v0 and v1 + v0->EnableAdjTriFlagsGroup(SIMP_MARK1); + v1->DisableAdjTriFlagsGroup(SIMP_MARK1); + + int32 AdjFaceCount = 0; + SimpVertType* vert = v0; + do + { + for (TriIterator j = vert->adjTris.Begin(); j != vert->adjTris.End(); ++j) + { + SimpTriType* tri = *j; + AdjFaceCount += tri->TestFlags(SIMP_MARK1) ? 0 : 1; + } + vert = vert->next; + } while (vert != v0); + + // reset v0-group flag. + v0->DisableAdjTriFlagsGroup(SIMP_MARK1); + + // process this edge. + EdgeVisitor(v0, v1, AdjFaceCount); + } + } + + // visited this vert and all the incoming edges. + v0->EnableFlagsGroup(SIMP_MARK2); + } + + for (int32 i = 0; i < NumSrcVerts; ++i) + { + SimpVertType* v0 = &VertArray[i]; + v0->DisableFlags(SIMP_MARK2); + } +} + + +//[TODO] convert this to use the VisitEdges method. void SkeletalSimplifier::FSimplifierMeshManager::FlagBoundary(const ESimpElementFlags Flag) { @@ -754,6 +859,7 @@ void SkeletalSimplifier::FSimplifierMeshManager::FlagBoundary(const ESimpElement checkSlow(v0 != NULL); check(v0->adjTris.Num() > 0); + // not sure if this test is valid. if (v0->TestFlags(Flag)) { // we must have visited this vert already in a vert group @@ -1743,4 +1849,149 @@ int32 SkeletalSimplifier::FSimplifierMeshManager::CountDegenerateEdges() const } return DegenerateCount; -} \ No newline at end of file +} + +#if 0 +// disabled because static analysis insists that NonManifoldEdgeCounter.EdgeCount != 0 is always false. +float SkeletalSimplifier::FSimplifierMeshManager::FractionNonManifoldEdges(bool bLockNonManifoldEdges) +{ + + FNonManifoldEdgeCounter NonManifoldEdgeCounter; + NonManifoldEdgeCounter.EdgeCount = 0; + NonManifoldEdgeCounter.NumNonManifoldEdges = 0; + NonManifoldEdgeCounter.bLockNonManifoldEdges = bLockNonManifoldEdges; + + + VisitEdges(NonManifoldEdgeCounter); + + float FractionBadEdges = 0.f; + if (NonManifoldEdgeCounter.EdgeCount != 0) + { + FractionBadEdges = float(NonManifoldEdgeCounter.NumNonManifoldEdges) / NonManifoldEdgeCounter.EdgeCount; + } + + return FractionBadEdges; +} +#else + +float SkeletalSimplifier::FSimplifierMeshManager::FractionNonManifoldEdges(bool bLockNonManifoldEdges) +{ + + int32 NumVisitedEdges = 0; + int32 NumNonManifoldEdges = 0; + + TArray< SimpVertType*, TInlineAllocator<64> > adjVerts; + if (NumSrcVerts == 0 || NumSrcTris == 0) + { + //Avoid trying to compute an empty mesh + return 0.f; + } + + // clear the mark2 flags. We use these to determine if we have visited a vert group. + for (int32 i = 0; i < NumSrcVerts; ++i) + { + SimpVertType* v0 = &VertArray[i]; + v0->DisableFlags(SIMP_MARK2); + } + + for (int i = 0; i < NumSrcVerts; i++) + { + + SimpVertType* v0 = &VertArray[i]; + checkSlow(v0 != NULL); + check(v0->adjTris.Num() > 0); + + // we have already visited this vertex group + if (v0->TestFlags(SIMP_MARK2)) + { + continue; + } + + if (v0->TestFlags(SIMP_REMOVED)) + { + continue; + } + + //Find all the verts that are adjacent to any vert in this group. + adjVerts.Reset(); + SimpVertType* v0Smallest = v0; + { + SimpVertType* v = v0; + do { + for (TriIterator triIter = v->adjTris.Begin(); triIter != v->adjTris.End(); ++triIter) + { + for (int j = 0; j < 3; j++) + { + SimpVertType* TriVert = (*triIter)->verts[j]; + if (TriVert != v) + { + adjVerts.AddUnique(TriVert); + } + } + } + v = v->next; + if (v0Smallest > v) + { + v0Smallest = v; + } + } while (v != v0); + } + + for (SimpVertType* v1 : adjVerts) + { + // visit edges that are incoming to this vertex group + // note, we may end up visiting a few edges twice. + if (v0Smallest < v1) + { + + // set if this edge is boundary + // find faces that share v0 and v1 + v0->EnableAdjTriFlagsGroup(SIMP_MARK1); + v1->DisableAdjTriFlagsGroup(SIMP_MARK1); + + int32 AdjFaceCount = 0; + SimpVertType* vert = v0; + do + { + for (TriIterator j = vert->adjTris.Begin(); j != vert->adjTris.End(); ++j) + { + SimpTriType* tri = *j; + AdjFaceCount += tri->TestFlags(SIMP_MARK1) ? 0 : 1; + } + vert = vert->next; + } while (vert != v0); + + // reset v0-group flag. + v0->DisableAdjTriFlagsGroup(SIMP_MARK1); + + // process this edge. + { + NumVisitedEdges++; + if (AdjFaceCount > 2) + { + NumNonManifoldEdges++; + if (bLockNonManifoldEdges) + { + // lock these verts. + v0->EnableFlagsGroup(SIMP_LOCKED); + v1->EnableFlagsGroup(SIMP_LOCKED); + } + } + } + } + } + + // visited this vert and all the incoming edges. + v0->EnableFlagsGroup(SIMP_MARK2); + } + + for (int32 i = 0; i < NumSrcVerts; ++i) + { + SimpVertType* v0 = &VertArray[i]; + v0->DisableFlags(SIMP_MARK2); + } + + return float(NumNonManifoldEdges) / (float(NumVisitedEdges) +0.01f); +} + +#endif diff --git a/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifierMeshManager.h b/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifierMeshManager.h index 53deabb8ff68..97fbd529c663 100644 --- a/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifierMeshManager.h +++ b/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifierMeshManager.h @@ -90,6 +90,9 @@ namespace SkeletalSimplifier // Apply flag to edges when the IsDifferent(AVert, BVert) == true void FlagEdges(const TFunction IsDifferent, const ESimpElementFlags Flag); + // Visit each edge group, and call EdgeVisitor(VertexA, VertexB, NumAdjacentFaces) + void VisitEdges(TFunctionRef EdgeVisitor); + // Change the attributes on a given simplifier vert. void UpdateVertexAttributes(SimpVertType& Vertex, const MeshVertType& AttributeVert) { @@ -106,6 +109,9 @@ namespace SkeletalSimplifier // Count the number of edges with zero length. int32 CountDegenerateEdges() const; + // Fraction (0 to 1) of edge-groups with more than two adjacent tris, optionally lock these edges + float FractionNonManifoldEdges(bool bLockNonManifoldEdges = false); + // Hash location static uint32 HashPoint(const FVector& p) { @@ -512,5 +518,30 @@ namespace SkeletalSimplifier } }; + // struct used with VistEdges when counting nonmanifold edges. + struct FNonManifoldEdgeCounter + { + int32 EdgeCount; + int32 NumNonManifoldEdges; + bool bLockNonManifoldEdges; + + void operator()(SimpVertType* v0, SimpVertType* v1, int32 AdjFaceCount) + { + EdgeCount++; + if (AdjFaceCount > 2) + { + NumNonManifoldEdges++; + + if (bLockNonManifoldEdges) + { + // lock these verts. + v0->EnableFlagsGroup(SIMP_LOCKED); + v1->EnableFlagsGroup(SIMP_LOCKED); + } + } + } + + }; + }; } \ No newline at end of file diff --git a/Engine/Plugins/Experimental/WebSocketNetworking/Source/WebSocketNetworking/Classes/WebSocketConnection.h b/Engine/Plugins/Experimental/WebSocketNetworking/Source/WebSocketNetworking/Classes/WebSocketConnection.h index f33000c84b80..7ceaf3b79597 100644 --- a/Engine/Plugins/Experimental/WebSocketNetworking/Source/WebSocketNetworking/Classes/WebSocketConnection.h +++ b/Engine/Plugins/Experimental/WebSocketNetworking/Source/WebSocketNetworking/Classes/WebSocketConnection.h @@ -20,7 +20,7 @@ class WEBSOCKETNETWORKING_API UWebSocketConnection : public UNetConnection virtual void LowLevelSend(void* Data, int32 CountBits, FOutPacketTraits& Traits) override; FString LowLevelGetRemoteAddress(bool bAppendPort = false) override; FString LowLevelDescribe() override; - virtual void Tick(); + virtual void Tick(float DeltaSeconds) override; virtual void FinishDestroy(); virtual void ReceivedRawPacket(void* Data,int32 Count); //~ End NetConnection Interface diff --git a/Engine/Plugins/Experimental/WebSocketNetworking/Source/WebSocketNetworking/Private/WebsocketConnection.cpp b/Engine/Plugins/Experimental/WebSocketNetworking/Source/WebSocketNetworking/Private/WebsocketConnection.cpp index 21c7950df923..6f4bf2b47930 100644 --- a/Engine/Plugins/Experimental/WebSocketNetworking/Source/WebSocketNetworking/Private/WebsocketConnection.cpp +++ b/Engine/Plugins/Experimental/WebSocketNetworking/Source/WebSocketNetworking/Private/WebsocketConnection.cpp @@ -136,9 +136,9 @@ FWebSocket* UWebSocketConnection::GetWebSocket() return WebSocket; } -void UWebSocketConnection::Tick() +void UWebSocketConnection::Tick(float DeltaSeconds) { - UNetConnection::Tick(); + UNetConnection::Tick(DeltaSeconds); WebSocket->Tick(); } diff --git a/Engine/Plugins/FX/CascadeToNiagaraConverter/Source/CascadeToNiagaraConverter/Private/NiagaraStackGraphUtilitiesAdapterLibrary.cpp b/Engine/Plugins/FX/CascadeToNiagaraConverter/Source/CascadeToNiagaraConverter/Private/NiagaraStackGraphUtilitiesAdapterLibrary.cpp index 920c8976364e..8597a4462f87 100644 --- a/Engine/Plugins/FX/CascadeToNiagaraConverter/Source/CascadeToNiagaraConverter/Private/NiagaraStackGraphUtilitiesAdapterLibrary.cpp +++ b/Engine/Plugins/FX/CascadeToNiagaraConverter/Source/CascadeToNiagaraConverter/Private/NiagaraStackGraphUtilitiesAdapterLibrary.cpp @@ -51,6 +51,7 @@ #include "NiagaraDataInterfaceVector4Curve.h" #include "NiagaraMessages.h" #include "NiagaraTypes.h" +#include "Math/InterpCurvePoint.h" #include "Distributions/DistributionFloatConstant.h" #include "Distributions/DistributionFloatConstantCurve.h" @@ -69,6 +70,7 @@ #include "NiagaraNode.h" #include "NiagaraNodeFunctionCall.h" #include "CascadeToNiagaraConverterModule.h" +#include "Curves/RichCurve.h" TMap> UFXConverterUtilitiesLibrary::GuidToNiagaraEmitterHandleViewModelMap; @@ -158,7 +160,7 @@ UNiagaraScriptConversionContextInput* UFXConverterUtilitiesLibrary::CreateScript UNiagaraClipboardFunctionInput* NewInput = UNiagaraClipboardEditorScriptingUtilities::CreateLinkedValueInput(GetTransientPackage(), FName(), InputTypeName, false, false, FName(ParameterNameString)); const FNiagaraTypeDefinition& TargetTypeDef = UNiagaraClipboardEditorScriptingUtilities::GetRegisteredTypeDefinitionByName(InputTypeName); UNiagaraScriptConversionContextInput* Input = NewObject(); - Input->Init(NewInput, TargetTypeDef); + Input->Init(NewInput, InputType, TargetTypeDef); return Input; } @@ -167,7 +169,16 @@ UNiagaraScriptConversionContextInput* UFXConverterUtilitiesLibrary::CreateScript UNiagaraClipboardFunctionInput* NewInput = UNiagaraClipboardEditorScriptingUtilities::CreateFloatLocalValueInput(GetTransientPackage(), FName(), false, false, Value); const FNiagaraTypeDefinition& TargetTypeDef = FNiagaraTypeDefinition::GetFloatDef(); UNiagaraScriptConversionContextInput* Input = NewObject(); - Input->Init(NewInput, TargetTypeDef); + Input->Init(NewInput, ENiagaraScriptInputType::Float, TargetTypeDef); + return Input; +} + +UNiagaraScriptConversionContextInput* UFXConverterUtilitiesLibrary::CreateScriptInputVec2(FVector2D Value) +{ + UNiagaraClipboardFunctionInput* NewInput = UNiagaraClipboardEditorScriptingUtilities::CreateVec2LocalValueInput(GetTransientPackage(), FName(), false, false, Value); + const FNiagaraTypeDefinition& TargetTypeDef = FNiagaraTypeDefinition::GetVec2Def(); + UNiagaraScriptConversionContextInput* Input = NewObject(); + Input->Init(NewInput, ENiagaraScriptInputType::Vec2, TargetTypeDef); return Input; } @@ -176,7 +187,7 @@ UNiagaraScriptConversionContextInput* UFXConverterUtilitiesLibrary::CreateScript UNiagaraClipboardFunctionInput* NewInput = UNiagaraClipboardEditorScriptingUtilities::CreateVec3LocalValueInput(GetTransientPackage(), FName(), false, false, Value); const FNiagaraTypeDefinition& TargetTypeDef = FNiagaraTypeDefinition::GetVec3Def(); UNiagaraScriptConversionContextInput* Input = NewObject(); - Input->Init(NewInput, TargetTypeDef); + Input->Init(NewInput, ENiagaraScriptInputType::Vec3, TargetTypeDef); return Input; } @@ -186,7 +197,7 @@ UNiagaraScriptConversionContextInput* UFXConverterUtilitiesLibrary::CreateScript if (NewInput != nullptr) { UNiagaraScriptConversionContextInput* Input = NewObject(); - Input->Init(NewInput, NewInput->GetTypeDef()); + Input->Init(NewInput, ENiagaraScriptInputType::Struct, NewInput->GetTypeDef()); return Input; } return nullptr; @@ -198,7 +209,7 @@ UNiagaraScriptConversionContextInput* UFXConverterUtilitiesLibrary::CreateScript if (NewInput != nullptr) { UNiagaraScriptConversionContextInput* Input = NewObject(); - Input->Init(NewInput, NewInput->GetTypeDef()); + Input->Init(NewInput, ENiagaraScriptInputType::Enum, NewInput->GetTypeDef()); return Input; } return nullptr; @@ -209,7 +220,7 @@ UNiagaraScriptConversionContextInput* UFXConverterUtilitiesLibrary::CreateScript UNiagaraClipboardFunctionInput* NewInput = UNiagaraClipboardEditorScriptingUtilities::CreateIntLocalValueInput(GetTransientPackage(), FName(), false, false, Value); const FNiagaraTypeDefinition& TargetTypeDef = FNiagaraTypeDefinition::GetIntDef(); UNiagaraScriptConversionContextInput* Input = NewObject(); - Input->Init(NewInput, TargetTypeDef); + Input->Init(NewInput, ENiagaraScriptInputType::Int, TargetTypeDef); return Input; } @@ -229,7 +240,8 @@ UNiagaraScriptConversionContextInput* UFXConverterUtilitiesLibrary::CreateScript NewInput->Dynamic->Inputs = DynamicInputScriptContext->GetClipboardFunctionInputs(); const FNiagaraTypeDefinition& TargetTypeDef = UNiagaraClipboardEditorScriptingUtilities::GetRegisteredTypeDefinitionByName(InputTypeName); UNiagaraScriptConversionContextInput* Input = NewObject(); - Input->Init(NewInput, TargetTypeDef); + Input->Init(NewInput, InputType, TargetTypeDef); + Input->StackMessages = DynamicInputScriptContext->GetStackMessages(); return Input; } @@ -245,7 +257,7 @@ UNiagaraScriptConversionContextInput* UFXConverterUtilitiesLibrary::CreateScript if (NewInput != nullptr) { UNiagaraScriptConversionContextInput* Input = NewObject(); - Input->Init(NewInput, NewInput->GetTypeDef()); + Input->Init(NewInput, ENiagaraScriptInputType::DataInterface, NewInput->GetTypeDef()); return Input; } return nullptr; @@ -261,24 +273,57 @@ UNiagaraMeshRendererProperties* UFXConverterUtilitiesLibrary::CreateMeshRenderer return NewObject(); } -UNiagaraDataInterfaceCurve* UFXConverterUtilitiesLibrary::CreateFloatCurveDI() +UNiagaraDataInterfaceCurve* UFXConverterUtilitiesLibrary::CreateFloatCurveDI(TArray Keys) { - return NewObject(); + UNiagaraDataInterfaceCurve* DI_Curve = NewObject(); + const TArray BaseKeys = FRichCurveKeyBP::KeysToBase(Keys); + DI_Curve->Curve.SetKeys(BaseKeys); + return DI_Curve; } -UNiagaraDataInterfaceVector2DCurve* UFXConverterUtilitiesLibrary::CreateVec2CurveDI() +UNiagaraDataInterfaceVector2DCurve* UFXConverterUtilitiesLibrary::CreateVec2CurveDI(TArray X_Keys, TArray Y_Keys) { - return NewObject(); + UNiagaraDataInterfaceVector2DCurve* DI_Curve = NewObject(); + const TArray X_BaseKeys = FRichCurveKeyBP::KeysToBase(X_Keys); + const TArray Y_BaseKeys = FRichCurveKeyBP::KeysToBase(Y_Keys); + DI_Curve->XCurve.SetKeys(X_BaseKeys); + DI_Curve->YCurve.SetKeys(Y_BaseKeys); + return DI_Curve; } -UNiagaraDataInterfaceVectorCurve* UFXConverterUtilitiesLibrary::CreateVec3CurveDI() +UNiagaraDataInterfaceVectorCurve* UFXConverterUtilitiesLibrary::CreateVec3CurveDI( + TArray X_Keys, + TArray Y_Keys, + TArray Z_Keys + ) { - return NewObject(); + UNiagaraDataInterfaceVectorCurve* DI_Curve = NewObject(); + const TArray X_BaseKeys = FRichCurveKeyBP::KeysToBase(X_Keys); + const TArray Y_BaseKeys = FRichCurveKeyBP::KeysToBase(Y_Keys); + const TArray Z_BaseKeys = FRichCurveKeyBP::KeysToBase(Z_Keys); + DI_Curve->XCurve.SetKeys(X_BaseKeys); + DI_Curve->YCurve.SetKeys(Y_BaseKeys); + DI_Curve->ZCurve.SetKeys(Z_BaseKeys); + return DI_Curve; } -UNiagaraDataInterfaceVector4Curve* UFXConverterUtilitiesLibrary::CreateVec4CurveDI() +UNiagaraDataInterfaceVector4Curve* UFXConverterUtilitiesLibrary::CreateVec4CurveDI( + TArray X_Keys, + TArray Y_Keys, + TArray Z_Keys, + TArray W_Keys + ) { - return NewObject(); + UNiagaraDataInterfaceVector4Curve* DI_Curve = NewObject(); + const TArray X_BaseKeys = FRichCurveKeyBP::KeysToBase(X_Keys); + const TArray Y_BaseKeys = FRichCurveKeyBP::KeysToBase(Y_Keys); + const TArray Z_BaseKeys = FRichCurveKeyBP::KeysToBase(Z_Keys); + const TArray W_BaseKeys = FRichCurveKeyBP::KeysToBase(W_Keys); + DI_Curve->XCurve.SetKeys(X_BaseKeys); + DI_Curve->YCurve.SetKeys(Y_BaseKeys); + DI_Curve->ZCurve.SetKeys(Z_BaseKeys); + DI_Curve->WCurve.SetKeys(W_BaseKeys); + return DI_Curve; } UNiagaraSystemConversionContext* UFXConverterUtilitiesLibrary::CreateSystemConversionContext(UNiagaraSystem* InSystem) @@ -297,139 +342,10 @@ UNiagaraSystemConversionContext* UFXConverterUtilitiesLibrary::CreateSystemConve return SystemConversionContext; } -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleSpawnClass() +void UFXConverterUtilitiesLibrary::GetParticleModuleTypeDataGpuProps(UParticleModuleTypeDataGpu* ParticleModule) { - return UParticleModuleSpawn::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleRequiredClass() -{ - return UParticleModuleRequired::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleColorClass() -{ - return UParticleModuleColor::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleColorOverLifeClass() -{ - return UParticleModuleColorOverLife::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleLifetimeClass() -{ - return UParticleModuleLifetime::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleSizeClass() -{ - return UParticleModuleSize::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleVelocityClass() -{ - return UParticleModuleVelocity::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleTypeDataGPUClass() -{ - return UParticleModuleTypeDataGpu::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleTypeDataMeshClass() -{ - return UParticleModuleTypeDataMesh::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleConstantAccelerationClass() -{ - return UParticleModuleAccelerationConstant::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleLocationPrimitiveSphereClass() -{ - return UParticleModuleLocationPrimitiveSphere::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleMeshRotationClass() -{ - return UParticleModuleMeshRotation::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleCollisionClass() -{ - return UParticleModuleCollision::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleSizeScaleBySpeedClass() -{ - return UParticleModuleSizeScaleBySpeed::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleVectorFieldLocalClass() -{ - return UParticleModuleVectorFieldLocal::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleVectorFieldRotationRateClass() -{ - return UParticleModuleVectorFieldRotationRate::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleOrbitClass() -{ - return UParticleModuleOrbit::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleSizeMultipleLifeClass() -{ - return UParticleModuleSizeMultiplyLife::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleColorScaleOverLifeClass() -{ - return UParticleModuleColorScaleOverLife::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleRotationClass() -{ - return UParticleModuleRotation::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleRotationRateClass() -{ - return UParticleModuleRotationRate::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleSubUVClass() -{ - return UParticleModuleSubUV::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleCameraOffsetClass() -{ - return UParticleModuleCameraOffset::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleSubUVMovieClass() -{ - return UParticleModuleSubUVMovie::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleParameterDynamicClass() -{ - return UParticleModuleParameterDynamic::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleAccelerationDragClass() -{ - return UParticleModuleAccelerationDrag::StaticClass(); -} - -UClass* UFXConverterUtilitiesLibrary::GetParticleModuleAccelerationClass() -{ - return UParticleModuleAcceleration::StaticClass(); + // empty impl, method arg taking UParticleModuleTypeDataGpu exposes this UObject type to python scripting reflection + return; } void UFXConverterUtilitiesLibrary::GetParticleModuleTypeDataMeshProps( @@ -554,7 +470,8 @@ void UFXConverterUtilitiesLibrary::GetParticleModuleRequiredProps( , float& OutMaxFacingCameraBlendDistance , UTexture2D*& OutCutoutTexture , TEnumAsByte& OutBoundingMode - , TEnumAsByte& OutOpacitySourceMode) + , TEnumAsByte& OutOpacitySourceMode + , float& OutAlphaThreshold) { OutMaterialInterface = ParticleModuleRequired->Material; OutScreenAlignment = ParticleModuleRequired->ScreenAlignment; @@ -569,6 +486,7 @@ void UFXConverterUtilitiesLibrary::GetParticleModuleRequiredProps( OutCutoutTexture = ParticleModuleRequired->CutoutTexture; OutBoundingMode = ParticleModuleRequired->BoundingMode; OutOpacitySourceMode = ParticleModuleRequired->OpacitySourceMode; + OutAlphaThreshold = ParticleModuleRequired->AlphaThreshold; } void UFXConverterUtilitiesLibrary::GetParticleModuleColorProps(UParticleModuleColor* ParticleModule, UDistribution*& OutStartColor, UDistribution*& OutStartAlpha, bool& bOutClampAlpha) @@ -715,12 +633,12 @@ void UFXConverterUtilitiesLibrary::GetParticleModuleOrbitProps( OutRotationRateOptions = ParticleModule->RotationRateOptions; } -void UFXConverterUtilitiesLibrary::GetParticleModuleSizeMultipleLifeProps( +void UFXConverterUtilitiesLibrary::GetParticleModuleSizeMultiplyLifeProps( UParticleModuleSizeMultiplyLife* ParticleModule , UDistribution*& OutLifeMultiplier - , int32& OutMultiplyX - , int32& OutMultiplyY - , int32& OutMultiplyZ) + , bool& OutMultiplyX + , bool& OutMultiplyY + , bool& OutMultiplyZ) { OutLifeMultiplier = ParticleModule->LifeMultiplier.Distribution; OutMultiplyX = ParticleModule->MultiplyX; @@ -808,6 +726,173 @@ void UFXConverterUtilitiesLibrary::GetParticleModuleAccelerationProps(UParticleM bOutApplyOwnerScale = ParticleModule->bApplyOwnerScale; } +void UFXConverterUtilitiesLibrary::GetDistributionMinMaxValues( + UDistribution* Distribution + , bool& bOutSuccess + , FVector& OutMinValue + , FVector& OutMaxValue) +{ + if (Distribution->IsA()) + { + float DistributionValue = 0.0f; + GetFloatDistributionConstValues(static_cast(Distribution), DistributionValue); + bOutSuccess = true; + OutMinValue = FVector(DistributionValue, 0.0, 0.0); + OutMaxValue = FVector(DistributionValue, 0.0, 0.0); + return; + } + + else if (Distribution->IsA()) + { + FVector DistributionValue = FVector(0.0f); + GetVectorDistributionConstValues(static_cast(Distribution), DistributionValue); + bOutSuccess = true; + OutMinValue = DistributionValue; + OutMaxValue = DistributionValue; + return; + } + + else if (Distribution->IsA()) + { + UDistributionFloatConstantCurve* FloatCurveDistribution = static_cast(Distribution); + if (FloatCurveDistribution->ConstantCurve.Points.Num() == 0) + { + bOutSuccess = false; + return; + } + + float MinValue = FloatCurveDistribution->ConstantCurve.Points[0].OutVal; + float MaxValue = FloatCurveDistribution->ConstantCurve.Points[0].OutVal; + + if (FloatCurveDistribution->ConstantCurve.Points.Num() > 1) + { + for (int i = 1; i < FloatCurveDistribution->ConstantCurve.Points.Num(); ++i) + { + const float& OutVal = FloatCurveDistribution->ConstantCurve.Points[i].OutVal; + MinValue = OutVal < MinValue ? OutVal : MinValue; + MaxValue = OutVal > MaxValue ? OutVal : MaxValue; + } + } + + bOutSuccess = true; + OutMinValue = FVector(MinValue, 0.0, 0.0); + OutMaxValue = FVector(MaxValue, 0.0, 0.0); + return; + } + + else if (Distribution->IsA()) + { + UDistributionVectorConstantCurve* VectorCurveDistribution = static_cast(Distribution); + if (VectorCurveDistribution->ConstantCurve.Points.Num() == 0) + { + bOutSuccess = false; + return; + } + + OutMinValue = VectorCurveDistribution->ConstantCurve.Points[0].OutVal; + OutMaxValue = VectorCurveDistribution->ConstantCurve.Points[0].OutVal; + + if (VectorCurveDistribution->ConstantCurve.Points.Num() > 1) + { + for (int i = 1; i < VectorCurveDistribution->ConstantCurve.Points.Num(); ++i) + { + const FVector& OutVal = VectorCurveDistribution->ConstantCurve.Points[i].OutVal; + OutMinValue = OutVal.ComponentMin(OutMinValue); + OutMaxValue = OutVal.ComponentMax(OutMaxValue); + } + } + + bOutSuccess = true; + return; + } + + else if (Distribution->IsA()) + { + float DistributionValueMin = 0.0f; + float DistributionValueMax = 0.0f; + GetFloatDistributionUniformValues(static_cast(Distribution), DistributionValueMin, DistributionValueMax); + bOutSuccess = true; + OutMinValue = FVector(DistributionValueMin, 0.0, 0.0); + OutMaxValue = FVector(DistributionValueMax, 0.0, 0.0); + return; + } + + else if (Distribution->IsA()) + { + GetVectorDistributionUniformValues(static_cast(Distribution), OutMinValue, OutMaxValue); + bOutSuccess = true; + return; + } + + else if (Distribution->IsA()) + { + UDistributionFloatUniformCurve* FloatCurveDistribution = static_cast(Distribution); + if (FloatCurveDistribution->ConstantCurve.Points.Num() == 0) + { + bOutSuccess = false; + return; + } + + float MinValue = FloatCurveDistribution->ConstantCurve.Points[0].OutVal.X; + float MaxValue = FloatCurveDistribution->ConstantCurve.Points[0].OutVal.Y; + + if (FloatCurveDistribution->ConstantCurve.Points.Num() > 1) + { + for (int i = 1; i < FloatCurveDistribution->ConstantCurve.Points.Num(); ++i) + { + const FVector2D& OutVal = FloatCurveDistribution->ConstantCurve.Points[i].OutVal; + MinValue = OutVal.X < MinValue ? OutVal.X : MinValue; + MaxValue = OutVal.Y > MaxValue ? OutVal.Y : MaxValue; + } + } + + bOutSuccess = true; + OutMinValue = FVector(MinValue, 0.0, 0.0); + OutMaxValue = FVector(MaxValue, 0.0, 0.0); + return; + } + + else if (Distribution->IsA()) + { + UDistributionVectorUniformCurve* VectorCurveDistribution = static_cast(Distribution); + if (VectorCurveDistribution->ConstantCurve.Points.Num() == 0) + { + bOutSuccess = false; + return; + } + + OutMinValue = VectorCurveDistribution->ConstantCurve.Points[0].OutVal.v1; + OutMaxValue = VectorCurveDistribution->ConstantCurve.Points[0].OutVal.v2; + + if (VectorCurveDistribution->ConstantCurve.Points.Num() > 1) + { + for (int i = 1; i < VectorCurveDistribution->ConstantCurve.Points.Num(); ++i) + { + const FTwoVectors& OutVal = VectorCurveDistribution->ConstantCurve.Points[i].OutVal; + OutMinValue = OutVal.v1.ComponentMin(OutMinValue); + OutMaxValue = OutVal.v2.ComponentMax(OutMaxValue); + } + } + + bOutSuccess = true; + return; + } + + else if (Distribution->IsA()) + { + bOutSuccess = false; + return; + } + + else if (Distribution->IsA()) + { + bOutSuccess = false; + return; + } + + bOutSuccess = false; +} + void UFXConverterUtilitiesLibrary::GetDistributionType( UDistribution* Distribution , EDistributionType& OutDistributionType @@ -938,84 +1023,47 @@ void UFXConverterUtilitiesLibrary::GetVectorDistributionParameterValues(UDistrib OutMaxOutput = Distribution->MaxOutput; } -void UFXConverterUtilitiesLibrary::CopyCascadeFloatCurveToNiagaraCurveDI(UNiagaraDataInterfaceCurve* CurveDI, FInterpCurveFloat InterpCurveFloat) +TArray UFXConverterUtilitiesLibrary::KeysFromInterpCurveFloat(FInterpCurveFloat Curve) { - TArray NewRichCurveKeys; - for(FInterpCurvePoint Point : InterpCurveFloat.Points) - { - NewRichCurveKeys.Emplace(FRichCurveKey(Point)); + TArray Keys; + for (const FInterpCurvePoint& Point : Curve.Points) + { + Keys.Emplace(FRichCurveKey(Point)); } - FRichCurve NewRichCurve = FRichCurve(); - NewRichCurve.SetKeys(NewRichCurveKeys); - CurveDI->Curve = NewRichCurve; + return Keys; } -void UFXConverterUtilitiesLibrary::CopyCascadeVectorCurveToNiagaraCurveDI(UNiagaraDataInterfaceVectorCurve* CurveDI, FInterpCurveVector InterpCurveVector) +TArray UFXConverterUtilitiesLibrary::KeysFromInterpCurveVector(FInterpCurveVector Curve, int32 ComponentIdx) { - TArray NewRichCurveKeys_X; - TArray NewRichCurveKeys_Y; - TArray NewRichCurveKeys_Z; - for (FInterpCurvePoint Point : InterpCurveVector.Points) + TArray Keys; + for (const FInterpCurvePoint& Point : Curve.Points) { - NewRichCurveKeys_X.Emplace(FRichCurveKey(Point, 0)); - NewRichCurveKeys_Y.Emplace(FRichCurveKey(Point, 1)); - NewRichCurveKeys_Z.Emplace(FRichCurveKey(Point, 2)); + Keys.Emplace(FRichCurveKey(Point, ComponentIdx)); } - FRichCurve NewRichCurve_X = FRichCurve(); - FRichCurve NewRichCurve_Y = FRichCurve(); - FRichCurve NewRichCurve_Z = FRichCurve(); - NewRichCurve_X.SetKeys(NewRichCurveKeys_X); - NewRichCurve_Y.SetKeys(NewRichCurveKeys_Y); - NewRichCurve_Z.SetKeys(NewRichCurveKeys_Z); - CurveDI->XCurve = NewRichCurve_X; - CurveDI->YCurve = NewRichCurve_Y; - CurveDI->ZCurve = NewRichCurve_Z; + return Keys; } -void UFXConverterUtilitiesLibrary::CopyCascadeVector2DCurveToNiagaraCurveDI(UNiagaraDataInterfaceVector2DCurve* CurveDI, FInterpCurveVector2D InterpCurveVector2D) +TArray UFXConverterUtilitiesLibrary::KeysFromInterpCurveVector2D(FInterpCurveVector2D Curve, int32 ComponentIdx) { - TArray NewRichCurveKeys_X; - TArray NewRichCurveKeys_Y; - for (FInterpCurvePoint Point : InterpCurveVector2D.Points) + TArray Keys; + for (const FInterpCurvePoint& Point : Curve.Points) { - NewRichCurveKeys_X.Emplace(FRichCurveKey(Point, 0)); - NewRichCurveKeys_Y.Emplace(FRichCurveKey(Point, 1)); + Keys.Emplace(FRichCurveKey(Point, ComponentIdx)); } - FRichCurve NewRichCurve_X = FRichCurve(); - FRichCurve NewRichCurve_Y = FRichCurve(); - NewRichCurve_X.SetKeys(NewRichCurveKeys_X); - NewRichCurve_Y.SetKeys(NewRichCurveKeys_Y); - CurveDI->XCurve = NewRichCurve_X; - CurveDI->YCurve = NewRichCurve_Y; + return Keys; } -void UFXConverterUtilitiesLibrary::CopyCascadeTwoVectorCurveToNiagaraCurveDI(UNiagaraDataInterfaceVector4Curve* CurveDI, FInterpCurveTwoVectors InterpCurveTwoVectors) +TArray UFXConverterUtilitiesLibrary::KeysFromInterpCurveTwoVectors(FInterpCurveTwoVectors Curve, int32 ComponentIdx) { - TArray NewRichCurveKeys_X; - TArray NewRichCurveKeys_Y; - TArray NewRichCurveKeys_Z; - TArray NewRichCurveKeys_W; - for (FInterpCurvePoint Point : InterpCurveTwoVectors.Points) + TArray Keys; + for (const FInterpCurvePoint& Point : Curve.Points) { - NewRichCurveKeys_X.Emplace(FRichCurveKey(Point, 0)); - NewRichCurveKeys_Y.Emplace(FRichCurveKey(Point, 1)); - NewRichCurveKeys_Z.Emplace(FRichCurveKey(Point, 2)); - NewRichCurveKeys_W.Emplace(FRichCurveKey(Point, 3)); + Keys.Emplace(FRichCurveKey(Point, ComponentIdx)); } - FRichCurve NewRichCurve_X = FRichCurve(); - FRichCurve NewRichCurve_Y = FRichCurve(); - FRichCurve NewRichCurve_Z = FRichCurve(); - FRichCurve NewRichCurve_W = FRichCurve(); - NewRichCurve_X.SetKeys(NewRichCurveKeys_X); - NewRichCurve_Y.SetKeys(NewRichCurveKeys_Y); - NewRichCurve_Z.SetKeys(NewRichCurveKeys_Z); - NewRichCurve_W.SetKeys(NewRichCurveKeys_W); - CurveDI->XCurve = NewRichCurve_X; - CurveDI->YCurve = NewRichCurve_Y; - CurveDI->ZCurve = NewRichCurve_Z; - CurveDI->WCurve = NewRichCurve_W; + return Keys; } + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///// UNiagaraSystemConversionContext ///// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1074,7 +1122,7 @@ void UNiagaraEmitterConversionContext::AddModuleScript(UNiagaraScriptConversionC void UNiagaraEmitterConversionContext::SetParameterDirectly(FString ParameterNameString, UNiagaraScriptConversionContextInput* ParameterInput, EScriptExecutionCategory TargetExecutionCategory) { const FName ParameterName = FName(ParameterNameString); - const FNiagaraVariable TargetVariable = FNiagaraVariable(ParameterInput->TargetTypeDefinition, ParameterName); + const FNiagaraVariable TargetVariable = FNiagaraVariable(ParameterInput->TypeDefinition, ParameterName); const TArray InVariables = {TargetVariable}; const TArray InVariableDefaults = {FString()}; UNiagaraClipboardFunction* Assignment = UNiagaraClipboardFunction::CreateAssignmentFunction(this, "SetParameter", InVariables, InVariableDefaults); @@ -1154,23 +1202,20 @@ void UNiagaraEmitterConversionContext::Finalize() continue; } - UNiagaraStackItemGroup* const* StackItemGroup = GetStackItemGroupForScriptExecutionCategory(ExecutionCategory); - if (StackItemGroup == nullptr) + UNiagaraStackItemGroup* const* StackItemGroupPtr = GetStackItemGroupForScriptExecutionCategory(ExecutionCategory); + if (StackItemGroupPtr == nullptr) { return; } - TArray TargetStackEntryArr; - TargetStackEntryArr.Add(*StackItemGroup); - for(const int32 Idx : ParameterSetIndices.Indices) { UNiagaraClipboardContent* ClipboardContent = UNiagaraClipboardContent::Create(); ClipboardContent->Functions.Add(StagedParameterSets[Idx]); - FNiagaraEditorModule::Get().GetClipboard().SetClipboardContent(ClipboardContent); FText PasteWarning = FText(); - FNiagaraStackClipboardUtilities::PasteSelection(TargetStackEntryArr, PasteWarning); + UNiagaraStackItemGroup* StackItemGroup = *StackItemGroupPtr; + StackItemGroup->Paste(ClipboardContent, PasteWarning); if (PasteWarning.IsEmpty() == false) { @@ -1194,7 +1239,7 @@ void UNiagaraEmitterConversionContext::Finalize() UNiagaraClipboardContent* ClipboardContent = UNiagaraClipboardContent::Create(); UNiagaraScript* NiagaraScript = StagedScriptContext->GetScript(); - UNiagaraClipboardFunction* ClipboardFunction = UNiagaraClipboardFunction::CreateScriptFunction(ClipboardContent, "Function", NiagaraScript); //@todo(ng) proper name here + UNiagaraClipboardFunction* ClipboardFunction = UNiagaraClipboardFunction::CreateScriptFunction(ClipboardContent, "Function", NiagaraScript); ClipboardFunction->Inputs = StagedScriptContext->GetClipboardFunctionInputs(); ClipboardContent->Functions.Add(ClipboardFunction); @@ -1237,11 +1282,14 @@ void UNiagaraEmitterConversionContext::Finalize() } - UNiagaraStackItemGroup* const* RendererStackItemGroup = StackItemGroups.FindByPredicate([](const UNiagaraStackItemGroup* EmitterItemGroup) { + UNiagaraStackItemGroup* const* RendererStackItemGroupPtr = StackItemGroups.FindByPredicate([](const UNiagaraStackItemGroup* EmitterItemGroup) { return EmitterItemGroup->GetExecutionCategoryName() == UNiagaraStackEntry::FExecutionCategoryNames::Render && EmitterItemGroup->GetExecutionSubcategoryName() == UNiagaraStackEntry::FExecutionSubcategoryNames::Render; }); - TArray TargetRendererStackEntryArr; - TargetRendererStackEntryArr.Add(*RendererStackItemGroup); + + if (RendererStackItemGroupPtr == nullptr) + { + return; + } // Add the staged renderer properties for (auto It = RendererNameToStagedRendererPropertiesMap.CreateIterator(); It; ++It) @@ -1250,9 +1298,10 @@ void UNiagaraEmitterConversionContext::Finalize() UNiagaraClipboardContent* ClipboardContent = UNiagaraClipboardContent::Create(); ClipboardContent->Renderers.Add(NewRendererProperties); - FNiagaraEditorModule::Get().GetClipboard().SetClipboardContent(ClipboardContent); + FText PasteWarning = FText(); - FNiagaraStackClipboardUtilities::PasteSelection(TargetRendererStackEntryArr, PasteWarning); + UNiagaraStackItemGroup* RendererStackItemGroup = *RendererStackItemGroupPtr; + RendererStackItemGroup->Paste(ClipboardContent, PasteWarning); if (PasteWarning.IsEmpty() == false) { UE_LOG(LogTemp, Warning, TEXT("%s"), *PasteWarning.ToString()); @@ -1275,142 +1324,80 @@ void UNiagaraEmitterConversionContext::Finalize() void UNiagaraScriptConversionContext::Init(const FAssetData& InNiagaraScriptAssetData) { Script = static_cast(InNiagaraScriptAssetData.GetAsset()); + if (Script == nullptr) + { + Log("Failed to create script! AssetData path was invalid!: " + InNiagaraScriptAssetData.PackagePath.ToString(), ENiagaraMessageSeverity::Error); + return; + } bEnabled = true; - // @todo(ng) build id table - //static_cast(GetScript()->GetSource())->NodeGraph->FindInputNodes() + + // Gather the inputs to this script and add them to the lookup table for validating UNiagaraScriptConversionContextInputs that are set. + TArray InputNodes; + + const TMap VarToPinsMap = static_cast(Script->GetSource())->NodeGraph->CollectVarsToInOutPinsMap(); + for (auto It = VarToPinsMap.CreateConstIterator(); It; ++It) + { + if (It->Value.OutputPins.Num() > 0) + { + const FNiagaraVariable& Var = It->Key; + InputNameToTypeDefMap.Add(FNiagaraEditorUtilities::GetNamespacelessVariableNameString(Var.GetName()), Var.GetType()); + } + } } bool UNiagaraScriptConversionContext::SetParameter(FString ParameterName, UNiagaraScriptConversionContextInput* ParameterInput, bool bInHasEditCondition /*= false*/, bool bInEditConditionValue /* = false*/) { - //@todo(ng) assert on ParameterInput.TypeDefinition + if (ParameterInput->ClipboardFunctionInput == nullptr) + { + return false; + } + + const FNiagaraTypeDefinition* InputTypeDef = InputNameToTypeDefMap.Find(ParameterName); + if (InputTypeDef == nullptr) + { + Log("Failed to set parameter " + ParameterName + ": Could not find input with this name!", ENiagaraMessageSeverity::Error); + return false; + } + else if (ParameterInput->TypeDefinition != *InputTypeDef) + { + Log("Failed to set parameter " + ParameterName + ": Input types did not match! /n Tried to set: " + ParameterInput->TypeDefinition.GetName() + " | Input type was: " + InputTypeDef->GetName(), ENiagaraMessageSeverity::Error); + return false; + } + ParameterInput->ClipboardFunctionInput->bHasEditCondition = bInHasEditCondition; ParameterInput->ClipboardFunctionInput->bEditConditionValue = bInEditConditionValue; ParameterInput->ClipboardFunctionInput->InputName = FName(*ParameterName); FunctionInputs.Add(ParameterInput->ClipboardFunctionInput); + StackMessages.Append(ParameterInput->StackMessages); return true; } void UNiagaraScriptConversionContext::Log(FString Message, ENiagaraMessageSeverity Severity, bool bIsVerbose /* = false*/) -{ +{ StackMessages.Add(FGenericConverterMessage(Message, Severity, bIsVerbose)); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///// UNiagaraScriptConversionContextInput ///// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void UNiagaraScriptConversionContextInput::Init(UNiagaraClipboardFunctionInput* InClipboardFunctionInput, const FNiagaraTypeDefinition& InTargetTypeDefinition) +void UNiagaraScriptConversionContextInput::Init( + UNiagaraClipboardFunctionInput* InClipboardFunctionInput + , const ENiagaraScriptInputType InInputType + , const FNiagaraTypeDefinition& InTypeDefinition) { ClipboardFunctionInput = InClipboardFunctionInput; - TargetTypeDefinition = InTargetTypeDefinition; + InputType = InInputType; + TypeDefinition = InTypeDefinition; } -bool UNiagaraScriptConversionContextInput::TryGetValueRangeFloat(float& OutMinValue, float& OutMaxValue) + +TArray FRichCurveKeyBP::KeysToBase(const TArray& InKeyBPs) { - if (ClipboardFunctionInput == nullptr) + TArray Keys; + Keys.AddUninitialized(InKeyBPs.Num()); + for (int i = 0; i < InKeyBPs.Num(); ++i) { - return false; + Keys[i] = InKeyBPs[i].ToBase(); } - - switch (ClipboardFunctionInput->ValueMode) { - case ENiagaraClipboardFunctionInputValueMode::Data: - if (ClipboardFunctionInput->Data != nullptr) - { - if (ClipboardFunctionInput->Data->IsA()) - { - UNiagaraDataInterfaceCurve* CurveDI = Cast(ClipboardFunctionInput->Data); - CurveDI->Curve.GetValueRange(OutMinValue, OutMaxValue); - return true; - } - } - case ENiagaraClipboardFunctionInputValueMode::Local: - if (ClipboardFunctionInput->GetTypeDef() == FNiagaraTypeDefinition::GetFloatDef()) - { - memcpy(&OutMaxValue, ClipboardFunctionInput->Local.GetData(), sizeof(float)); - memcpy(&OutMinValue, ClipboardFunctionInput->Local.GetData(), sizeof(float)); - return true; - } - default: - return false; - } - return false; -} - -bool UNiagaraScriptConversionContextInput::TryGetValueRangeVector(FVector& OutMinValue, FVector& OutMaxValue) -{ - if (ClipboardFunctionInput == nullptr) - { - return false; - } - - switch (ClipboardFunctionInput->ValueMode) { - case ENiagaraClipboardFunctionInputValueMode::Data: - if (ClipboardFunctionInput->Data != nullptr) - { - if (ClipboardFunctionInput->Data->IsA()) - { - UNiagaraDataInterfaceVectorCurve* CurveDI = Cast(ClipboardFunctionInput->Data); - - OutMaxValue = FVector(INT32_MIN); - OutMinValue = FVector(INT32_MAX); - float MinX, MaxX, MinY, MaxY, MinZ, MaxZ; - CurveDI->XCurve.GetValueRange(MinX, MaxX); - CurveDI->YCurve.GetValueRange(MinY, MaxY); - CurveDI->ZCurve.GetValueRange(MinZ, MaxZ); - OutMaxValue = OutMaxValue.ComponentMax(FVector(MaxX, MaxY, MaxZ)); - OutMinValue = OutMinValue.ComponentMin(FVector(MinX, MinY, MinZ)); - return true; - } - } - case ENiagaraClipboardFunctionInputValueMode::Local: - if (ClipboardFunctionInput->GetTypeDef() == FNiagaraTypeDefinition::GetVec3Def()) - { - memcpy(&OutMaxValue, ClipboardFunctionInput->Local.GetData(), sizeof(FVector)); - memcpy(&OutMinValue, ClipboardFunctionInput->Local.GetData(), sizeof(FVector)); - return true; - } - default: - return false; - } - return false; -} - -bool UNiagaraScriptConversionContextInput::ValueIsAlwaysEqual(TArray ConstValues) -{ - float MinFloat, MaxFloat = 0.0f; - if (TryGetValueRangeFloat(MinFloat, MaxFloat)) - { - if (MinFloat != MaxFloat) - { - return false; - } - - for (const float& ConstValue : ConstValues) - { - if (ConstValue == MinFloat) - { - return true; - } - } - return false; - } - - FVector MinVector, MaxVector = FVector(0.0f); - if (TryGetValueRangeVector(MinVector, MaxVector)) - { - if (MinVector != MaxVector) - { - return false; - } - - for (const float& ConstValue : ConstValues) - { - if (FVector(ConstValue) == MinVector) - { - return true; - } - } - return false; - } - - return false; + return Keys; } diff --git a/Engine/Plugins/FX/CascadeToNiagaraConverter/Source/CascadeToNiagaraConverter/Public/NiagaraStackGraphUtilitiesAdapterLibrary.h b/Engine/Plugins/FX/CascadeToNiagaraConverter/Source/CascadeToNiagaraConverter/Public/NiagaraStackGraphUtilitiesAdapterLibrary.h index 2b587db64aae..bcd32c45c45a 100644 --- a/Engine/Plugins/FX/CascadeToNiagaraConverter/Source/CascadeToNiagaraConverter/Public/NiagaraStackGraphUtilitiesAdapterLibrary.h +++ b/Engine/Plugins/FX/CascadeToNiagaraConverter/Source/CascadeToNiagaraConverter/Public/NiagaraStackGraphUtilitiesAdapterLibrary.h @@ -14,8 +14,10 @@ #include "Particles/ParticleModuleRequired.h" #include "Particles/Orbit/ParticleModuleOrbit.h" #include "Particles/Collision/ParticleModuleCollisionBase.h" +#include "Particles/TypeData/ParticleModuleTypeDataGpu.h" #include "Particles/TypeData/ParticleModuleTypeDataMesh.h" #include "Particles/TypeData/ParticleModuleTypeDataRibbon.h" +#include "Curves/RichCurve.h" #include "NiagaraStackGraphUtilitiesAdapterLibrary.generated.h" class UNiagaraSystem; @@ -89,6 +91,9 @@ enum class ENiagaraScriptInputType : uint8 Vec4, LinearColor, Quaternion, + Struct, + Enum, + DataInterface, NONE }; @@ -222,7 +227,12 @@ struct FParticleBurstBlueprint { GENERATED_USTRUCT_BODY() - FParticleBurstBlueprint() {}; + FParticleBurstBlueprint() + { + Count = 0; + CountLow = 0; + Time = 0.0f; + }; FParticleBurstBlueprint(const FParticleBurst& InParticleBurst) : Count(InParticleBurst.Count) @@ -253,6 +263,25 @@ struct FParameterSetIndices TArray Indices; }; +USTRUCT(BlueprintInternalUseOnly) +struct FRichCurveKeyBP : public FRichCurveKey +{ + GENERATED_BODY() + + FRichCurveKeyBP() + :FRichCurveKey() + {}; + + FRichCurveKeyBP(const FRichCurveKey& Other) + :FRichCurveKey(Other) + {}; + + FRichCurveKey ToBase() const { return FRichCurveKey(Time, Value, ArriveTangent, LeaveTangent, InterpMode); }; + + static TArray KeysToBase(const TArray& InKeyBPs); +}; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///// Logging Framework ///// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -481,6 +510,10 @@ private: UPROPERTY() TArray StackMessages; + // Map of input variable names to their type defs for verifying inputs. + UPROPERTY() + TMap InputNameToTypeDefMap; + UPROPERTY() bool bEnabled; }; @@ -495,24 +528,19 @@ public: UNiagaraScriptConversionContextInput() {}; UFUNCTION() - void Init(UNiagaraClipboardFunctionInput* InClipboardFunctionInput, const FNiagaraTypeDefinition& InTargetTypeDefinition); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - bool TryGetValueRangeFloat(float& OutMinValue, float& OutMaxValue); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - bool TryGetValueRangeVector(FVector& OutMinValue, FVector& OutMaxValue); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - bool ValueIsAlwaysEqual(TArray ConstValues); + void Init(UNiagaraClipboardFunctionInput* InClipboardFunctionInput, const ENiagaraScriptInputType InInputType, const FNiagaraTypeDefinition& InTypeDefinition); UPROPERTY() UNiagaraClipboardFunctionInput* ClipboardFunctionInput; - UPROPERTY() - FNiagaraTypeDefinition TargetTypeDefinition; -}; + UPROPERTY(BlueprintReadOnly, Category = StaticValue) + ENiagaraScriptInputType InputType; + UPROPERTY() + FNiagaraTypeDefinition TypeDefinition; + + TArray StackMessages; +}; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///// UFXConverterUtilitiesLibrary ///// @@ -529,9 +557,6 @@ public: // Generic Utilities UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") static FString GetLongPackagePath(const FString& InLongPackageName) { return FPackageName::GetLongPackagePath(InLongPackageName); } - - UFUNCTION(BlueprintCallable, meta = (ScriptMethod), Category = "FXConverterUtilities") - static bool ObjectIsA(UObject* Object, UClass* Class) { return Object->IsA(Class); }; // Cascade Emitter and ParticleLodLevel Getters @@ -570,6 +595,9 @@ public: UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") static UNiagaraScriptConversionContextInput* CreateScriptInputFloat(float Value); + UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") + static UNiagaraScriptConversionContextInput* CreateScriptInputVec2(FVector2D Value); + UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") static UNiagaraScriptConversionContextInput* CreateScriptInputVector(FVector Value); @@ -599,105 +627,33 @@ public: // Niagara DI Helpers UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UNiagaraDataInterfaceCurve* CreateFloatCurveDI(); + static UNiagaraDataInterfaceCurve* CreateFloatCurveDI(TArray Keys); UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UNiagaraDataInterfaceVector2DCurve* CreateVec2CurveDI(); + static UNiagaraDataInterfaceVector2DCurve* CreateVec2CurveDI(TArray X_Keys, TArray Y_Keys); UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UNiagaraDataInterfaceVectorCurve* CreateVec3CurveDI(); + static UNiagaraDataInterfaceVectorCurve* CreateVec3CurveDI( + TArray X_Keys, + TArray Y_Keys, + TArray Z_Keys + ); UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UNiagaraDataInterfaceVector4Curve* CreateVec4CurveDI(); + static UNiagaraDataInterfaceVector4Curve* CreateVec4CurveDI( + TArray X_Keys, + TArray Y_Keys, + TArray Z_Keys, + TArray W_Keys + ); // Niagara System and Emitter Helpers UFUNCTION(BlueprintCallable, meta = (ScriptMethod), Category = "FXConverterUtilities") static UNiagaraSystemConversionContext* CreateSystemConversionContext(UNiagaraSystem* InSystem); - - // Cascade Particle Module Getters - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleSpawnClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleRequiredClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleColorClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleColorOverLifeClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleLifetimeClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleSizeClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleVelocityClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleTypeDataGPUClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleTypeDataMeshClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleConstantAccelerationClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleLocationPrimitiveSphereClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleMeshRotationClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleCollisionClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleSizeScaleBySpeedClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleVectorFieldLocalClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleVectorFieldRotationRateClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleOrbitClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleSizeMultipleLifeClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleColorScaleOverLifeClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleRotationClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleRotationRateClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleSubUVClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleCameraOffsetClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleSubUVMovieClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleParameterDynamicClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleAccelerationDragClass(); - - UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static UClass* GetParticleModuleAccelerationClass(); - + UFUNCTION(BlueprintCallable, meta = (ScriptMethod), Category = "FXConverterUtilities") + static void GetParticleModuleTypeDataGpuProps(UParticleModuleTypeDataGpu* ParticleModule); UFUNCTION(BlueprintCallable, meta = (ScriptMethod), Category = "FXConverterUtilities") static void GetParticleModuleTypeDataMeshProps( @@ -777,6 +733,7 @@ public: , UTexture2D*& OutCutoutTexture , TEnumAsByte& OutBoundingMode , TEnumAsByte& OutOpacitySourceMode + , float& OutAlphaThreshold ); UFUNCTION(BlueprintCallable, meta = (ScriptMethod), Category = "FXConverterUtilities") @@ -860,12 +817,12 @@ public: ); UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") - static void GetParticleModuleSizeMultipleLifeProps( + static void GetParticleModuleSizeMultiplyLifeProps( UParticleModuleSizeMultiplyLife* ParticleModule , UDistribution*& OutLifeMultiplier - , int32& OutMultiplyX - , int32& OutMultiplyY - , int32& OutMultiplyZ + , bool& OutMultiplyX + , bool& OutMultiplyY + , bool& OutMultiplyZ ); UFUNCTION(BlueprintCallable, Category = "FXConverterUtilities") @@ -917,6 +874,13 @@ public: // Cascade Distribution Getters + UFUNCTION(BlueprintCallable, meta = (ScriptMethod), Category = "FXConverterUtilities") + static void GetDistributionMinMaxValues( + UDistribution* Distribution, + bool& bOutSuccess, + FVector& OutMinValue, + FVector& OutMaxValue); + UFUNCTION(BlueprintCallable, meta = (ScriptMethod), Category = "FXConverterUtilities") static void GetDistributionType( UDistribution* Distribution @@ -956,16 +920,16 @@ public: // Cascade curve helpers UFUNCTION(BlueprintCallable, meta = (ScriptMethod), Category = "FXConverterUtilities") - static void CopyCascadeFloatCurveToNiagaraCurveDI(UNiagaraDataInterfaceCurve* CurveDI, FInterpCurveFloat InterpCurveFloat); + static TArray KeysFromInterpCurveFloat(FInterpCurveFloat Curve); UFUNCTION(BlueprintCallable, meta = (ScriptMethod), Category = "FXConverterUtilities") - static void CopyCascadeVectorCurveToNiagaraCurveDI(UNiagaraDataInterfaceVectorCurve* CurveDI, FInterpCurveVector InterpCurveVector); + static TArray KeysFromInterpCurveVector(FInterpCurveVector Curve, int32 ComponentIdx); UFUNCTION(BlueprintCallable, meta = (ScriptMethod), Category = "FXConverterUtilities") - static void CopyCascadeVector2DCurveToNiagaraCurveDI(UNiagaraDataInterfaceVector2DCurve* CurveDI, FInterpCurveVector2D InterpCurveVector2D); + static TArray KeysFromInterpCurveVector2D(FInterpCurveVector2D Curve, int32 ComponentIdx); UFUNCTION(BlueprintCallable, meta = (ScriptMethod), Category = "FXConverterUtilities") - static void CopyCascadeTwoVectorCurveToNiagaraCurveDI(UNiagaraDataInterfaceVector4Curve* CurveDI, FInterpCurveTwoVectors InterpCurveTwoVectors); + static TArray KeysFromInterpCurveTwoVectors(FInterpCurveTwoVectors Curve, int32 ComponentIdx); // Maps from python addressable FGuid to non-blueprint types diff --git a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraEmitterInstanceShader.usf b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraEmitterInstanceShader.usf index 7f0dd1f4a160..040e8abdd6ad 100644 --- a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraEmitterInstanceShader.usf +++ b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraEmitterInstanceShader.usf @@ -65,7 +65,6 @@ bool NiagaraAny(bool4 b) { return b.x || b.y || b.z || b.w; } static uint GEmitterTickCounter; static uint GSimStart; static uint GRandomSeedOffset = 0; - static bool GStageWritesAlive = false; const static uint GSpawnPhase = 0; const static uint GUpdatePhase = 1; @@ -119,6 +118,15 @@ bool NiagaraAny(bool4 b) { return b.x || b.y || b.z || b.w; } static int Engine_ExecutionCount; static int GGPUExecIndex; + int3 SimulationStageIterationInfo; // Packed data where X = Instance Count, Y = Iteration Index, Z = Num Iterations + float SimulationStageNormalizedIterationIndex; + + // Note: These are referenced from an asset that passes back the data to the user (see SimulationStageIterationInfo) + int SimulationStage_GetInstanceCount() { return SimulationStageIterationInfo.x; } + int SimulationStage_GetIterationIndex() { return SimulationStageIterationInfo.y; } + int SimulationStage_GetNumIterations() { return SimulationStageIterationInfo.z; } + float SimulationStage_GetNormalizedIterationIndex() { return SimulationStageNormalizedIterationIndex; } + static uint GSpawnStartInstance; uint SpawnedInstances; uint UpdateStartInstance; @@ -376,7 +384,7 @@ int rand_int(int x) // Going through the float function also give us a better distribution than using modulo // to get an integer range. // This will not include the upper range as rand_float returns [0, max), not [0, max]. - return rand_float(x.x); + return (int) rand_float(x.x); } // Explicit deterministic random overrides used by Random Float/Integer and Seeded Random Float/Integer op nodes @@ -460,10 +468,10 @@ int rand_int(int x, int Seed1, int Seed2, int Seed3) * declared as int instead of uint to avoid compiler warnings because we can't expose uint on Niagara module parameters. * This is still declared as a uint in the corresponding C++ classes */ - int DefaultSimulationStageIndex; - int SimulationStageIndex; - - int IterationInterfaceCount; + #if NIAGARA_SHADER_PERMUTATIONS == 0 + int DefaultSimulationStageIndex; + int SimulationStageIndex; + #endif uint ComponentBufferSizeRead; uint ComponentBufferSizeWrite; @@ -671,6 +679,30 @@ int rand_int(int x, int Seed1, int Seed2, int Seed3) return InputHalf[RegisterIdx*ComponentBufferSizeRead + InstanceIdx]; } + /* --------------------------------------------------------------------- + * InputData from RW buffer + * --------------------------------------------------------------------- + */ + float RWInputDataFloat(int DataSetIndex, int RegisterIdx, int InstanceIdx) + { + return RWOutputFloat[RegisterIdx*ComponentBufferSizeRead + InstanceIdx]; + } + + int RWInputDataInt(int DataSetIndex, int RegisterIdx, int InstanceIdx) + { + return RWOutputInt[RegisterIdx*ComponentBufferSizeRead + InstanceIdx]; + } + + bool RWInputDataBool(int DataSetIndex, int RegisterIdx, int InstanceIdx) + { + return RWOutputInt[RegisterIdx*ComponentBufferSizeRead + InstanceIdx] == -1; + } + + float RWInputDataHalf(int DataSetIndex, int RegisterIdx, int InstanceIdx) + { + return RWOutputHalf[RegisterIdx*ComponentBufferSizeRead + InstanceIdx]; + } + /* --------------------------------------------------------------------- * OutputData operations * --------------------------------------------------------------------- diff --git a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraMeshVertexFactory.ush b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraMeshVertexFactory.ush index c7e525f431cf..396543c6669c 100644 --- a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraMeshVertexFactory.ush +++ b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraMeshVertexFactory.ush @@ -639,7 +639,7 @@ void CalculateTransforms(int InstanceId, in FVertexFactoryIntermediates Intermed float3 CameraOffsetWorldPosition = WorldParticlePosition; if (NiagaraMeshVF.bLocalSpace) { - CameraOffsetWorldPosition = TransformLocalToWorld(WorldParticlePosition); + CameraOffsetWorldPosition = TransformLocalToWorld(WorldParticlePosition).xyz; } WorldParticlePosition += CalculateCameraOffset(CameraOffsetWorldPosition, InstanceId); diff --git a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraRibbonVertexFactory.ush b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraRibbonVertexFactory.ush index 8ae739978b29..76d152e4e155 100644 --- a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraRibbonVertexFactory.ush +++ b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraRibbonVertexFactory.ush @@ -14,6 +14,10 @@ #define USE_PARTICLE_RANDOM (NEEDS_PARTICLE_RANDOM) #define USE_PARTICLE_INTERPOLATION 1 +#define U_DISTRIBUTION_SCALED_UNIFORMLY 0 +#define U_DISTRIBUTION_SCALED_USING_RIBBON_LENGTH 1 +#define U_DISTRIBUTION_TILED_OVER_RIBBON_LENGTH 2 + float3 TransformPosition(float3 InPosition) { if (NiagaraRibbonVF.bLocalSpace) @@ -90,7 +94,8 @@ struct FSegmentData float2 UV0Offset; float2 UV1Scale; float2 UV1Offset; - float OneOverNumSegments; + float U0DistributionScaler; + float U1DistributionScaler; uint StartParticleId; }; @@ -228,32 +233,62 @@ void GetNiagaraParticleDynamicParameters(in FInterpVtxData InterpVtxData, in out void GetNiagaraParticleTextureCoords(in FVertexFactoryInput Input, in FInterpVtxData InterpVtxData, in FSegmentData SegmentData, in out FVertexFactoryIntermediates Intermediates) { - float U0ForSegment; + float U0ForSegment = 0; BRANCH - if(NiagaraRibbonVF.OneOverUV0TilingDistance != 0.0f) + if(NiagaraRibbonVF.U0OverrideDataOffset != -1) { - U0ForSegment = Intermediates.DistanceOnSegment * NiagaraRibbonVF.OneOverUV0TilingDistance; + U0ForSegment = lerp(GetFloat(NiagaraRibbonVF.U0OverrideDataOffset, InterpVtxData.ParticleId0), GetFloat(NiagaraRibbonVF.U0OverrideDataOffset, InterpVtxData.ParticleId1), InterpVtxData.Alpha); + } + else if (NiagaraRibbonVF.U0DistributionMode == U_DISTRIBUTION_SCALED_UNIFORMLY) + { + U0ForSegment = ((float)(InterpVtxData.RawParticleId0 - SegmentData.StartParticleId) + InterpVtxData.Alpha) * SegmentData.U0DistributionScaler; + } + else if (NiagaraRibbonVF.U0DistributionMode == U_DISTRIBUTION_SCALED_USING_RIBBON_LENGTH || NiagaraRibbonVF.U0DistributionMode == U_DISTRIBUTION_TILED_OVER_RIBBON_LENGTH) + { + U0ForSegment = Intermediates.DistanceOnSegment * SegmentData.U0DistributionScaler; + } + + float U1ForSegment = 0; + BRANCH + if(NiagaraRibbonVF.U1OverrideDataOffset != -1) + { + U1ForSegment = lerp(GetFloat(NiagaraRibbonVF.U1OverrideDataOffset, InterpVtxData.ParticleId0), GetFloat(NiagaraRibbonVF.U1OverrideDataOffset, InterpVtxData.ParticleId1), InterpVtxData.Alpha); + } + else if (NiagaraRibbonVF.U1DistributionMode == U_DISTRIBUTION_SCALED_UNIFORMLY) + { + U1ForSegment = ((float)(InterpVtxData.RawParticleId0 - SegmentData.StartParticleId) + InterpVtxData.Alpha) * SegmentData.U1DistributionScaler; + } + else if (NiagaraRibbonVF.U1DistributionMode == U_DISTRIBUTION_SCALED_USING_RIBBON_LENGTH || NiagaraRibbonVF.U1DistributionMode == U_DISTRIBUTION_TILED_OVER_RIBBON_LENGTH) + { + U1ForSegment = Intermediates.DistanceOnSegment * SegmentData.U1DistributionScaler; + } + + float V0ForSegment; + BRANCH + if(NiagaraRibbonVF.V0RangeOverrideDataOffset != -1) + { + float2 V0RangeForSegment = lerp(GetVec2(NiagaraRibbonVF.V0RangeOverrideDataOffset, InterpVtxData.ParticleId0), GetVec2(NiagaraRibbonVF.V0RangeOverrideDataOffset, InterpVtxData.ParticleId1), InterpVtxData.Alpha); + V0ForSegment = ((1 - (Input.InterpVtxId & 0x1)) * V0RangeForSegment.x) + ((Input.InterpVtxId & 0x1) * V0RangeForSegment.y); } else { - U0ForSegment = ((float)(InterpVtxData.RawParticleId0 - SegmentData.StartParticleId) + InterpVtxData.Alpha) * SegmentData.OneOverNumSegments; + V0ForSegment = Input.InterpVtxId & 0x1; } - - float U1ForSegment; - BRANCH - if(NiagaraRibbonVF.OneOverUV1TilingDistance != 0.0f) - { - U1ForSegment = Intermediates.DistanceOnSegment * NiagaraRibbonVF.OneOverUV1TilingDistance; - } - else - { - U1ForSegment = ((float)(InterpVtxData.RawParticleId0 - SegmentData.StartParticleId) + InterpVtxData.Alpha) * SegmentData.OneOverNumSegments; - } - - float VForSegment = Input.InterpVtxId & 0x1; - float2 UV0ForSegment = float2(U0ForSegment, VForSegment); - float2 UV1ForSegment = float2(U1ForSegment, VForSegment); + float V1ForSegment; + BRANCH + if(NiagaraRibbonVF.V1RangeOverrideDataOffset != -1) + { + float2 V1RangeForSegment = lerp(GetVec2(NiagaraRibbonVF.V1RangeOverrideDataOffset, InterpVtxData.ParticleId0), GetVec2(NiagaraRibbonVF.V1RangeOverrideDataOffset, InterpVtxData.ParticleId1), InterpVtxData.Alpha); + V1ForSegment = ((1 - (Input.InterpVtxId & 0x1)) * V1RangeForSegment.x) + ((Input.InterpVtxId & 0x1) * V1RangeForSegment.y); + } + else + { + V1ForSegment = Input.InterpVtxId & 0x1; + } + + float2 UV0ForSegment = float2(U0ForSegment, V0ForSegment); + float2 UV1ForSegment = float2(U1ForSegment, V1ForSegment); Intermediates.TexCoord.xy = UV0ForSegment * SegmentData.UV0Scale + SegmentData.UV0Offset; Intermediates.TexCoord.zw = UV1ForSegment * SegmentData.UV1Scale + SegmentData.UV1Offset; @@ -419,14 +454,17 @@ FInterpVtxData GetInterpVtxData(FVertexFactoryInput Input) FSegmentData UnpackPerRibbonDataByIndex(int RibbonIndex) { FSegmentData SegmentData = (FSegmentData)0; - const int Index = RibbonIndex * 6; + const int Index = RibbonIndex * 7; SegmentData.UV0Scale = float2(NiagaraRibbonVFLooseParameters.PackedPerRibbonDataByIndex[Index], NiagaraRibbonVF.PackedVData.x); SegmentData.UV0Offset = float2(NiagaraRibbonVFLooseParameters.PackedPerRibbonDataByIndex[Index + 1], NiagaraRibbonVF.PackedVData.y); - SegmentData.UV1Scale = float2(NiagaraRibbonVFLooseParameters.PackedPerRibbonDataByIndex[Index + 2], NiagaraRibbonVF.PackedVData.z); - SegmentData.UV1Offset = float2(NiagaraRibbonVFLooseParameters.PackedPerRibbonDataByIndex[Index + 3], NiagaraRibbonVF.PackedVData.w); - SegmentData.OneOverNumSegments = NiagaraRibbonVFLooseParameters.PackedPerRibbonDataByIndex[Index + 4]; - SegmentData.StartParticleId = asuint(NiagaraRibbonVFLooseParameters.PackedPerRibbonDataByIndex[Index + 5]); + SegmentData.U0DistributionScaler = NiagaraRibbonVFLooseParameters.PackedPerRibbonDataByIndex[Index + 2]; + + SegmentData.UV1Scale = float2(NiagaraRibbonVFLooseParameters.PackedPerRibbonDataByIndex[Index + 3], NiagaraRibbonVF.PackedVData.z); + SegmentData.UV1Offset = float2(NiagaraRibbonVFLooseParameters.PackedPerRibbonDataByIndex[Index + 4], NiagaraRibbonVF.PackedVData.w); + SegmentData.U1DistributionScaler = NiagaraRibbonVFLooseParameters.PackedPerRibbonDataByIndex[Index + 5]; + + SegmentData.StartParticleId = asuint(NiagaraRibbonVFLooseParameters.PackedPerRibbonDataByIndex[Index + 6]); return SegmentData; } diff --git a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraSortKeyGen.usf b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraSortKeyGen.usf index 52da6a50f0ae..e5b0eccd778c 100644 --- a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraSortKeyGen.usf +++ b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraSortKeyGen.usf @@ -60,9 +60,9 @@ groupshared uint CullGroupCount; float GetFloat(uint RegisterIdx, uint InstanceID) { BRANCH - if (RegisterIdx & (1 << 31)) + if (RegisterIdx & (1 << 31U)) { - RegisterIdx &= ~(1 << 31); + RegisterIdx &= ~(1 << 31U); return NiagaraParticleDataHalf[RegisterIdx * HalfDataStride + InstanceID]; } else @@ -156,7 +156,7 @@ bool CullParticle(in uint ParticleIndex) // Transform the bounding sphere float4 BSphere = LocalBoundingSphere; BSphere.xyz = RotateVectorByQuat(BSphere.xyz * Scale, Quat) + Pos; - BSphere.w *= max(Scale.x, max(Scale.y, Scale.z)); + BSphere.w *= max(abs(Scale.x), max(abs(Scale.y), abs(Scale.z))); // Cull particles using distance ranges float3 CamVec = BSphere.xyz - CameraPosition; @@ -179,21 +179,19 @@ bool CullParticle(in uint ParticleIndex) } // Cull particles using cull planes - for (int i = 0; i < NIAGARA_KEY_GEN_MAX_CULL_PLANES; ++i) + int NumCullPlanesToCheck = min(NumCullPlanes, NIAGARA_KEY_GEN_MAX_CULL_PLANES); + bool Found = false; + int i = 0; + while (!Found && (i < NumCullPlanesToCheck)) { - if (i >= NumCullPlanes) - break; - float4 Plane = CullPlanes[i]; - if (dot(Plane.xyz, BSphere.xyz) - Plane.w > BSphere.w) - { - // We are completely outside one of the culling planes - return true; - } + Found = ((dot(Plane.xyz, BSphere.xyz) - Plane.w) > BSphere.w); + i = i + 1; } -#endif - + return Found; +#else return false; +#endif } [numthreads(THREAD_COUNT,1,1)] diff --git a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraSpriteVertexFactory.ush b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraSpriteVertexFactory.ush index a40ae52060ae..10eb17a492a9 100644 --- a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraSpriteVertexFactory.ush +++ b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraSpriteVertexFactory.ush @@ -46,16 +46,36 @@ struct FVertexFactoryInput #if COMPUTESHADER || RAYHITGROUPSHADER FVertexFactoryInput LoadVertexFactoryInputForHGS(uint TriangleIndex, int VertexIndex) { - FVertexFactoryInput Input; + // Different numbers of cutout vertices correspond to different index buffers + // For 8 verts, use GSixTriangleParticleIndexBuffer + if (NiagaraSpriteVFLooseParameters.NumCutoutVerticesPerFrame == 8) + { + FVertexFactoryInput Input; + + // Hard coded GSixTriangleParticleIndexBuffer + uint IndexBuffer[18] = { 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 6, 7 }; + uint VertexId = IndexBuffer[(TriangleIndex * 3 + VertexIndex) % 18]; + float2 TexCoords[4] = { float2(0.0f, 0.0f), float2(0.0f, 1.0f), float2(1.0f, 1.0f), float2(1.0f, 0.0f) }; + Input.TexCoord = TexCoords[VertexId]; + Input.VertexId = VertexId; + Input.InstanceId = TriangleIndex / 6; - uint IndexBuffer[6] = { 0, 2, 3, 0, 1, 2 }; - uint VertexId = IndexBuffer[(TriangleIndex * 3 + VertexIndex) % 6]; - float2 TexCoords[4] = { float2(0.0f, 0.0f), float2(0.0f, 1.0f), float2(1.0f, 1.0f), float2(1.0f, 0.0f) }; - Input.TexCoord = TexCoords[VertexId]; - Input.VertexId = VertexId; - Input.InstanceId = TriangleIndex / 2; - - return Input; + return Input; + } + else + { + // For 4 verts cutout geometry and normal particle geometry, use the typical 6 indices + FVertexFactoryInput Input; + + uint IndexBuffer[6] = { 0, 2, 3, 0, 1, 2 }; + uint VertexId = IndexBuffer[(TriangleIndex * 3 + VertexIndex) % 6]; + float2 TexCoords[4] = { float2(0.0f, 0.0f), float2(0.0f, 1.0f), float2(1.0f, 1.0f), float2(1.0f, 0.0f) }; + Input.TexCoord = TexCoords[VertexId]; + Input.VertexId = VertexId; + Input.InstanceId = TriangleIndex / 2; + + return Input; + } } FVertexFactoryInput LoadVertexFactoryInputForDynamicUpdate(uint TriangleIndex, int VertexIndex, uint PrimitiveId) @@ -651,7 +671,7 @@ float3 GetNiagaraParticleVelocity(uint InstanceID) if(NiagaraSpriteVF.VelocityDataOffset == -1) { // See FNiagaraConstants for a synchronized value... - return TransformVector(float3(0.0f,0.0f,0.0f)); + return TransformVector(NiagaraSpriteVF.DefaultVelocity.xyz); } return TransformVector(GetVec3(NiagaraSpriteVF.VelocityDataOffset, InstanceID)); } @@ -660,7 +680,7 @@ float2 GetNiagaraParticleSize(uint InstanceID) if(NiagaraSpriteVF.SizeDataOffset == -1) { // See FNiagaraConstants for a synchronized value... - return float2(50.0f, 50.0f); + return NiagaraSpriteVF.DefaultSize.xy; } return GetVec2(NiagaraSpriteVF.SizeDataOffset, InstanceID); } @@ -669,7 +689,7 @@ float GetNiagaraParticleRotation(uint InstanceID) if(NiagaraSpriteVF.RotationDataOffset == -1) { // See FNiagaraConstants for a synchronized value... - return 0.0f; + return (NiagaraSpriteVF.DefaultRotation / 180.0f) * PI; } return (GetFloat(NiagaraSpriteVF.RotationDataOffset, InstanceID) / 180.0f) * PI; } @@ -678,7 +698,7 @@ float4 GetNiagaraParticleColor(uint InstanceID) if(NiagaraSpriteVF.ColorDataOffset == -1) { // See FNiagaraConstants for a synchronized value... - return float4(1.0f, 1.0f, 1.0f, 1.0f); + return NiagaraSpriteVF.DefaultColor; } return GetVec4(NiagaraSpriteVF.ColorDataOffset, InstanceID); } @@ -688,7 +708,7 @@ float2 GetNiagaraUVScale(uint InstanceID) if(NiagaraSpriteVF.UVScaleDataOffset == -1) { // See FNiagaraConstants for a synchronized value... - return float2(1.0f, 1.0f); + return NiagaraSpriteVF.DefaultUVScale.xy; } return GetVec2(NiagaraSpriteVF.UVScaleDataOffset, InstanceID); } @@ -698,7 +718,7 @@ float GetNiagaraParticleRandom(uint InstanceID) if(NiagaraSpriteVF.MaterialRandomDataOffset == -1) { // See FNiagaraConstants for a synchronized value... - return 0.0f; + return NiagaraSpriteVF.DefaultMatRandom; } return GetFloat(NiagaraSpriteVF.MaterialRandomDataOffset, InstanceID); } @@ -708,7 +728,7 @@ float GetNiagaraCameraOffset(uint InstanceID) if (NiagaraSpriteVF.CameraOffsetDataOffset == -1) { // See FNiagaraConstants for a synchronized value... - return 0.0f; + return NiagaraSpriteVF.DefaultCamOffset; } return GetFloat(NiagaraSpriteVF.CameraOffsetDataOffset, InstanceID); } @@ -718,7 +738,7 @@ float GetNiagaraNormalizedAge(uint InstanceID) { if (NiagaraSpriteVF.NormalizedAgeDataOffset == -1) { - return 0; + return NiagaraSpriteVF.DefaultNormAge; } return GetFloat(NiagaraSpriteVF.NormalizedAgeDataOffset, InstanceID); } @@ -733,7 +753,7 @@ float4 GetNiagaraParticleDynamicParameters(uint InstanceID) } else { - return float4(1.0f, 1.0f, 1.0f, 1.0f);; + return NiagaraSpriteVF.DefaultDynamicMaterialParameter0; } } #endif @@ -746,7 +766,7 @@ float4 GetNiagaraParticleDynamicParameters1(uint InstanceID) } else { - return float4(1.0f, 1.0f, 1.0f, 1.0f); + return NiagaraSpriteVF.DefaultDynamicMaterialParameter1; } } #endif @@ -759,7 +779,7 @@ float4 GetNiagaraParticleDynamicParameters2(uint InstanceID) } else { - return float4(1.0f, 1.0f, 1.0f, 1.0f); + return NiagaraSpriteVF.DefaultDynamicMaterialParameter2; } } #endif @@ -772,7 +792,7 @@ float4 GetNiagaraParticleDynamicParameters3(uint InstanceID) } else { - return float4(1.0f, 1.0f, 1.0f, 1.0f); + return NiagaraSpriteVF.DefaultDynamicMaterialParameter3; } } #endif @@ -781,7 +801,7 @@ float4 GetNiagaraParticleDynamicParameters3(uint InstanceID) float GetNiagaraParticleSubimage(uint InstanceID) { // See FNiagaraConstants for a synchronized value... - float Val = 0.0f; + float Val = NiagaraSpriteVF.DefaultSubImage; if(NiagaraSpriteVF.SubimageDataOffset != -1) { Val = GetFloat(NiagaraSpriteVF.SubimageDataOffset, InstanceID); @@ -792,7 +812,7 @@ float GetNiagaraParticleSubimage(uint InstanceID) float3 GetNiagaraParticleFacingVector(uint InstanceID) { // See FNiagaraConstants for a synchronized value... - float3 Val = float3(1.0f, 0.0f, 0.0f); + float3 Val = NiagaraSpriteVF.DefaultFacing.xyz; if(NiagaraSpriteVF.FacingDataOffset != -1) { Val = GetVec3(NiagaraSpriteVF.FacingDataOffset, InstanceID); @@ -802,7 +822,7 @@ float3 GetNiagaraParticleFacingVector(uint InstanceID) float3 GetNiagaraParticleAlignmentVector(uint InstanceID) { - float3 Val = float3(1.0f, 0.0f, 0.0f); + float3 Val = NiagaraSpriteVF.DefaultAlignment.xyz; if(NiagaraSpriteVF.AlignmentDataOffset != -1) { Val = GetVec3(NiagaraSpriteVF.AlignmentDataOffset, InstanceID); diff --git a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraVFParticleAccess.usf b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraVFParticleAccess.usf index f906a0b74020..78527fb1a1ba 100644 --- a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraVFParticleAccess.usf +++ b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraVFParticleAccess.usf @@ -8,39 +8,94 @@ //uint NiagaraIntDataOffset; //uint NiagaraIntDataStride; - -float GetFloat(int RegisterIdx, uint InstanceID) +bool GetIsHalfAndFixupRegister(inout int RegisterIdx) { // this was originally: bool IsHalf = RegisterIdx & (1 << 31); // but the glsl comes out as -2147483648 and this appears to be upsetting some old android glsl compilers. Doing this forces an // unsigned 2147483648u literal into glsl. FORT-286011. - uint HalfBit = (1 << 31); - bool IsHalf = RegisterIdx & HalfBit; + const uint HalfBit = (1 << 31); + const bool bIsHalf = RegisterIdx & HalfBit; - RegisterIdx &= (~(1 << 31)); + RegisterIdx &= ~HalfBit; - if(IsHalf) + return bIsHalf; +} + +float GetFloat(Buffer FloatBuffer, int RegisterIdx, uint InstanceID) +{ + return FloatBuffer[RegisterIdx * NiagaraVFLooseParameters.NiagaraFloatDataStride + InstanceID]; +} + +float2 GetVec2(Buffer FloatBuffer, int RegisterIdx, uint InstanceID) +{ + return float2(GetFloat(FloatBuffer, RegisterIdx + 0, InstanceID), + GetFloat(FloatBuffer, RegisterIdx + 1, InstanceID)); +} + +float3 GetVec3(Buffer FloatBuffer, int RegisterIdx, uint InstanceID) +{ + return float3(GetFloat(FloatBuffer, RegisterIdx + 0, InstanceID), + GetFloat(FloatBuffer, RegisterIdx + 1, InstanceID), + GetFloat(FloatBuffer, RegisterIdx + 2, InstanceID)); +} + +float4 GetVec4(Buffer FloatBuffer, int RegisterIdx, uint InstanceID) +{ + return float4(GetFloat(FloatBuffer, RegisterIdx + 0, InstanceID), + GetFloat(FloatBuffer, RegisterIdx + 1, InstanceID), + GetFloat(FloatBuffer, RegisterIdx + 2, InstanceID), + GetFloat(FloatBuffer, RegisterIdx + 3, InstanceID)); +} + +float GetFloat(int RegisterIdx, uint InstanceID) +{ + BRANCH + if (GetIsHalfAndFixupRegister(RegisterIdx)) { - return NiagaraVFLooseParameters.NiagaraParticleDataHalf[(RegisterIdx * NiagaraVFLooseParameters.NiagaraFloatDataStride + InstanceID)]; + return GetFloat(NiagaraVFLooseParameters.NiagaraParticleDataHalf, RegisterIdx, InstanceID); } else { - return NiagaraVFLooseParameters.NiagaraParticleDataFloat[(RegisterIdx * NiagaraVFLooseParameters.NiagaraFloatDataStride + InstanceID)]; + return GetFloat(NiagaraVFLooseParameters.NiagaraParticleDataFloat, RegisterIdx, InstanceID); } } -float2 GetVec2(int RegisterIndex, uint InstanceID) +float2 GetVec2(int RegisterIdx, uint InstanceID) { - return float2(GetFloat(RegisterIndex, InstanceID), GetFloat(RegisterIndex+1, InstanceID)); + BRANCH + if (GetIsHalfAndFixupRegister(RegisterIdx)) + { + return GetVec2(NiagaraVFLooseParameters.NiagaraParticleDataHalf, RegisterIdx, InstanceID); + } + else + { + return GetVec2(NiagaraVFLooseParameters.NiagaraParticleDataFloat, RegisterIdx, InstanceID); + } } -float3 GetVec3(int RegisterIndex, uint InstanceID) +float3 GetVec3(int RegisterIdx, uint InstanceID) { - return float3(GetFloat(RegisterIndex, InstanceID), GetFloat(RegisterIndex+1, InstanceID), GetFloat(RegisterIndex+2, InstanceID)); + BRANCH + if (GetIsHalfAndFixupRegister(RegisterIdx)) + { + return GetVec3(NiagaraVFLooseParameters.NiagaraParticleDataHalf, RegisterIdx, InstanceID); + } + else + { + return GetVec3(NiagaraVFLooseParameters.NiagaraParticleDataFloat, RegisterIdx, InstanceID); + } } -float4 GetVec4(int RegisterIndex, uint InstanceID) +float4 GetVec4(int RegisterIdx, uint InstanceID) { - return float4(GetFloat(RegisterIndex, InstanceID), GetFloat(RegisterIndex+1, InstanceID), GetFloat(RegisterIndex+2, InstanceID), GetFloat(RegisterIndex+3, InstanceID)); + BRANCH + if (GetIsHalfAndFixupRegister(RegisterIdx)) + { + return GetVec4(NiagaraVFLooseParameters.NiagaraParticleDataHalf, RegisterIdx, InstanceID); + } + else + { + return GetVec4(NiagaraVFLooseParameters.NiagaraParticleDataFloat, RegisterIdx, InstanceID); + } } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NDISkeletalMeshCommon.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NDISkeletalMeshCommon.h index eaacc0ec7c58..58362b6c8d32 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NDISkeletalMeshCommon.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NDISkeletalMeshCommon.h @@ -38,8 +38,8 @@ struct FSkeletalMeshAccessorHelper template FORCEINLINE void Init(FNDISkeletalMesh_InstanceData* InstData) { - Comp = Cast(InstData->Component.Get()); - Mesh = InstData->Mesh; + Comp = Cast(InstData->SceneComponent.Get()); + Mesh = InstData->SkeletalMesh.Get(); LODData = InstData->CachedLODData; SkinWeightBuffer = InstData->GetSkinWeights(); IndexBuffer = LODData ? LODData->MultiSizeIndexContainer.GetIndexBuffer() : nullptr; @@ -470,14 +470,14 @@ struct TAreaWeightingModeBinder } else if (InstData->SamplingRegionIndices.Num() == 1) { - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->SkeletalMesh->GetSamplingInfo(); const FSkeletalMeshSamplingRegion& Region = SamplingInfo.GetRegion(InstData->SamplingRegionIndices[0]); bAreaWeighting = Region.bSupportUniformlyDistributedSampling; } else { int32 LODIndex = InstData->GetLODIndex(); - bAreaWeighting = InstData->Mesh->GetLODInfo(LODIndex)->bSupportUniformlyDistributedSampling; + bAreaWeighting = InstData->SkeletalMesh->GetLODInfo(LODIndex)->bSupportUniformlyDistributedSampling; } } @@ -555,7 +555,7 @@ struct TSkinningModeBinder { FNDISkeletalMesh_InstanceData* InstData = (FNDISkeletalMesh_InstanceData*)InstanceData; UNiagaraDataInterfaceSkeletalMesh* MeshInterface = CastChecked(Interface); - USkeletalMeshComponent* Component = Cast(InstData->Component.Get()); + USkeletalMeshComponent* Component = Cast(InstData->SceneComponent.Get()); if (MeshInterface->SkinningMode == ENDISkeletalMesh_SkinningMode::None || !Component) // Can't skin if we have no component. { NextBinder::template Bind>(Interface, BindingInfo, InstanceData, OutFunc); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraConstants.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraConstants.h index 943e91bf8ffd..d309142f87b0 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraConstants.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraConstants.h @@ -116,9 +116,14 @@ #define SYS_PARAM_PARTICLES_RIBBONTWIST INiagaraModule::GetVar_Particles_RibbonTwist() #define SYS_PARAM_PARTICLES_RIBBONFACING INiagaraModule::GetVar_Particles_RibbonFacing() #define SYS_PARAM_PARTICLES_RIBBONLINKORDER INiagaraModule::GetVar_Particles_RibbonLinkOrder() +#define SYS_PARAM_PARTICLES_RIBBONU0OVERRIDE INiagaraModule::GetVar_Particles_RibbonU0Override() +#define SYS_PARAM_PARTICLES_RIBBONV0RANGEOVERRIDE INiagaraModule::GetVar_Particles_RibbonV0RangeOverride() +#define SYS_PARAM_PARTICLES_RIBBONU1OVERRIDE INiagaraModule::GetVar_Particles_RibbonU1Override() +#define SYS_PARAM_PARTICLES_RIBBONV1RANGEOVERRIDE INiagaraModule::GetVar_Particles_RibbonV1RangeOverride() #define SYS_PARAM_INSTANCE_ALIVE INiagaraModule::GetVar_DataInstance_Alive() #define SYS_PARAM_SCRIPT_USAGE INiagaraModule::GetVar_ScriptUsage() +#define SYS_PARAM_SCRIPT_CONTEXT INiagaraModule::GetVar_ScriptContext() #define TRANSLATOR_PARAM_BEGIN_DEFAULTS INiagaraModule::GetVar_BeginDefaults() struct NIAGARA_API FNiagaraConstants @@ -135,7 +140,8 @@ struct NIAGARA_API FNiagaraConstants static FText GetAttributeDescription(const FNiagaraVariable& InVar); static FString GetAttributeDefaultValue(const FNiagaraVariable& InVar); static FNiagaraVariable GetAttributeWithDefaultValue(const FNiagaraVariable& InAttribute); - static FNiagaraVariable GetAttributeAsDataSetKey(const FNiagaraVariable& InAttribute); + static FNiagaraVariable GetAttributeAsParticleDataSetKey(const FNiagaraVariable& InAttribute); + static FNiagaraVariable GetAttributeAsEmitterDataSetKey(const FNiagaraVariable& InAttribute); static FNiagaraVariableAttributeBinding GetAttributeDefaultBinding(const FNiagaraVariable& InAttribute); static bool IsNiagaraConstant(const FNiagaraVariable& InVar); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterface.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterface.h index 9e90678cb4db..26ed10498788 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterface.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterface.h @@ -231,11 +231,11 @@ struct FNiagaraDataInterfaceProxy : TSharedFromThis& OutVariables) const {} + virtual bool GetExposedVariableValue(const FNiagaraVariableBase& InVariable, void* InPerInstanceData, FNiagaraSystemInstance* InSystemInstance, void* OutData) const { return false; } + FNiagaraDataInterfaceProxy* GetProxy() { @@ -669,7 +673,7 @@ struct FNDIInputParam VectorVM::FExternalFuncInputHandler B; VectorVM::FExternalFuncInputHandler A; FORCEINLINE FNDIInputParam(FVectorVMContext& Context) : R(Context), G(Context), B(Context), A(Context) {} - FORCEINLINE FVector4 GetAndAdvance() { return FLinearColor(R.GetAndAdvance(), G.GetAndAdvance(), B.GetAndAdvance(), A.GetAndAdvance()); } + FORCEINLINE FLinearColor GetAndAdvance() { return FLinearColor(R.GetAndAdvance(), G.GetAndAdvance(), B.GetAndAdvance(), A.GetAndAdvance()); } }; template<> @@ -766,6 +770,50 @@ struct FNDIOutputParam } }; +template<> +struct FNDIOutputParam +{ + VectorVM::FExternalFuncRegisterHandler Out00; + VectorVM::FExternalFuncRegisterHandler Out01; + VectorVM::FExternalFuncRegisterHandler Out02; + VectorVM::FExternalFuncRegisterHandler Out03; + VectorVM::FExternalFuncRegisterHandler Out04; + VectorVM::FExternalFuncRegisterHandler Out05; + VectorVM::FExternalFuncRegisterHandler Out06; + VectorVM::FExternalFuncRegisterHandler Out07; + VectorVM::FExternalFuncRegisterHandler Out08; + VectorVM::FExternalFuncRegisterHandler Out09; + VectorVM::FExternalFuncRegisterHandler Out10; + VectorVM::FExternalFuncRegisterHandler Out11; + VectorVM::FExternalFuncRegisterHandler Out12; + VectorVM::FExternalFuncRegisterHandler Out13; + VectorVM::FExternalFuncRegisterHandler Out14; + VectorVM::FExternalFuncRegisterHandler Out15; + + FORCEINLINE FNDIOutputParam(FVectorVMContext& Context) : Out00(Context), Out01(Context), Out02(Context), Out03(Context), Out04(Context), Out05(Context), + Out06(Context), Out07(Context), Out08(Context), Out09(Context), Out10(Context), Out11(Context), Out12(Context), Out13(Context), Out14(Context), Out15(Context) {} + FORCEINLINE bool IsValid() const { return Out00.IsValid(); } + FORCEINLINE void SetAndAdvance(const FMatrix& Val) + { + *Out00.GetDestAndAdvance() = Val.M[0][0]; + *Out01.GetDestAndAdvance() = Val.M[0][1]; + *Out02.GetDestAndAdvance() = Val.M[0][2]; + *Out03.GetDestAndAdvance() = Val.M[0][3]; + *Out04.GetDestAndAdvance() = Val.M[1][0]; + *Out05.GetDestAndAdvance() = Val.M[1][1]; + *Out06.GetDestAndAdvance() = Val.M[1][2]; + *Out07.GetDestAndAdvance() = Val.M[1][3]; + *Out08.GetDestAndAdvance() = Val.M[2][0]; + *Out09.GetDestAndAdvance() = Val.M[2][1]; + *Out10.GetDestAndAdvance() = Val.M[2][2]; + *Out11.GetDestAndAdvance() = Val.M[2][3]; + *Out12.GetDestAndAdvance() = Val.M[3][0]; + *Out13.GetDestAndAdvance() = Val.M[3][1]; + *Out14.GetDestAndAdvance() = Val.M[3][2]; + *Out15.GetDestAndAdvance() = Val.M[3][3]; + } +}; + template<> struct FNDIOutputParam { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceArray.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceArray.h index 6ecab4572e4f..181231dc0eaf 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceArray.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceArray.h @@ -66,6 +66,10 @@ public: /** ReadWrite lock to ensure safe access to the underlying array. */ FRWLock ArrayRWGuard; + /** When greater than 0 sets the maximum number of elements the array can hold, only relevant when using operations that modify the array size. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Array", meta=(ClampMin="0")) + int32 MaxElements; + protected: TUniquePtr Impl; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceArrayFunctionLibrary.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceArrayFunctionLibrary.h index 2e2566505827..8cca26c521a8 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceArrayFunctionLibrary.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceArrayFunctionLibrary.h @@ -34,6 +34,9 @@ class NIAGARA_API UNiagaraDataInterfaceArrayFunctionLibrary : public UBlueprintF /** Sets Niagara Array Int32 Data. */ UFUNCTION(BlueprintCallable, Category = Niagara, meta = (DisplayName = "Niagara Set Int32 Array")) static void SetNiagaraArrayInt32(UNiagaraComponent* NiagaraSystem, FName OverrideName, const TArray& ArrayData); + /** Sets Niagara Array Bool Data. */ + UFUNCTION(BlueprintCallable, Category = Niagara, meta = (DisplayName = "Niagara Set Bool Array")) + static void SetNiagaraArrayBool(UNiagaraComponent* NiagaraSystem, FName OverrideName, const TArray& ArrayData); /** Gets a copy of Niagara Float Data. */ UFUNCTION(BlueprintCallable, Category = Niagara, meta = (DisplayName = "Niagara Get Float Array")) @@ -56,4 +59,7 @@ class NIAGARA_API UNiagaraDataInterfaceArrayFunctionLibrary : public UBlueprintF /** Gets a copy of Niagara Int32 Data. */ UFUNCTION(BlueprintCallable, Category = Niagara, meta = (DisplayName = "Niagara Get Int32 Array")) static TArray GetNiagaraArrayInt32(UNiagaraComponent* NiagaraSystem, FName OverrideName); + /** Gets a copy of Niagara Bool Data. */ + UFUNCTION(BlueprintCallable, Category = Niagara, meta = (DisplayName = "Niagara Get Bool Array")) + static TArray GetNiagaraArrayBool(UNiagaraComponent* NiagaraSystem, FName OverrideName); }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceArrayImpl.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceArrayImpl.h index a85dd348c052..97539d69df3d 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceArrayImpl.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceArrayImpl.h @@ -15,7 +15,6 @@ struct FNDIArrayImplHelperBase //static constexpr TCHAR const* HLSLValueTypeName = TEXT("float4"); //static constexpr TCHAR const* HLSLBufferTypeName = TEXT("float4"); //static constexpr EPixelFormat PixelFormat = PF_R32_FLOAT; - //static FRHIShaderResourceView* GetDummyBuffer() { return FNiagaraRenderer::GetDummyFloat4Buffer(); } //static const FNiagaraTypeDefinition& GetTypeDefinition() { return FNiagaraTypeDefinition::GetIntDef(); } //static const TArrayType GetDefaultValue(); @@ -43,6 +42,12 @@ struct FNiagaraDataInterfaceArrayImplHelper static const FName Function_IsValidIndexName; static const FName Function_GetValueName; + static const FName Function_Reset; + static const FName Function_SetNumValue; + static const FName Function_SetValueName; + static const FName Function_PushValueName; + static const FName Function_PopValueName; + static FString GetBufferName(const FString& InterfaceName); static FString GetBufferSizeName(const FString& InterfaceName); }; @@ -74,14 +79,14 @@ private: template struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl { - TUniquePtr& Proxy; - TArray& Data; - FRWLock& ArrayGuard; + static constexpr int32 kSafeMaxElements = TNumericLimits::Max(); - FNiagaraDataInterfaceArrayImpl(TUniquePtr& InProxy, TArray& InData, FRWLock& InArrayGuard) - : Proxy(InProxy) + UNiagaraDataInterfaceArray* Owner; + TArray& Data; + + FNiagaraDataInterfaceArrayImpl(UNiagaraDataInterfaceArray* InOwner, TArray& InData) + : Owner(InOwner) , Data(InData) - , ArrayGuard(InArrayGuard) { } @@ -89,12 +94,13 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl { OutFunctions.Reserve(OutFunctions.Num() + 3); + // Immutable functions { FNiagaraFunctionSignature& Sig = OutFunctions.AddDefaulted_GetRef(); Sig.Name = FNiagaraDataInterfaceArrayImplHelper::Function_GetNumName; - //#if WITH_EDITORONLY_DATA - // Sig.Description = NSLOCTEXT("Niagara", "GetNumDescription", "This function returns the properties of the current view. Only valid for gpu particles."); - //#endif + #if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("Niagara", "Array_GetNumDesc", "Gets the number of elements in the array."); + #endif Sig.bMemberFunction = true; Sig.bRequiresContext = false; Sig.bExperimental = true; @@ -107,9 +113,9 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl { FNiagaraFunctionSignature& Sig = OutFunctions.AddDefaulted_GetRef(); Sig.Name = FNiagaraDataInterfaceArrayImplHelper::Function_IsValidIndexName; - //#if WITH_EDITORONLY_DATA - // Sig.Description = NSLOCTEXT("Niagara", "GetNumDescription", "This function returns the properties of the current view. Only valid for gpu particles."); - //#endif + #if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("Niagara", "Array_IsValidIndexDesc", "Tests to see if the index is valid and exists in the array."); + #endif Sig.bMemberFunction = true; Sig.bRequiresContext = false; Sig.bExperimental = true; @@ -123,9 +129,9 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl { FNiagaraFunctionSignature& Sig = OutFunctions.AddDefaulted_GetRef(); Sig.Name = FNiagaraDataInterfaceArrayImplHelper::Function_GetValueName; - //#if WITH_EDITORONLY_DATA - // Sig.Description = NSLOCTEXT("Niagara", "GetValueDescription", "This function returns the properties of the current view. Only valid for gpu particles."); - //#endif + #if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("Niagara", "Array_GetValueDesc", "Gets the value from the array at the given zero based index."); + #endif Sig.bMemberFunction = true; Sig.bRequiresContext = false; Sig.bExperimental = true; @@ -135,11 +141,98 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Index"))); Sig.Outputs.Add(FNiagaraVariable(FNDIArrayImplHelper::GetTypeDefinition(), TEXT("Value"))); } + + // Mutable functions + { + FNiagaraFunctionSignature& Sig = OutFunctions.AddDefaulted_GetRef(); + Sig.Name = FNiagaraDataInterfaceArrayImplHelper::Function_Reset; + #if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("Niagara", "Array_ResetDesc", "Resets the array back to 0 elements"); + #endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bExperimental = true; + Sig.bSupportsCPU = FNDIArrayImplHelper::bSupportsCPU; + Sig.bSupportsGPU = false; + Sig.bRequiresExecPin = true; + Sig.ModuleUsageBitmask = ENiagaraScriptUsageMask::System | ENiagaraScriptUsageMask::Emitter; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(TObjectType::StaticClass()), TEXT("Array interface"))); + } + + { + FNiagaraFunctionSignature& Sig = OutFunctions.AddDefaulted_GetRef(); + Sig.Name = FNiagaraDataInterfaceArrayImplHelper::Function_SetNumValue; + #if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("Niagara", "Array_SetNumDesc", "Sets the number of values in the array (i.e. a value of 10 means 10 elements, zero based indexed with 0-9), initializing new elements with the default value."); + #endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bExperimental = true; + Sig.bSupportsCPU = FNDIArrayImplHelper::bSupportsCPU; + Sig.bSupportsGPU = false; + Sig.bRequiresExecPin = true; + Sig.ModuleUsageBitmask = ENiagaraScriptUsageMask::System | ENiagaraScriptUsageMask::Emitter; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(TObjectType::StaticClass()), TEXT("Array interface"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Num"))); + } + + { + FNiagaraFunctionSignature& Sig = OutFunctions.AddDefaulted_GetRef(); + Sig.Name = FNiagaraDataInterfaceArrayImplHelper::Function_SetValueName; + #if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("Niagara", "Array_SetValueDesc", "Sets the array value at the given zero based index."); + #endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bExperimental = true; + Sig.bSupportsCPU = FNDIArrayImplHelper::bSupportsCPU; + Sig.bSupportsGPU = false; + Sig.bRequiresExecPin = true; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(TObjectType::StaticClass()), TEXT("Array interface"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Index"))); + Sig.Inputs.Add(FNiagaraVariable(FNDIArrayImplHelper::GetTypeDefinition(), TEXT("Value"))); + } + + { + FNiagaraFunctionSignature& Sig = OutFunctions.AddDefaulted_GetRef(); + Sig.Name = FNiagaraDataInterfaceArrayImplHelper::Function_PushValueName; + #if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("Niagara", "Array_PushValueDesc", "Optionally push value onto the end of the array."); + #endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bExperimental = true; + Sig.bSupportsCPU = FNDIArrayImplHelper::bSupportsCPU; + Sig.bSupportsGPU = false; + Sig.bRequiresExecPin = true; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(TObjectType::StaticClass()), TEXT("Array interface"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("SkipPush"))); + Sig.Inputs.Add(FNiagaraVariable(FNDIArrayImplHelper::GetTypeDefinition(), TEXT("Value"))); + } + + { + FNiagaraFunctionSignature& Sig = OutFunctions.AddDefaulted_GetRef(); + Sig.Name = FNiagaraDataInterfaceArrayImplHelper::Function_PopValueName; +#if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("Niagara", "Array_PopValueDesc", "Optionally pop value from the end of the array. Returns the default value if no elements are in the array."); +#endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bExperimental = true; + Sig.bSupportsCPU = FNDIArrayImplHelper::bSupportsCPU; + Sig.bSupportsGPU = false; + Sig.bRequiresExecPin = true; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(TObjectType::StaticClass()), TEXT("Array interface"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("SkipPop"))); + Sig.Outputs.Add(FNiagaraVariable(FNDIArrayImplHelper::GetTypeDefinition(), TEXT("Value"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("IsValid"))); + } } template> typename TEnableIf::Type GetVMExternalFunction_Internal(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction &OutFunc) { + // Immutable functions if (BindingInfo.Name == FNiagaraDataInterfaceArrayImplHelper::Function_GetNumName) { check(BindingInfo.GetNumInputs() == 0 && BindingInfo.GetNumOutputs() == 1); @@ -156,6 +249,31 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl //check(BindingInfo.GetNumInputs() == 1 && BindingInfo.GetNumOutputs() == 1); OutFunc = FVMExternalFunction::CreateLambda([this](FVectorVMContext& Context) { this->GetValue(Context); }); } + // Mutable functions + else if (BindingInfo.Name == FNiagaraDataInterfaceArrayImplHelper::Function_Reset) + { + check(BindingInfo.GetNumInputs() == 0 && BindingInfo.GetNumOutputs() == 0); + OutFunc = FVMExternalFunction::CreateLambda([this](FVectorVMContext& Context) { this->Reset(Context); }); + } + else if (BindingInfo.Name == FNiagaraDataInterfaceArrayImplHelper::Function_SetNumValue) + { + check(BindingInfo.GetNumInputs() == 1 && BindingInfo.GetNumOutputs() == 0); + OutFunc = FVMExternalFunction::CreateLambda([this](FVectorVMContext& Context) { this->SetNum(Context); }); + } + else if (BindingInfo.Name == FNiagaraDataInterfaceArrayImplHelper::Function_SetValueName) + { + OutFunc = FVMExternalFunction::CreateLambda([this](FVectorVMContext& Context) { this->SetValue(Context); }); + } + else if (BindingInfo.Name == FNiagaraDataInterfaceArrayImplHelper::Function_PushValueName) + { + // Note: Inputs is variable based upon type + OutFunc = FVMExternalFunction::CreateLambda([this](FVectorVMContext& Context) { this->PushValue(Context); }); + } + else if (BindingInfo.Name == FNiagaraDataInterfaceArrayImplHelper::Function_PopValueName) + { + // Note: Outputs is variable based upon type + OutFunc = FVMExternalFunction::CreateLambda([this](FVectorVMContext& Context) { this->PopValue(Context); }); + } } template> @@ -172,7 +290,7 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl typename TEnableIf::Type GetParameterDefinitionHLSL_Internal(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) const { OutHLSL.Appendf(TEXT("Buffer<%s> %s;\n"), FNDIArrayImplHelper::HLSLBufferTypeName, *FNiagaraDataInterfaceArrayImplHelper::GetBufferName(ParamInfo.DataInterfaceHLSLSymbol)); - OutHLSL.Appendf(TEXT("int %s[2];\n"), *FNiagaraDataInterfaceArrayImplHelper::GetBufferSizeName(ParamInfo.DataInterfaceHLSLSymbol)); + OutHLSL.Appendf(TEXT("int2 %s;\n"), *FNiagaraDataInterfaceArrayImplHelper::GetBufferSizeName(ParamInfo.DataInterfaceHLSLSymbol)); } template> @@ -188,6 +306,7 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl template> typename TEnableIf::Type GetFunctionHLSL_Internal(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, const FNiagaraDataInterfaceGeneratedFunction& FunctionInfo, int FunctionInstanceIndex, FString& OutHLSL) const { + // Immutable functions if (FunctionInfo.DefinitionName == FNiagaraDataInterfaceArrayImplHelper::Function_GetNumName) { OutHLSL.Appendf(TEXT("void %s(out int OutValue) { OutValue = %s[0]; }\n"), *FunctionInfo.InstanceName, *FNiagaraDataInterfaceArrayImplHelper::GetBufferSizeName(ParamInfo.DataInterfaceHLSLSymbol)); @@ -205,6 +324,22 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl OutHLSL.Append(TEXT(" }\n")); return true; } + // Mutable functions + //else if (FunctionInfo.DefinitionName == FNiagaraDataInterfaceArrayImplHelper::Function_Reset) + //{ + //} + //else if (FunctionInfo.DefinitionName == FNiagaraDataInterfaceArrayImplHelper::Function_SetNumValue) + //{ + //} + //else if (FunctionInfo.DefinitionName == FNiagaraDataInterfaceArrayImplHelper::Function_SetValueName) + //{ + //} + //else if (FunctionInfo.DefinitionName == FNiagaraDataInterfaceArrayImplHelper::Function_PushValueName) + //{ + //} + //else if (FunctionInfo.DefinitionName == FNiagaraDataInterfaceArrayImplHelper::Function_PopValueName) + //{ + //} return false; } @@ -222,7 +357,10 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl virtual bool CopyToInternal(INiagaraDataInterfaceArrayImpl* InDestination) const override { auto Destination = static_cast*>(InDestination); - Destination->Data = Data; + { + FRWScopeLock WriteLock(Owner->ArrayRWGuard, SLT_Write); + Destination->Data = Data; + } Destination->PushToRenderThread(); return true; } @@ -230,16 +368,19 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl virtual bool Equals(const INiagaraDataInterfaceArrayImpl* InOther) const override { auto Other = static_cast*>(InOther); + FRWScopeLock ReadLock(Owner->ArrayRWGuard, SLT_ReadOnly); return Other->Data == Data; } template> typename TEnableIf::Type PushToRenderThread_Internal() const { + FNiagaraDataInterfaceProxy* Proxy = Owner->GetProxy(); + //-TODO: Only create RT resource if we are servicing a GPU system ENQUEUE_RENDER_COMMAND(UpdateArray) ( - [RT_Proxy=static_cast(Proxy.Get()), RT_Array=TArray(Data)](FRHICommandListImmediate& RHICmdList) + [RT_Proxy=static_cast(Proxy), RT_Array=TArray(Data)](FRHICommandListImmediate& RHICmdList) { RT_Proxy->Buffer.Release(); RT_Proxy->NumElements = RT_Array.Num(); @@ -256,6 +397,20 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl FMemory::Memcpy(GPUMemory, RT_Array.GetData(), BufferSize); RHICmdList.UnlockVertexBuffer(RT_Proxy->Buffer.Buffer); } + else + { + const int32 BufferStride = T::GPUGetTypeStride(); + const int32 BufferSize = RT_Array.GetTypeSize(); + const int32 BufferNumElements = BufferSize / BufferStride; + check((sizeof(TArrayType) % BufferStride) == 0); + + const TArrayType DefaultValue = FNDIArrayImplHelper::GetDefaultValue(); + + RT_Proxy->Buffer.Initialize(BufferStride, BufferNumElements, FNDIArrayImplHelper::PixelFormat, BUF_Static, TEXT("NiagaraArrayFloat")); + void* GPUMemory = RHICmdList.LockVertexBuffer(RT_Proxy->Buffer.Buffer, 0, BufferSize, RLM_WriteOnly); + FMemory::Memcpy(GPUMemory, &DefaultValue, BufferSize); + RHICmdList.UnlockVertexBuffer(RT_Proxy->Buffer.Buffer); + } } ); } @@ -300,14 +455,8 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl FRHIComputeShader* ComputeShaderRHI = Context.Shader.GetComputeShader(); FNiagaraDataInterfaceProxyArrayImpl* DataInterface = static_cast(Context.DataInterface); - if (DataInterface->Buffer.NumBytes > 0) - { - static_cast(Base)->SetBuffer(RHICmdList, ComputeShaderRHI, DataInterface->Buffer.SRV, DataInterface->NumElements); - } - else - { - static_cast(Base)->SetBuffer(RHICmdList, ComputeShaderRHI, FNDIArrayImplHelper::GetDummyBuffer(), 0); - } + check(DataInterface->Buffer.NumBytes > 0); + static_cast(Base)->SetBuffer(RHICmdList, ComputeShaderRHI, DataInterface->Buffer.SRV, DataInterface->NumElements); } void UnsetParameters(const FNiagaraDataInterfaceParametersCS* Base, FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) const @@ -321,9 +470,9 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl { FNDIOutputParam OutValue(Context); - ArrayGuard.ReadLock(); + Owner->ArrayRWGuard.ReadLock(); const int32 Num = Data.Num(); - ArrayGuard.ReadUnlock(); + Owner->ArrayRWGuard.ReadUnlock(); for (int32 i = 0; i < Context.NumInstances; ++i) { OutValue.SetAndAdvance(Num); @@ -335,9 +484,9 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl FNDIInputParam IndexParam(Context); FNDIOutputParam OutValue(Context); - ArrayGuard.ReadLock(); + Owner->ArrayRWGuard.ReadLock(); const int32 Num = Data.Num(); - ArrayGuard.ReadUnlock(); + Owner->ArrayRWGuard.ReadUnlock(); for (int32 i = 0; i < Context.NumInstances; ++i) { const int32 Index = IndexParam.GetAndAdvance(); @@ -350,7 +499,7 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl FNDIInputParam IndexParam(Context); FNDIOutputParam::TVMArrayType> OutValue(Context); - FRWScopeLock ReadLock(ArrayGuard, SLT_ReadOnly); + FRWScopeLock ReadLock(Owner->ArrayRWGuard, SLT_ReadOnly); const int32 Num = Data.Num() - 1; if (Num >= 0) { @@ -369,4 +518,99 @@ struct FNiagaraDataInterfaceArrayImpl : public INiagaraDataInterfaceArrayImpl } } } + + void Reset(FVectorVMContext& Context) + { + //-TODO: This dirties the GPU data + ensureMsgf(Context.NumInstances == 1, TEXT("Setting the number of values in an array with more than one instance, which doesn't make sense")); + + FRWScopeLock WriteLock(Owner->ArrayRWGuard, SLT_Write); + Data.Reset(); + } + + void SetNum(FVectorVMContext& Context) + { + //-TODO: This dirties the GPU data + FNDIInputParam NewNumParam(Context); + + ensureMsgf(Context.NumInstances == 1, TEXT("Setting the number of values in an array with more than one instance, which doesn't make sense")); + + FRWScopeLock WriteLock(Owner->ArrayRWGuard, SLT_Write); + const int32 OldNum = Data.Num(); + const int32 NewNum = FMath::Min(NewNumParam.GetAndAdvance(), kSafeMaxElements); + Data.SetNumUninitialized(NewNum); + + if (NewNum > OldNum) + { + const TArrayType DefaultValue = FNDIArrayImplHelper::GetDefaultValue(); + for (int32 i = OldNum; i < NewNum; ++i) + { + Data[i] = DefaultValue; + } + } + } + + void SetValue(FVectorVMContext& Context) + { + //-TODO: This dirties the GPU data + FNDIInputParam IndexParam(Context); + FNDIInputParam::TVMArrayType> InValue(Context); + + FRWScopeLock WriteLock(Owner->ArrayRWGuard, SLT_Write); + for (int32 i = 0; i < Context.NumInstances; ++i) + { + const int32 Index = IndexParam.GetAndAdvance(); + const TArrayType Value = InValue.GetAndAdvance(); + + if (Data.IsValidIndex(Index)) + { + Data[Index] = Value; + } + } + } + + void PushValue(FVectorVMContext& Context) + { + //-TODO: This dirties the GPU data + FNDIInputParam InSkipExecute(Context); + FNDIInputParam::TVMArrayType> InValue(Context); + + const int32 MaxElements = Owner->MaxElements > 0 ? Owner->MaxElements : kSafeMaxElements; + + FRWScopeLock WriteLock(Owner->ArrayRWGuard, SLT_Write); + for (int32 i = 0; i < Context.NumInstances; ++i) + { + const bool bSkipExecute = InSkipExecute.GetAndAdvance(); + const TArrayType Value = InValue.GetAndAdvance(); + if (!bSkipExecute && (Data.Num() < MaxElements)) + { + Data.Emplace(Value); + } + } + } + + void PopValue(FVectorVMContext& Context) + { + //-TODO: This dirties the GPU data + FNDIInputParam InSkipExecute(Context); + FNDIOutputParam::TVMArrayType> OutValue(Context); + FNDIOutputParam OutIsValid(Context); + const TArrayType DefaultValue = FNDIArrayImplHelper::GetDefaultValue(); + + FRWScopeLock WriteLock(Owner->ArrayRWGuard, SLT_Write); + for (int32 i=0; i < Context.NumInstances; ++i) + { + const bool bSkipExecute = InSkipExecute.GetAndAdvance(); + if (bSkipExecute || (Data.Num() == 0)) + { + OutValue.SetAndAdvance(DefaultValue); + OutIsValid.SetAndAdvance(false); + } + else + { + OutValue.SetAndAdvance(Data.Pop()); + OutIsValid.SetAndAdvance(true); + } + } + } }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceAudioPlayer.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceAudioPlayer.h index 847400741e07..2e282ae949b1 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceAudioPlayer.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceAudioPlayer.h @@ -19,14 +19,27 @@ struct FAudioParticleData float StartTime = 1; }; +struct FPersistentAudioParticleData +{ + int32 AudioHandle = 0; + + /** The update callback is executed in PerInstanceTickPostSimulate, which runs on the game thread */ + TFunction UpdateCallback; +}; + struct FAudioPlayerInterface_InstanceData { /** We use a lock-free queue here because multiple threads might try to push data to it at the same time. */ - TQueue GatheredData; + TQueue PlayAudioQueue; + TQueue PersistentAudioActionQueue; + FThreadSafeCounter HandleCount; + + TSortedMap> PersistentAudioMapping; TWeakObjectPtr SoundToPlay; TWeakObjectPtr Attenuation; TWeakObjectPtr Concurrency; + TArray ParameterNames; int32 MaxPlaysPerTick = 0; }; @@ -49,6 +62,10 @@ public: /** Optional sound concurrency setting to use */ UPROPERTY(EditAnywhere, Category = "Audio") USoundConcurrency* Concurrency; + + /** A set of parameter names that can be referenced via index when setting sound cue parameters on persistent audio */ + UPROPERTY(EditAnywhere, Category = "Parameters") + TArray ParameterNames; UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Audio", meta = (InlineEditConditionToggle)) bool bLimitPlaysPerTick; @@ -79,11 +96,29 @@ public: virtual bool HasPostSimulateTick() const override { return true; } //UNiagaraDataInterface Interface - virtual void StoreData(FVectorVMContext& Context); + virtual void PlayOneShotAudio(FVectorVMContext& Context); + virtual void PlayPersistentAudio(FVectorVMContext& Context); + virtual void SetParameterBool(FVectorVMContext& Context); + virtual void SetParameterInteger(FVectorVMContext& Context); + virtual void SetParameterFloat(FVectorVMContext& Context); + virtual void UpdateVolume(FVectorVMContext& Context); + virtual void UpdatePitch(FVectorVMContext& Context); + virtual void UpdateLocation(FVectorVMContext& Context); + virtual void UpdateRotation(FVectorVMContext& Context); + virtual void SetPausedState(FVectorVMContext& Context); protected: virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; private: static const FName PlayAudioName; + static const FName PlayPersistentAudioName; + static const FName SetPersistentAudioVolumeName; + static const FName SetPersistentAudioPitchName; + static const FName SetPersistentAudioLocationName; + static const FName SetPersistentAudioRotationName; + static const FName SetPersistentAudioBoolParamName; + static const FName SetPersistentAudioIntegerParamName; + static const FName SetPersistentAudioFloatParamName; + static const FName PausePersistentAudioName; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceCamera.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceCamera.h index 0b967862ddf9..daaa300bece8 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceCamera.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceCamera.h @@ -4,14 +4,22 @@ #include "NiagaraCommon.h" #include "NiagaraShared.h" #include "NiagaraDataInterface.h" -#include "Camera/PlayerCameraManager.h" #include "NiagaraDataInterfaceCamera.generated.h" +struct FDistanceData +{ + FNiagaraID ParticleID; + float DistanceSquared; +}; + struct CameraDataInterface_InstanceData { FVector CameraLocation; FRotator CameraRotation; float CameraFOV; + + TQueue DistanceSortQueue; + TArray ParticlesSortedByDistance; }; UCLASS(EditInlineNew, Category = "Camera", meta = (DisplayName = "Camera Query")) @@ -56,6 +64,8 @@ public: #endif //UNiagaraDataInterface Interface + void CalculateParticleDistances(FVectorVMContext& Context); + void GetClosestParticles(FVectorVMContext& Context); void GetCameraFOV(FVectorVMContext& Context); void GetCameraProperties(FVectorVMContext& Context); void GetViewPropertiesGPU(FVectorVMContext& Context); @@ -66,6 +76,8 @@ public: protected: virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; private: + static const FName CalculateDistancesName; + static const FName QueryClosestName; static const FName GetViewPropertiesName; static const FName GetClipSpaceTransformsName; static const FName GetViewSpaceTransformsName; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceGrid2DCollection.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceGrid2DCollection.h index 89c4a7e33f20..5f36d46e3722 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceGrid2DCollection.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceGrid2DCollection.h @@ -31,6 +31,8 @@ struct FGrid2DCollectionRWInstanceData_GameThread /** A binding to the user ptr we're reading the RT from (if we are). */ FNiagaraParameterDirectBinding RTUserParamBinding; + + UTextureRenderTarget2D* TargetTexture = nullptr; }; struct FGrid2DCollectionRWInstanceData_RenderThread @@ -44,21 +46,22 @@ struct FGrid2DCollectionRWInstanceData_RenderThread FGrid2DBuffer* CurrentData = nullptr; FGrid2DBuffer* DestinationData = nullptr; - FRHITexture* RenderTargetToCopyTo; + FTextureRHIRef RenderTargetToCopyTo; void BeginSimulate(); void EndSimulate(); + void* DebugTargetTexture = nullptr; }; struct FNiagaraDataInterfaceProxyGrid2DCollectionProxy : public FNiagaraDataInterfaceProxyRW { FNiagaraDataInterfaceProxyGrid2DCollectionProxy() {} + + virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) override; + virtual void PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) override; + virtual void PostSimulate(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) override; - virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - virtual void PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - virtual void PostSimulate(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - - virtual void ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; + virtual void ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) override; /* List of proxy data for each system instances*/ // #todo(dmp): this should all be refactored to avoid duplicate code @@ -79,6 +82,9 @@ public: UPROPERTY(EditAnywhere, Category = "Grid2DCollection") FNiagaraUserParameterBinding RenderTargetUserParameter; + UPROPERTY(EditAnywhere, Category = "Grid2DCollection") + uint8 bCreateRenderTarget : 1; + virtual void PostInitProperties() override; //~ UNiagaraDataInterface interface @@ -98,6 +104,11 @@ public: virtual bool PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) override; virtual int32 PerInstanceDataSize()const override { return sizeof(FGrid2DCollectionRWInstanceData_GameThread); } virtual bool HasPreSimulateTick() const override { return true; } + + virtual bool CanExposeVariables() const override { return true;} + virtual void GetExposedVariables(TArray& OutVariables) const override; + virtual bool GetExposedVariableValue(const FNiagaraVariableBase& InVariable, void* InPerInstanceData, FNiagaraSystemInstance* InSystemInstance, void* OutData) const override; + //~ UNiagaraDataInterface interface END // Fills a texture render target 2d with the current data from the simulation @@ -116,6 +127,7 @@ public: void GetWorldBBoxSize(FVectorVMContext& Context); void GetCellSize(FVectorVMContext& Context); + void GetNumCells(FVectorVMContext& Context); static const FString NumTilesName; @@ -127,11 +139,18 @@ public: static const FName GetValueFunctionName; static const FName SampleGridFunctionName; + protected: //~ UNiagaraDataInterface interface virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; + //~ UNiagaraDataInterface interface END + static FNiagaraVariableBase ExposedRTVar; + TMap SystemInstancesToProxyData_GT; + + UPROPERTY(Transient) + TMap< uint64, UTextureRenderTarget2D*> ManagedRenderTargets; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceGrid3DCollection.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceGrid3DCollection.h index 1df4434cdf87..082c51f8d7b8 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceGrid3DCollection.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceGrid3DCollection.h @@ -9,7 +9,7 @@ #include "NiagaraDataInterfaceGrid3DCollection.generated.h" class FNiagaraSystemInstance; -class UTextureRenderTarget2D; +class UTextureRenderTargetVolume; class FGrid3DBuffer { @@ -28,6 +28,9 @@ struct FGrid3DCollectionRWInstanceData_GameThread FIntVector NumTiles = FIntVector::ZeroValue; FVector CellSize = FVector::ZeroVector; FVector WorldBBoxSize = FVector::ZeroVector; + + /** A binding to the user ptr we're reading the RT from (if we are). */ + FNiagaraParameterDirectBinding RTUserParamBinding; }; struct FGrid3DCollectionRWInstanceData_RenderThread @@ -41,6 +44,8 @@ struct FGrid3DCollectionRWInstanceData_RenderThread FGrid3DBuffer* CurrentData = nullptr; FGrid3DBuffer* DestinationData = nullptr; + FTextureRHIRef RenderTargetToCopyTo; + void BeginSimulate(); void EndSimulate(); }; @@ -49,9 +54,10 @@ struct FNiagaraDataInterfaceProxyGrid3DCollectionProxy : public FNiagaraDataInte { FNiagaraDataInterfaceProxyGrid3DCollectionProxy() {} - virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - virtual void PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; - virtual void ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; + virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) override; + virtual void PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) override; + virtual void PostSimulate(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) override; + virtual void ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) override; /* List of proxy data for each system instances*/ // #todo(dmp): this should all be refactored to avoid duplicate code @@ -70,6 +76,10 @@ public: UPROPERTY(EditAnywhere, Category = "Grid") int32 NumAttributes; + /** Reference to a user parameter if we're reading one. */ + UPROPERTY(EditAnywhere, Category = "Grid3DCollection") + FNiagaraUserParameterBinding RenderTargetUserParameter; + virtual void PostInitProperties() override; //~ UNiagaraDataInterface interface @@ -86,7 +96,7 @@ public: virtual void ProvidePerInstanceDataForRenderThread(void* DataForRenderThread, void* PerInstanceData, const FNiagaraSystemInstanceID& SystemInstance) override {} virtual bool InitPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) override; virtual void DestroyPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) override; - virtual bool PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) override { return false; } + virtual bool PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) override; virtual int32 PerInstanceDataSize()const override { return sizeof(FGrid3DCollectionRWInstanceData_GameThread); } virtual bool HasPreSimulateTick() const override { return true; } //~ UNiagaraDataInterface interface END @@ -95,10 +105,10 @@ public: // #todo(dmp): this will eventually go away when we formalize how data makes it out of Niagara // #todo(dmp): reimplement for 3d - UFUNCTION(BlueprintCallable, Category = Niagara) + UFUNCTION(BlueprintCallable, Category = Niagara, meta = (DeprecatedFunction, DeprecationMessage = "This function has been replaced by object user variables on the emitter to specify render targets to fill with data.")) virtual bool FillVolumeTexture(const UNiagaraComponent *Component, UVolumeTexture *dest, int AttributeIndex); - UFUNCTION(BlueprintCallable, Category = Niagara) + UFUNCTION(BlueprintCallable, Category = Niagara, meta = (DeprecatedFunction, DeprecationMessage = "This function has been replaced by object user variables on the emitter to specify render targets to fill with data.")) virtual bool FillRawVolumeTexture(const UNiagaraComponent *Component, UVolumeTexture*Dest, int &TilesX, int &TilesY, int &TileZ); UFUNCTION(BlueprintCallable, Category = Niagara) diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceNeighborGrid3D.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceNeighborGrid3D.h index 0937771b1120..d3b955ef759c 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceNeighborGrid3D.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceNeighborGrid3D.h @@ -31,7 +31,7 @@ public: struct FNiagaraDataInterfaceProxyNeighborGrid3D : public FNiagaraDataInterfaceProxyRW { - virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) override; + virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) override; virtual void ConsumePerInstanceDataFromGameThread(void* PerInstanceData, const FNiagaraSystemInstanceID& Instance) override {} virtual int32 PerInstanceDataPassedToRenderThreadSize() const override { return sizeof(NeighborGrid3DRWInstanceData); } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceRW.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceRW.h index e2ab6afd088e..7d459dd514d6 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceRW.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceRW.h @@ -24,12 +24,15 @@ extern NIAGARA_API const FName SimulationToUnitFunctionName; extern NIAGARA_API const FName UnitToSimulationFunctionName; extern NIAGARA_API const FName UnitToIndexFunctionName; extern NIAGARA_API const FName IndexToUnitFunctionName; + extern NIAGARA_API const FName IndexToUnitStaggeredXFunctionName; extern NIAGARA_API const FName IndexToUnitStaggeredYFunctionName; extern NIAGARA_API const FName IndexToLinearFunctionName; extern NIAGARA_API const FName LinearToIndexFunctionName; +extern NIAGARA_API const FName ExecutionIndexToGridIndexFunctionName; +extern NIAGARA_API const FName ExecutionIndexToUnitFunctionName; UENUM() enum class ESetResolutionMethod { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceRenderTarget2D.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceRenderTarget2D.h new file mode 100644 index 000000000000..2b421a2c354f --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceRenderTarget2D.h @@ -0,0 +1,111 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "NiagaraDataInterface.h" +#include "ClearQuad.h" +#include "NiagaraComponent.h" +#include "NiagaraDataInterfaceRenderTarget2D.generated.h" + +class FNiagaraSystemInstance; +class UTextureRenderTarget2D; + + +struct FRenderTarget2DRWInstanceData_GameThread +{ + FIntPoint Size = FIntPoint(EForceInit::ForceInitToZero); + + UTextureRenderTarget2D* TargetTexture = nullptr; + +}; + +struct FRenderTarget2DRWInstanceData_RenderThread +{ + FIntPoint Size = FIntPoint(EForceInit::ForceInitToZero); + + FTextureRHIRef RenderTargetToCopyTo; + FUnorderedAccessViewRHIRef UAV; + + void* DebugTargetTexture = nullptr; +}; + +struct FNiagaraDataInterfaceProxyRenderTarget2DProxy : public FNiagaraDataInterfaceProxy +{ + FNiagaraDataInterfaceProxyRenderTarget2DProxy() {} + virtual void ConsumePerInstanceDataFromGameThread(void* PerInstanceData, const FNiagaraSystemInstanceID& Instance) override {} + virtual int32 PerInstanceDataPassedToRenderThreadSize() const override + { + return 0; + } + + virtual void ClearBuffers(FRHICommandList& RHICmdList) {} + virtual void PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) override; + virtual void PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) override; + virtual void PostSimulate(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) override; + + virtual void ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) override; + + /* List of proxy data for each system instances*/ + // #todo(dmp): this should all be refactored to avoid duplicate code + TMap SystemInstancesToProxyData_RT; +}; + +UCLASS(EditInlineNew, Category = "Grid", meta = (DisplayName = "Render Target 2D", Experimental), Blueprintable, BlueprintType) +class NIAGARA_API UNiagaraDataInterfaceRenderTarget2D : public UNiagaraDataInterface +{ + GENERATED_UCLASS_BODY() + +public: + + DECLARE_NIAGARA_DI_PARAMETER(); + + virtual void PostInitProperties() override; + + //~ UNiagaraDataInterface interface + // VM functionality + virtual bool CanExecuteOnTarget(ENiagaraSimTarget Target)const override { return true; } + virtual void GetFunctions(TArray& OutFunctions) override; + virtual void GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction &OutFunc) override; + + virtual bool Equals(const UNiagaraDataInterface* Other) const override; + + // GPU sim functionality + virtual void GetParameterDefinitionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; + virtual bool GetFunctionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, const FNiagaraDataInterfaceGeneratedFunction& FunctionInfo, int FunctionInstanceIndex, FString& OutHLSL) override; + + virtual void ProvidePerInstanceDataForRenderThread(void* DataForRenderThread, void* PerInstanceData, const FNiagaraSystemInstanceID& SystemInstance) override {} + virtual bool InitPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) override; + virtual void DestroyPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) override; + virtual bool PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) override; + virtual int32 PerInstanceDataSize()const override { return sizeof(FRenderTarget2DRWInstanceData_GameThread); } + virtual bool PerInstanceTickPostSimulate(void* PerInstanceData, FNiagaraSystemInstance* InSystemInstance, float DeltaSeconds) override; + virtual bool HasPreSimulateTick() const override { return true; } + virtual bool HasPostSimulateTick() const override { return true; } + + virtual bool CanExposeVariables() const override { return true;} + virtual void GetExposedVariables(TArray& OutVariables) const override; + virtual bool GetExposedVariableValue(const FNiagaraVariableBase& InVariable, void* InPerInstanceData, FNiagaraSystemInstance* InSystemInstance, void* OutData) const override; + + //~ UNiagaraDataInterface interface END + void GetSize(FVectorVMContext& Context); + void SetSize(FVectorVMContext& Context); + + static const FName SetValueFunctionName; + static const FName GetValueFunctionName; + static const FName SetSizeFunctionName; + static const FName GetSizeFunctionName; + + static const FString SizeName; + static const FString OutputName; + +protected: + //~ UNiagaraDataInterface interface + virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; + + //~ UNiagaraDataInterface interface END + + static FNiagaraVariableBase ExposedRTVar; + + UPROPERTY(Transient) + TMap< uint64, UTextureRenderTarget2D*> ManagedRenderTargets; + +}; \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceSkeletalMesh.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceSkeletalMesh.h index 8db4aef3bcb7..9212eb464dda 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceSkeletalMesh.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceSkeletalMesh.h @@ -444,20 +444,18 @@ private: struct FNDISkeletalMesh_InstanceData { - //Cached ptr to component we sample from. TODO: This should not need to be a weak ptr. We should always be clearing out DIs when the component is destroyed. - TWeakObjectPtr Component; + /** Cached ptr to SkeletalMeshComponent we sample from, when found. Otherwise, the scene component to use to transform the PreviewMesh */ + TWeakObjectPtr SceneComponent; /** A binding to the user ptr we're reading the mesh from (if we are). */ FNiagaraParameterDirectBinding UserParamBinding; - //Always reset the DI when the attach parent changes. + /** Always reset the DI when the attach parent changes. */ TWeakObjectPtr CachedAttachParent; UObject* CachedUserParam; - USkeletalMesh* Mesh; - - TWeakObjectPtr MeshSafe; + TWeakObjectPtr SkeletalMesh; /** Handle to our skinning data. */ FSkeletalMeshSkinningDataHandle SkinningData; @@ -468,12 +466,12 @@ struct FNDISkeletalMesh_InstanceData /** Additional sampler for if we need to do area weighting sampling across multiple area weighted regions. */ FSkeletalMeshSamplingRegionAreaWeightedSampler SamplingRegionAreaWeightedSampler; - //Cached ComponentToWorld. + /** Cached ComponentToWorld of the mesh (falls back to WorldTransform of the system instance). */ FMatrix Transform; - //InverseTranspose of above for transforming normals/tangents. + /** InverseTranspose of above for transforming normals/tangents. */ FMatrix TransformInverseTransposed; - //Cached ComponentToWorld from previous tick. + /** Cached ComponentToWorld from previous tick. */ FMatrix PrevTransform; /** Time separating Transform and PrevTransform. */ @@ -482,9 +480,9 @@ struct FNDISkeletalMesh_InstanceData /* Excluded bone for some specific functions, generally the root bone which you don't want to include when picking a random bone. */ int32 ExcludedBoneIndex = INDEX_NONE; - /* Number of filtered bones in the array. */ + /** Number of filtered bones in the array. */ int32 NumFilteredBones = 0; - /* Number of unfiltered bones in the array. */ + /** Number of unfiltered bones in the array. */ int32 NumUnfilteredBones = 0; /** Indices of the bones filtered by the user followed by the unfiltered bones, if this array is empty no filtering is in effect. */ TArray FilteredAndUnfilteredBones; @@ -508,6 +506,12 @@ struct FNDISkeletalMesh_InstanceData uint32 ChangeId; + /** True if SceneComponent was valid on initialization (used to track invalidation of the component on tick) */ + uint32 bComponentValid : 1; + + /** True if StaticMesh was valid on initialization (used to track invalidation of the mesh on tick) */ + uint32 bMeshValid : 1; + /** True if the mesh we're using allows area weighted sampling on GPU. */ uint32 bIsGpuUniformlyDistributedSampling : 1; @@ -534,7 +538,7 @@ struct FNDISkeletalMesh_InstanceData /** The referenced LOD data, used to prevent streaming out LODs while they are being referenced*/ TRefCountPtr CachedLODData; - FORCEINLINE_DEBUGGABLE bool ResetRequired(UNiagaraDataInterfaceSkeletalMesh* Interface)const; + FORCEINLINE_DEBUGGABLE bool ResetRequired(UNiagaraDataInterfaceSkeletalMesh* Interface, FNiagaraSystemInstance* SystemInstance) const; bool Init(UNiagaraDataInterfaceSkeletalMesh* Interface, FNiagaraSystemInstance* SystemInstance); FORCEINLINE_DEBUGGABLE bool Tick(UNiagaraDataInterfaceSkeletalMesh* Interface, FNiagaraSystemInstance* SystemInstance, float InDeltaSeconds); @@ -543,7 +547,7 @@ struct FNDISkeletalMesh_InstanceData FORCEINLINE_DEBUGGABLE const FSkinWeightVertexBuffer* GetSkinWeights() { - USkeletalMeshComponent* SkelComp = Cast(Component.Get()); + USkeletalMeshComponent* SkelComp = Cast(SceneComponent.Get()); if (SkelComp != nullptr && SkelComp->SkeletalMesh != nullptr) { return SkelComp->GetSkinWeightBuffer(CachedLODIdx); @@ -656,7 +660,7 @@ public: virtual bool AppendCompileHash(FNiagaraCompileHashVisitor* InVisitor) const override; //~ UNiagaraDataInterface interface END - USkeletalMesh* GetSkeletalMesh(class UNiagaraComponent* OwningComponent, TWeakObjectPtr& SceneComponent, USkeletalMeshComponent*& FoundSkelComp, FNDISkeletalMesh_InstanceData* InstData = nullptr); + USkeletalMesh* GetSkeletalMesh(FNiagaraSystemInstance* SystemInstance, TWeakObjectPtr& SceneComponent, USkeletalMeshComponent*& FoundSkelComp, FNDISkeletalMesh_InstanceData* InstData = nullptr); virtual void GetCommonHLSL(FString& OutHLSL) override; virtual void GetParameterDefinitionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceStaticMesh.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceStaticMesh.h index 064fc451cca7..f736bdb8c67e 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceStaticMesh.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceStaticMesh.h @@ -116,23 +116,18 @@ protected: struct FNDIStaticMesh_InstanceData { - FNDIStaticMesh_InstanceData() : Mesh(nullptr) {} + /** Cached ptr to StaticMeshComponent we sample from, when found. Otherwise, the SceneComponent to use to transform the Default or Preview mesh. */ + TWeakObjectPtr SceneComponent; - //Cached ptr to component we sample from. - TWeakObjectPtr SafeComponent_GT; + /** Cached ptr to the mesh so that we can make sure that we haven't been deleted. */ + TWeakObjectPtr StaticMesh; - // Cached ptr to the mesh so that we can make sure that we haven't been deleted. - TWeakObjectPtr SafeMesh_GT; - - //Cached ptr to actual mesh we sample from. - UStaticMesh* Mesh; - - //Cached ComponentToWorld. + /** Cached ComponentToWorld. (Falls back to WorldTransform of the system instance) */ FMatrix Transform; - //InverseTranspose of above for transforming normals/tangents. + /** InverseTranspose of above for transforming normals/tangents. */ FMatrix TransformInverseTransposed; - //Cached ComponentToWorld from previous tick. + /** Cached ComponentToWorld from previous tick. */ FMatrix PrevTransform; /** Time separating Transform and PrevTransform. */ @@ -143,6 +138,12 @@ struct FNDIStaticMesh_InstanceData /** True if velocity should not be calculated via the transforms, but rather read the physics data from the mesh component */ uint32 bUsePhysicsVelocity : 1; + /** True if SceneComponent was valid on initialization (used to track invalidation of the component on tick) */ + uint32 bComponentValid : 1; + + /** True if StaticMesh was valid on initialization (used to track invalidation of the mesh on tick) */ + uint32 bMeshValid : 1; + /** True if the mesh allows CPU access. Use to reset the instance in the editor*/ uint32 bMeshAllowsCpuAccess : 1; /** True if the mesh we're using allows area weighted sampling on CPU. */ @@ -175,11 +176,7 @@ struct FNDIStaticMesh_InstanceData /** Filter sockets followed by unfiltered sockets */ TArray FilteredAndUnfilteredSockets; - FORCEINLINE UStaticMesh* GetActualMesh()const { return Mesh; } FORCEINLINE bool UsesCpuUniformlyDistributedSampling() const { return bIsCpuUniformlyDistributedSampling; } - FORCEINLINE bool MeshHasPositions()const { return Mesh && Mesh->RenderData->LODResources[0].VertexBuffers.PositionVertexBuffer.GetNumVertices() > 0; } - FORCEINLINE bool MeshHasVerts()const { return Mesh && Mesh->RenderData->LODResources[0].VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() > 0; } - FORCEINLINE bool MeshHasColors()const { return Mesh && Mesh->RenderData->LODResources[0].VertexBuffers.ColorVertexBuffer.GetNumVertices() > 0; } FORCEINLINE_DEBUGGABLE bool ResetRequired(UNiagaraDataInterfaceStaticMesh* Interface)const; @@ -194,6 +191,8 @@ struct FNDIStaticMesh_InstanceData FORCEINLINE const FStaticMeshLODResources* GetCurrentFirstLOD() { + UStaticMesh* Mesh = StaticMesh.Get(); + check(Mesh); // sanity - should have been checked for GC earlier return Mesh->RenderData->GetCurrentFirstLOD(MinLOD); } }; @@ -273,7 +272,8 @@ public: virtual bool HasPreSimulateTick() const override { return true; } #if WITH_EDITOR - virtual TArray GetErrors() override; + virtual void GetFeedback(UNiagaraSystem* Asset, UNiagaraComponent* Component, TArray& OutErrors, + TArray& OutWarnings, TArray& OutInfo) override; #endif virtual void GetParameterDefinitionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; @@ -304,7 +304,7 @@ public: static const FString NumSocketsAndFilteredName; public: - TWeakObjectPtr GetStaticMesh(TWeakObjectPtr& OutComponent, class FNiagaraSystemInstance* SystemInstance = nullptr); + UStaticMesh* GetStaticMesh(TWeakObjectPtr& OutComponent, class FNiagaraSystemInstance* SystemInstance = nullptr); void IsValid(FVectorVMContext& Context); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceTexture.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceTexture.h index a7e0c464914c..dec75504d626 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceTexture.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceTexture.h @@ -50,12 +50,12 @@ public: static const FString TextureName; static const FString SamplerName; static const FString DimensionsBaseName; + protected: virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; void PushToRenderThread(); - static const FName SampleTexture2DName; static const FName SampleVolumeTextureName; static const FName SamplePseudoVolumeTextureName; @@ -64,8 +64,8 @@ protected: struct FNiagaraDataInterfaceProxyTexture : public FNiagaraDataInterfaceProxy { + FTextureReferenceRHIRef TextureReferenceRHI; FSamplerStateRHIRef SamplerStateRHI; - FTextureRHIRef TextureRHI; FVector2D TexDims; virtual void ConsumePerInstanceDataFromGameThread(void* PerInstanceData, const FNiagaraSystemInstanceID& Instance) override { check(false); } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSet.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSet.h index 7085388c1a7f..dad0de3665d9 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSet.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSet.h @@ -396,7 +396,6 @@ private: void BuildLayout(); void ResetBuffersInternal(); - void ReleaseBuffers(); FORCEINLINE void CheckCorrectThread()const { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSetAccessor.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSetAccessor.h index 46f9c409abe4..dc84ae13c8b7 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSetAccessor.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSetAccessor.h @@ -85,7 +85,7 @@ struct FNiagaraDataSetReaderFloat check(ElementIndex < FNiagaraDataSetAccessorTypeInfo::NumElements); if (!bSupportsHalf || bIsFloat) { - float MaxValue = FLT_MIN; + float MaxValue = -FLT_MAX; for (int i = 0; i < NumInstances; ++i) { MaxValue = FMath::Max(MaxValue, reinterpret_cast(ComponentData[ElementIndex])[i]); @@ -110,7 +110,7 @@ struct FNiagaraDataSetReaderFloat if (!bSupportsHalf || bIsFloat) { float MinValue = FLT_MAX; - float MaxValue = FLT_MIN; + float MaxValue = -FLT_MAX; for (int i = 0; i < NumInstances; ++i) { MinValue = FMath::Min(MinValue, reinterpret_cast(ComponentData[ElementIndex])[i]); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitter.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitter.h index d599caf8f340..546dd22efb01 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitter.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitter.h @@ -7,7 +7,7 @@ #include "UObject/ObjectMacros.h" #include "UObject/Object.h" #include "NiagaraScript.h" -#include "NiagaraCollision.h" +#include "NiagaraMessageDataBase.h" #include "INiagaraMergeManager.h" #include "NiagaraEffectType.h" #include "NiagaraDataSetAccessor.h" @@ -467,6 +467,10 @@ public: bool UsesScript(const UNiagaraScript* Script)const; //bool UsesDataInterface(UNiagaraDataInterface* Interface); bool UsesCollection(const class UNiagaraParameterCollection* Collection)const; + bool CanObtainParticleAttribute(const FNiagaraVariableBase& InVar) const; + bool CanObtainEmitterAttribute(const FNiagaraVariableBase& InVarWithUniqueNameNamespace) const; + bool CanObtainSystemAttribute(const FNiagaraVariableBase& InVar) const; + bool CanObtainUserVariable(const FNiagaraVariableBase& InVar) const; #if !UE_BUILD_SHIPPING const TCHAR* GetDebugSimName() const { return *DebugSimName; } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstance.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstance.h index e9f155e54cc9..df7eb5fe6c80 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstance.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstance.h @@ -78,6 +78,8 @@ public: /** Potentially reads back data from the GPU which will introduce a stall and should only be used for debug purposes. */ NIAGARA_API void CalculateFixedBounds(const FTransform& ToWorldSpace); #endif + + bool GetBoundRendererValue_GT(const FNiagaraVariableBase& InBaseVar, const FNiagaraVariableBase& InSubVar, void* OutValueData) const; FNiagaraDataSet& GetData()const { return *ParticleDataSet; } @@ -139,6 +141,10 @@ public: bool HasTicked() const { return TickCount > 0; } + const FNiagaraParameterStore& GetRendererBoundVariables() const { return RendererBindings; } + FNiagaraParameterStore& GetRendererBoundVariables() { return RendererBindings; } + + private: void CheckForErrors(); @@ -164,6 +170,7 @@ private: FNiagaraParameterDirectBinding UpdateExecCountBinding; TSharedPtr CachedEmitterCompiledData; + FNiagaraParameterStore RendererBindings; TUniquePtr EventInstanceData; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstanceBatcher.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstanceBatcher.h index 1d79d68e21fc..d25d3efaecec 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstanceBatcher.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstanceBatcher.h @@ -29,7 +29,8 @@ enum class ETickStage { PreInitViews, PostInitViews, - PostOpaqueRender + PostOpaqueRender, + Max }; class NiagaraEmitterInstanceBatcher : public FFXSystemInterface @@ -109,7 +110,7 @@ public: void ProcessDebugInfo(FRHICommandList &RHICmdLis, const FNiagaraComputeExecutionContext* Context) const; void SetDataInterfaceParameters(const TArray& DataInterfaceProxies, const FNiagaraShaderRef& Shader, FRHICommandList &RHICmdList, const FNiagaraComputeInstanceData* Instance, const FNiagaraGPUSystemTick& Tick, uint32 SimulationStageIndex) const; - void UnsetDataInterfaceParameters(const TArray& DataInterfaceProxies, const FNiagaraShaderRef& Shader, FRHICommandList& RHICmdList, const FNiagaraComputeInstanceData* Instance, const FNiagaraGPUSystemTick& Tick) const; + void UnsetDataInterfaceParameters(const TArray& DataInterfaceProxies, const FNiagaraShaderRef& Shader, FRHICommandList& RHICmdList, const FNiagaraComputeInstanceData* Instance, const FNiagaraGPUSystemTick& Tick, uint32 SimulationStageIndex) const; void Run( const FNiagaraGPUSystemTick& Tick, const FNiagaraComputeInstanceData* Instance, @@ -134,7 +135,7 @@ public: FORCEINLINE ERHIFeatureLevel::Type GetFeatureLevel() const { return FeatureLevel; } /** Reset the data interfaces and check if the spawn stages are valid */ - bool ResetDataInterfaces(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData *Instance, FRHICommandList &RHICmdList, const FNiagaraShaderRef& ComputeShader ) const; + bool ResetDataInterfaces(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData *Instance, FRHICommandList &RHICmdList, const FNiagaraShaderScript* ShaderScript) const; /** Given a shader stage index, find the corresponding data interface */ FNiagaraDataInterfaceProxy* FindIterationInterface(FNiagaraComputeInstanceData *Instance, const uint32 SimulationStageIndex) const; @@ -146,7 +147,7 @@ public: void PostStageInterface(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData *Instance, FRHICommandList &RHICmdList, const FNiagaraShaderRef& ComputeShader, const uint32 SimulationStageIndex) const; /** Loop over all data interfaces and call the postsimulate methods */ - void PostSimulateInterface(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData* Instance, FRHICommandList& RHICmdList, const FNiagaraShaderRef& ComputeShader) const; + void PostSimulateInterface(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData* Instance, FRHICommandList& RHICmdList, const FNiagaraShaderScript* ShaderScript) const; NIAGARA_API FRHIUnorderedAccessView* GetEmptyRWBufferFromPool(FRHICommandList& RHICmdList, EPixelFormat Format) const { return GetEmptyUAVFromPool(RHICmdList, Format, false); } NIAGARA_API FRHIUnorderedAccessView* GetEmptyRWTextureFromPool(FRHICommandList& RHICmdList, EPixelFormat Format) const { return GetEmptyUAVFromPool(RHICmdList, Format, true); } @@ -157,13 +158,14 @@ public: private: using FEmitterInstanceList = TArray; + void UpdateInstanceCountManager(FRHICommandListImmediate& RHICmdList); + void BuildTickStagePasses(FRHICommandListImmediate& RHICmdList); + void ExecuteAll(FRHICommandList& RHICmdList, FRHIUniformBuffer* ViewUniformBuffer, ETickStage TickStage); - void ResizeBuffersAndGatherResources(FOverlappableTicks& OverlappableTick, FRHICommandList& RHICmdList, FNiagaraBufferArray& OutputGraphicsBuffers, FEmitterInstanceList& InstancesWithPersistentIDs); + void GatherResources(FOverlappableTicks& OverlappableTick, FRHICommandList& RHICmdList, FNiagaraBufferArray& OutputGraphicsBuffers, FEmitterInstanceList& InstancesWithPersistentIDs); void TransitionBuffers(FRHICommandList& RHICmdList, const FNiagaraShaderRef& ComputeShader, FNiagaraComputeInstanceData* Instance, uint32 SimulationStageIndex, const FNiagaraDataBuffer*& LastSource, bool& bFreeIDTableTransitioned); void DispatchAllOnCompute(FOverlappableTicks& OverlappableTick, FRHICommandList& RHICmdList, FRHIUniformBuffer* ViewUniformBuffer); - void DispatchMultipleStages(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData* Instance, FRHICommandList& RHICmdList, FRHIUniformBuffer* ViewUniformBuffer, const FNiagaraShaderRef& ComputeShader); - - bool ShouldTickForStage(const FNiagaraGPUSystemTick& Tick, ETickStage TickStage) const; + void DispatchMultipleStages(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData* Instance, FRHICommandList& RHICmdList, FRHIUniformBuffer* ViewUniformBuffer, const FNiagaraShaderScript* ShaderScript); /** * Generate all the initial keys and values for a GPUSortManager sort batch. @@ -263,4 +265,11 @@ private: FRHIUnorderedAccessView* GetEmptyUAVFromPool(FRHICommandList& RHICmdList, EPixelFormat Format, bool IsTexture) const; void ResetEmptyUAVPool(TMap& UAVMap, TArray& Transitions); void ResetEmptyUAVPools(FRHICommandList& RHICmdList); + + uint32 NumTicksThatRequireDistanceFieldData = 0; + uint32 NumTicksThatRequireDepthBuffer = 0; + uint32 NumTicksThatRequireEarlyViewData = 0; + + TArray ContextsPerStage[(int)ETickStage::Max]; + TArray TicksPerStage[(int)ETickStage::Max]; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScript.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScript.h index 9b4f9cdb3ea5..ebe70e2faf4e 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScript.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScript.h @@ -3,18 +3,17 @@ #pragma once #include "CoreMinimal.h" +#include "HAL/ThreadSafeBool.h" #include "UObject/ObjectMacros.h" #include "UObject/Object.h" #include "NiagaraCommon.h" +#include "NiagaraScriptBase.h" #include "NiagaraShared.h" #include "NiagaraShader.h" #include "NiagaraParameters.h" #include "NiagaraDataSet.h" -#include "NiagaraShared.h" #include "NiagaraScriptExecutionParameterStore.h" #include "NiagaraScriptHighlight.h" -#include "NiagaraCustomVersion.h" -#include "NiagaraMessageDataBase.h" #include "NiagaraScript.generated.h" @@ -60,6 +59,21 @@ enum class ENiagaraModuleDependencyScriptConstraint : uint8 AllScripts }; +UENUM() +enum class ENiagaraScriptLibraryVisibility : uint8 +{ + Invalid = 0 UMETA(Hidden), + + /** The script is not visible by default to the user, but can be made visible by disabling the "Library only" filter option. */ + Unexposed UMETA(DisplayName = "Unexposed"), + + /** The script is exposed to the asset library and always visible to the user. */ + Library UMETA(DisplayName = "Exposed"), + + /** The script is never visible to the user. This is useful to "soft deprecate" assets that should not be shown to a user, but should also not generate errors for existing usages. */ + Hidden UMETA(DisplayName = "Hidden") +}; + USTRUCT() struct FNiagaraModuleDependency { @@ -73,7 +87,7 @@ public: UPROPERTY(AssetRegistrySearchable, EditAnywhere, Category = Script) ENiagaraModuleDependencyType Type; // e.g. PreDependency - /** Specifies constraints related to the source script a modules provising a depency. */ + /** Specifies constraints related to the source script a modules provides as dependency. */ UPROPERTY(AssetRegistrySearchable, EditAnywhere, Category = Script) ENiagaraModuleDependencyScriptConstraint ScriptConstraint; @@ -110,38 +124,6 @@ struct FNiagaraScriptDebuggerInfo TAtomic bWritten; }; -USTRUCT() -struct NIAGARA_API FSimulationStageMetaData -{ - GENERATED_USTRUCT_BODY() -public: - - /** The Data Interface that we iterate over for this stage. If None, then use particles.*/ - UPROPERTY() - FName IterationSource; - - /** Is this stage a spawn-only stage? */ - UPROPERTY() - uint32 bSpawnOnly : 1; - - /** Do we write to particles this stage?*/ - UPROPERTY() - uint32 bWritesParticles : 1; - - /** DataInterfaces that we write to in this stage.*/ - UPROPERTY() - TArray OutputDestinations; - - /** Index of the simulation stage where we begin iterating. This is meant to encompass iteration count without having an entry for each iteration.*/ - UPROPERTY() - int32 MinStage; - - /** Index of the simulation stage where we end iterating. This is meant to encompass iteration count without having an entry for each iteration.*/ - UPROPERTY() - int32 MaxStage; -}; - - /** Struct containing all of the data necessary to look up a NiagaraScript's VM executable results from the Derived Data Cache.*/ USTRUCT() struct NIAGARA_API FNiagaraVMExecutableDataId @@ -170,6 +152,10 @@ public: UPROPERTY() uint32 bUsesRapidIterationParams : 1; + /** Should we use shader permutations to reduce the cost of simulation stages or not */ + UPROPERTY() + uint32 bUseShaderPermutations : 1; + /** Do we require interpolated spawning */ UPROPERTY() uint32 bInterpolatedSpawn : 1; @@ -203,6 +189,7 @@ public: : CompilerVersionID() , ScriptUsageType(ENiagaraScriptUsage::Function) , bUsesRapidIterationParams(true) + , bUseShaderPermutations(true) , bInterpolatedSpawn(false) , bRequiresPersistentIDs(false) , BaseScriptID_DEPRECATED(0, 0, 0, 0) @@ -383,7 +370,7 @@ public: /** Runtime script for a Niagara system */ UCLASS(MinimalAPI) -class UNiagaraScript : public UObject +class UNiagaraScript : public UNiagaraScriptBase { GENERATED_UCLASS_BODY() public: @@ -447,9 +434,13 @@ public: UPROPERTY(EditAnywhere, Category = Script, meta = (EditCondition = "bExperimental", MultiLine = true)) FText ExperimentalMessage; - /* If this script is exposed to the library. */ + /* Deprecated, use LibraryVisibility instead. */ + UPROPERTY(AssetRegistrySearchable, meta = (DeprecatedProperty)) + uint32 bExposeToLibrary_DEPRECATED : 1; + + /* Defines if this script is visible to the user when searching for modules to add to an emitter. */ UPROPERTY(AssetRegistrySearchable, EditAnywhere, Category = Script) - uint32 bExposeToLibrary : 1; + ENiagaraScriptLibraryVisibility LibraryVisibility; #endif /** Contains all of the top-level values that are iterated on in the UI. These are usually "Module" variables in the graph. They don't necessarily have to be in the order that they are expected in the uniform table.*/ @@ -468,6 +459,11 @@ public: UPROPERTY(AssetRegistrySearchable, EditAnywhere, Category = Script) FText Keywords; + /** The format for the text to display in the stack if the value is collapsed. + * This supports formatting placeholders for the function inputs, for example "myfunc({0}, {1})" will be converted to "myfunc(1.23, Particles.Position)". */ + UPROPERTY(EditAnywhere, Category = Script, meta = (EditCondition = "Usage == ENiagaraScriptUsage::DynamicInput")) + FText CollapsedViewFormat; + UPROPERTY(EditAnywhere, Category = Script) TArray Highlights; @@ -548,6 +544,7 @@ public: NIAGARA_API TArray GetUnsupportedParameterScopes() const; NIAGARA_API TArray GetSupportedUsageContexts() const; static NIAGARA_API TArray GetSupportedUsageContextsForBitmask(int32 InModuleUsageBitmask); + static NIAGARA_API bool IsSupportedUsageContextForBitmask(int32 InModuleUsageBitmask, ENiagaraScriptUsage InUsageContext); #endif NIAGARA_API bool CanBeRunOnGpu() const; @@ -576,8 +573,15 @@ public: virtual void GetAssetRegistryTags(TArray& OutTags) const override; virtual bool IsEditorOnly() const override; + + virtual NIAGARA_API void BeginDestroy() override; + virtual NIAGARA_API bool IsReadyForFinishDestroy() override; //~ End UObject interface + //~ Begin UNiagaraScriptBase interface + virtual TConstArrayView GetSimulationStageMetaData() const override { return MakeArrayView(CachedScriptVM.SimulationStageMetaData); } + //~ End UNiagaraScriptBase interface + // Infrastructure for GPU compute Shaders #if WITH_EDITOR NIAGARA_API void CacheResourceShadersForCooking(EShaderPlatform ShaderPlatform, TArray& InOutCachedResources, const ITargetPlatform* TargetPlatform = nullptr); @@ -597,26 +601,6 @@ public: { return ScriptResource.Get(); } -#if WITH_EDITORONLY_DATA - - FComputeShaderRHIRef GetScriptShader() - { - if (!ScriptShader && ScriptResource.IsValid()) - { - ScriptShader = ScriptResource->GetShader().GetComputeShader(); // NIAGARATODO: need to put this caching somewhere else, as it wont' know when we update the resource - } - return ScriptShader; - } - - FComputeShaderRHIRef GetScriptShaderGameThread() - { - if (!ScriptShader && ScriptResource.IsValid()) - { - ScriptShader = ScriptResource->GetShaderGameThread().GetComputeShader(); // NIAGARATODO: need to put this caching somewhere else, as it wont' know when we update the resource - } - return ScriptShader; - } -#endif NIAGARA_API void GenerateStatIDs(); @@ -683,9 +667,9 @@ public: void RaiseOnGPUCompilationComplete(); - NIAGARA_API FNiagaraVMExecutableData& GetVMExecutableData() { return CachedScriptVM; } - NIAGARA_API const FNiagaraVMExecutableData& GetVMExecutableData() const { return CachedScriptVM; } - NIAGARA_API const FNiagaraVMExecutableDataId& GetVMExecutableDataCompilationId() const { return CachedScriptVMId; } + NIAGARA_API FORCEINLINE FNiagaraVMExecutableData& GetVMExecutableData() { return CachedScriptVM; } + NIAGARA_API FORCEINLINE const FNiagaraVMExecutableData& GetVMExecutableData() const { return CachedScriptVM; } + NIAGARA_API FORCEINLINE const FNiagaraVMExecutableDataId& GetVMExecutableDataCompilationId() const { return CachedScriptVMId; } TArray& GetCachedParameterCollectionReferences() { return CachedParameterCollectionReferences; } TArray& GetCachedDefaultDataInterfaces() { return CachedDefaultDataInterfaces; } @@ -800,4 +784,6 @@ private: static UNiagaraDataInterface* CopyDataInterface(UNiagaraDataInterface* Src, UObject* Owner); + /** Flag used to guarantee that the RT isn't accessing the FNiagaraScriptResource before cleanup. */ + FThreadSafeBool ReleasedByRT; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScriptExecutionContext.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScriptExecutionContext.h index 8a8353494f4c..79f11f48f41d 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScriptExecutionContext.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScriptExecutionContext.h @@ -21,6 +21,15 @@ struct FNiagaraDataInterfaceProxy; class FNiagaraGPUInstanceCountManager; class NiagaraEmitterInstanceBatcher; +/** All scripts that will use the system script execution context. */ +enum class ENiagaraSystemSimulationScript : uint8 +{ + Spawn, + Update, + Num, + //TODO: Maybe add emitter spawn and update here if we split those scripts out. +}; + /** Container for data needed to process event data. */ struct FNiagaraEventHandlingInfo { @@ -145,20 +154,17 @@ struct FScriptExecutionConstantBufferTable } }; -struct FNiagaraScriptExecutionContext +struct FNiagaraScriptExecutionContextBase { UNiagaraScript* Script; /** Table of external function delegate handles called from the VM. */ TArray FunctionTable; -private: - /** Table of external function delegates unique to the instance */ - TArray LocalFunctionTable; - -public: - /** Table of instance data for data interfaces that require it. */ - TArray DataInterfaceInstDataTable; + /** + Table of user ptrs to pass to the VM. + */ + TArray UserPtrTable; /** Parameter store. Contains all data interfaces and a parameter buffer that can be used directly by the VM or GPU. */ FNiagaraScriptInstanceParameterStore Parameters; @@ -170,14 +176,12 @@ public: static uint32 TickCounter; int32 HasInterpolationParameters : 1; - - FNiagaraScriptExecutionContext(); - ~FNiagaraScriptExecutionContext(); - - bool Init(UNiagaraScript* InScript, ENiagaraSimTarget InTarget); - bool Tick(class FNiagaraSystemInstance* Instance, ENiagaraSimTarget SimTarget); - void PostTick(); + FNiagaraScriptExecutionContextBase(); + virtual ~FNiagaraScriptExecutionContextBase(); + + virtual bool Init(UNiagaraScript* InScript, ENiagaraSimTarget InTarget); + virtual bool Tick(class FNiagaraSystemInstance* Instance, ENiagaraSimTarget SimTarget) = 0; void BindData(int32 Index, FNiagaraDataSet& DataSet, int32 StartInstance, bool bUpdateInstanceCounts); void BindData(int32 Index, FNiagaraDataBuffer* Input, int32 StartInstance, bool bUpdateInstanceCounts); @@ -185,11 +189,75 @@ public: const TArray& GetDataInterfaces()const { return Parameters.GetDataInterfaces(); } - void DirtyDataInterfaces(); - bool CanExecute()const; TArrayView GetScriptLiterals() const; + + void DirtyDataInterfaces(); + void PostTick(); + + //Unused. These are only useful in the new SystemScript context. + virtual void BindSystemInstances(TArray& InSystemInstances) {} + virtual bool GeneratePerInstanceDIFunctionTable(FNiagaraSystemInstance* Inst, TArray& OutFunctions) {return true;} +}; + +struct FNiagaraScriptExecutionContext : public FNiagaraScriptExecutionContextBase +{ +protected: + /** + Table of external function delegates unique to the instance. + */ + TArray LocalFunctionTable; + +public: + virtual bool Tick(class FNiagaraSystemInstance* Instance, ENiagaraSimTarget SimTarget)override; +}; + +/** +For function calls from system scripts on User DIs or those with per instance data, we build a per instance binding table that is called from a helper function in the exec context. +TODO: We can embed the instance data in the lambda capture for reduced complexity here. No need for the user ptr table. +We have to rebind if the instance data is recreated anyway. +*/ +struct FNiagaraPerInstanceDIFuncInfo +{ + FVMExternalFunction Function; + void* InstData; +}; + +/** Specialized exec context for system scripts. Allows us to better handle the added complication of Data Interfaces across different system instances. */ +struct FNiagaraSystemScriptExecutionContext : public FNiagaraScriptExecutionContextBase +{ +protected: + + struct FExternalFuncInfo + { + FVMExternalFunction Function; + }; + + TArray ExtFunctionInfo; + + /** + Array of system instances the context is currently operating on. + We need this to allow us to call into per instance DI functions. + */ + TArray* SystemInstances; + + /** The script type this context is for. Allows us to access the correct per instance function table on the system instance. */ + ENiagaraSystemSimulationScript ScriptType; + + /** Helper function that handles calling into per instance DI calls and massages the VM context appropriately. */ + void PerInstanceFunctionHook(FVectorVMContext& Context, int32 PerInstFunctionIndex, int32 UserPtrIndex); + +public: + FNiagaraSystemScriptExecutionContext(ENiagaraSystemSimulationScript InScriptType) : SystemInstances(nullptr), ScriptType(InScriptType){} + + virtual bool Init(UNiagaraScript* InScript, ENiagaraSimTarget InTarget)override; + virtual bool Tick(class FNiagaraSystemInstance* Instance, ENiagaraSimTarget SimTarget); + + void BindSystemInstances(TArray& InSystemInstances) { SystemInstances = &InSystemInstances; } + + /** Generates a table of DI calls unique to the passed system instance. These are then accesss inside the PerInstanceFunctionHook. */ + virtual bool GeneratePerInstanceDIFunctionTable(FNiagaraSystemInstance* Inst, TArray& OutFunctions); }; struct FNiagaraGpuSpawnInfoParams @@ -225,16 +293,15 @@ struct FNiagaraComputeExecutionContext void Reset(NiagaraEmitterInstanceBatcher* Batcher); void InitParams(UNiagaraScript* InGPUComputeScript, ENiagaraSimTarget InSimTarget, const uint32 InDefaultSimulationStageIndex, int32 InMaxUpdateIterations, const TSet InSpawnStages); + void BakeVariableNamesForIterationLookup(); void DirtyDataInterfaces(); bool Tick(FNiagaraSystemInstance* ParentSystemInstance); void PostTick(); void SetDataToRender(FNiagaraDataBuffer* InDataToRender); - FNiagaraDataBuffer* GetDataToRender() const { return DataToRender; } - - void SetTranslucentDataToRender(FNiagaraDataBuffer* InDataToRender); - FNiagaraDataBuffer* GetTranslucentDataToRender() const { return TranslucentDataToRender; } + void SetTranslucentDataToRender(FNiagaraDataBuffer* InTranslucentDataToRender); + FNiagaraDataBuffer* GetDataToRender(bool bIsLowLatencyTranslucent) const { return bIsLowLatencyTranslucent && TranslucentDataToRender ? TranslucentDataToRender : DataToRender; } struct { @@ -281,11 +348,10 @@ public: TArray DataInterfaceProxies; // Most current buffer that can be used for rendering. - FNiagaraDataBuffer* DataToRender; + FNiagaraDataBuffer* DataToRender = nullptr; - // Buffer that will be optional set for translucent rendering. - // This is used to remove a frame of latency for effects that require depth buffer / view / distance fields - FNiagaraDataBuffer* TranslucentDataToRender; + // Optional buffer which can be used to render translucent data with no latency (i.e. this frames data) + FNiagaraDataBuffer* TranslucentDataToRender = nullptr; // Game thread spawn info will be sent to the render thread inside FNiagaraComputeInstanceData FNiagaraGpuSpawnInfo GpuSpawnInfo_GT; @@ -341,11 +407,14 @@ struct FNiagaraDataInterfaceInstanceData struct FNiagaraSimStageData { - FNiagaraDataBuffer* Source; - FNiagaraDataBuffer* Destination; - FNiagaraDataInterfaceProxy* AlternateIterationSource; - uint32 SourceCountOffset; - uint32 DestinationCountOffset; + FNiagaraDataBuffer* Source = nullptr; + FNiagaraDataBuffer* Destination = nullptr; + FNiagaraDataInterfaceProxy* AlternateIterationSource = nullptr; + uint32 SourceCountOffset = 0; + uint32 DestinationCountOffset = 0; + uint32 SourceNumInstances = 0; + uint32 DestinationNumInstances = 0; + const FSimulationStageMetaData* StageMetaData = nullptr; }; struct FNiagaraComputeInstanceData diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h index 7a99dae22e1a..0a9c7bc0fd9f 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h @@ -193,7 +193,6 @@ public: virtual void PostLoad() override; virtual void BeginDestroy() override; virtual void PreSave(const class ITargetPlatform* TargetPlatform) override; - virtual bool NeedsLoadForTargetPlatform(const ITargetPlatform* TargetPlatform) const override; #if WITH_EDITOR virtual void PreEditChange(FProperty* PropertyThatWillChange)override; virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; @@ -362,6 +361,10 @@ public: UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Performance") uint32 bBakeOutRapidIterationOnCook : 1; + /** If true we generate shader permutations per stage to optimize the GPU. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Performance") + uint32 bUseShaderPermutations : 1; + /** Toggles whether or not emitters within this system will try and compress their particle attributes. In some cases, this precision change can lead to perceivable differences, but memory costs and or performance (especially true for GPU emitters) can improve. */ UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Performance") @@ -451,6 +454,9 @@ public: void AddToInstanceCountStat(int32 NumInstances, bool bSolo)const; const FString& GetCrashReporterTag()const; + bool CanObtainEmitterAttribute(const FNiagaraVariableBase& InVarWithUniqueNameNamespace) const; + bool CanObtainSystemAttribute(const FNiagaraVariableBase& InVar) const; + bool CanObtainUserVariable(const FNiagaraVariableBase& InVar) const; #if WITH_EDITORONLY_DATA const TMap& GetMessages() const { return MessageKeyToMessageMap; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_TriangleSampling.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_TriangleSampling.cpp index a1d5733ba825..f5a933e6407f 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_TriangleSampling.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_TriangleSampling.cpp @@ -367,7 +367,8 @@ FORCEINLINE int32 UNiagaraDataInterfaceSkeletalMesh::RandomTriIndexMesh->GetSamplingInfo(); + check(Accessor.Mesh); + const FSkeletalMeshSamplingInfo& SamplingInfo = Accessor.Mesh->GetSamplingInfo(); const FSkeletalMeshSamplingLODBuiltData& WholeMeshBuiltData = SamplingInfo.GetWholeMeshLODBuiltData(InstData->GetLODIndex()); if ( WholeMeshBuiltData.AreaWeightedTriangleSampler.GetNumEntries() > 0 ) { @@ -409,8 +410,9 @@ FORCEINLINE int32 UNiagaraDataInterfaceSkeletalMesh::RandomTriIndexSamplingRegionIndices.Num() > 0) { + check(Accessor.Mesh); const int32 RegionIdx = RandHelper.RandRange(InstanceIndex, 0, InstData->SamplingRegionIndices.Num() - 1); - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = Accessor.Mesh->GetSamplingInfo(); const FSkeletalMeshSamplingRegion& Region = SamplingInfo.GetRegion(InstData->SamplingRegionIndices[RegionIdx]); const FSkeletalMeshSamplingRegionBuiltData& RegionBuiltData = SamplingInfo.GetRegionBuiltData(InstData->SamplingRegionIndices[RegionIdx]); int32 Idx = RandHelper.RandRange(InstanceIndex, 0, RegionBuiltData.TriangleIndices.Num() - 1); @@ -429,8 +431,9 @@ FORCEINLINE int32 UNiagaraDataInterfaceSkeletalMesh::RandomTriIndexSamplingRegionAreaWeightedSampler.GetNumEntries() > 0 ) { + check(Accessor.Mesh); const int32 RegionIdx = InstData->SamplingRegionAreaWeightedSampler.GetEntryIndex(RandHelper.Rand(InstanceIndex), RandHelper.Rand(InstanceIndex)); - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = Accessor.Mesh->GetSamplingInfo(); const FSkeletalMeshSamplingRegion& Region = SamplingInfo.GetRegion(InstData->SamplingRegionIndices[RegionIdx]); const FSkeletalMeshSamplingRegionBuiltData& RegionBuiltData = SamplingInfo.GetRegionBuiltData(InstData->SamplingRegionIndices[RegionIdx]); if ( RegionBuiltData.AreaWeightedSampler.GetNumEntries() > 0 ) @@ -538,12 +541,14 @@ void UNiagaraDataInterfaceSkeletalMesh::RandomTriangle(FVectorVMContext& Context } //-TODO: AREA WEIGHTED + USkeletalMesh* SkelMesh = MeshAccessor.Mesh; + check(SkelMesh); // IsSkinAccessible should have ensured this const int32 LODIndex = InstData->GetLODIndex(); - const bool bAreaWeighted = InstData->Mesh->GetLODInfo(LODIndex)->bSupportUniformlyDistributedSampling; + const bool bAreaWeighted = SkelMesh->GetLODInfo(LODIndex)->bSupportUniformlyDistributedSampling; if (bAreaWeighted) { - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = SkelMesh->GetSamplingInfo(); const FSkeletalMeshSamplingLODBuiltData& WholeMeshBuiltData = SamplingInfo.GetWholeMeshLODBuiltData(InstData->GetLODIndex()); if (WholeMeshBuiltData.AreaWeightedTriangleSampler.GetNumEntries() > 0) { @@ -617,7 +622,8 @@ template<> FORCEINLINE int32 UNiagaraDataInterfaceSkeletalMesh::GetFilteredTriangleCount (FSkeletalMeshAccessorHelper& Accessor, FNDISkeletalMesh_InstanceData* InstData) { - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + check(Accessor.Mesh); + const FSkeletalMeshSamplingInfo& SamplingInfo = Accessor.Mesh->GetSamplingInfo(); const FSkeletalMeshSamplingLODBuiltData& WholeMeshBuiltData = SamplingInfo.GetWholeMeshLODBuiltData(InstData->GetLODIndex()); return WholeMeshBuiltData.AreaWeightedTriangleSampler.GetNumEntries(); } @@ -640,11 +646,13 @@ template<> FORCEINLINE int32 UNiagaraDataInterfaceSkeletalMesh::GetFilteredTriangleCount (FSkeletalMeshAccessorHelper& Accessor, FNDISkeletalMesh_InstanceData* InstData) { + USkeletalMesh* SkelMesh = Accessor.Mesh; + check(SkelMesh); int32 NumTris = 0; for (int32 RegionIdx = 0; RegionIdx < InstData->SamplingRegionIndices.Num(); RegionIdx++) { - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = SkelMesh->GetSamplingInfo(); const FSkeletalMeshSamplingRegion& Region = SamplingInfo.GetRegion(InstData->SamplingRegionIndices[RegionIdx]); const FSkeletalMeshSamplingRegionBuiltData& RegionBuiltData = SamplingInfo.GetRegionBuiltData(InstData->SamplingRegionIndices[RegionIdx]); NumTris += RegionBuiltData.TriangleIndices.Num(); @@ -656,11 +664,13 @@ template<> FORCEINLINE int32 UNiagaraDataInterfaceSkeletalMesh::GetFilteredTriangleCount (FSkeletalMeshAccessorHelper& Accessor, FNDISkeletalMesh_InstanceData* InstData) { + USkeletalMesh* SkelMesh = Accessor.Mesh; + check(SkelMesh); int32 NumTris = 0; for (int32 RegionIdx = 0; RegionIdx < InstData->SamplingRegionIndices.Num(); RegionIdx++) { - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = SkelMesh->GetSamplingInfo(); const FSkeletalMeshSamplingRegion& Region = SamplingInfo.GetRegion(InstData->SamplingRegionIndices[RegionIdx]); const FSkeletalMeshSamplingRegionBuiltData& RegionBuiltData = SamplingInfo.GetRegionBuiltData(InstData->SamplingRegionIndices[RegionIdx]); NumTris += RegionBuiltData.TriangleIndices.Num(); @@ -744,9 +754,12 @@ template<> FORCEINLINE int32 UNiagaraDataInterfaceSkeletalMesh::GetFilteredTriangleAt (FSkeletalMeshAccessorHelper& Accessor, FNDISkeletalMesh_InstanceData* InstData, int32 FilteredIndex) { + USkeletalMesh* SkelMesh = Accessor.Mesh; + check(SkelMesh); + for (int32 RegionIdx = 0; RegionIdx < InstData->SamplingRegionIndices.Num(); RegionIdx++) { - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = SkelMesh->GetSamplingInfo(); const FSkeletalMeshSamplingRegion& Region = SamplingInfo.GetRegion(InstData->SamplingRegionIndices[RegionIdx]); const FSkeletalMeshSamplingRegionBuiltData& RegionBuiltData = SamplingInfo.GetRegionBuiltData(InstData->SamplingRegionIndices[RegionIdx]); if (FilteredIndex < RegionBuiltData.TriangleIndices.Num()) @@ -763,9 +776,12 @@ template<> FORCEINLINE int32 UNiagaraDataInterfaceSkeletalMesh::GetFilteredTriangleAt (FSkeletalMeshAccessorHelper& Accessor, FNDISkeletalMesh_InstanceData* InstData, int32 FilteredIndex) { + USkeletalMesh* SkelMesh = Accessor.Mesh; + check(SkelMesh); + for (int32 RegionIdx = 0; RegionIdx < InstData->SamplingRegionIndices.Num(); RegionIdx++) { - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = SkelMesh->GetSamplingInfo(); const FSkeletalMeshSamplingRegion& Region = SamplingInfo.GetRegion(InstData->SamplingRegionIndices[RegionIdx]); const FSkeletalMeshSamplingRegionBuiltData& RegionBuiltData = SamplingInfo.GetRegionBuiltData(InstData->SamplingRegionIndices[RegionIdx]); if (FilteredIndex < RegionBuiltData.TriangleIndices.Num()) @@ -827,7 +843,7 @@ void UNiagaraDataInterfaceSkeletalMesh::GetTriCoordColor(FVectorVMContext& Conte FNDIOutputParam OutColor(Context); - USkeletalMeshComponent* Comp = Cast(InstData->Component.Get()); + USkeletalMeshComponent* Comp = Cast(InstData->SceneComponent.Get()); const FSkeletalMeshLODRenderData* LODData = InstData->CachedLODData; check(LODData); const FColorVertexBuffer& Colors = LODData->StaticVertexBuffers.ColorVertexBuffer; @@ -873,12 +889,12 @@ void UNiagaraDataInterfaceSkeletalMesh::GetTriCoordUV(FVectorVMContext& Context) FNDIInputParam BaryParam(Context); FNDIInputParam UVSetParam(Context); - checkfSlow(InstData.Get(), TEXT("Skeletal Mesh Interface has invalid instance data. %s"), *GetPathName()); - checkfSlow(InstData->Mesh, TEXT("Skeletal Mesh Interface has invalid mesh. %s"), *GetPathName()); + checkf(InstData.Get(), TEXT("Skeletal Mesh Interface has invalid instance data. %s"), *GetPathName()); + checkf(InstData->bMeshValid, TEXT("Skeletal Mesh Interface has invalid mesh. %s"), *GetPathName()); FNDIOutputParam OutUV(Context); - USkeletalMeshComponent* Comp = Cast(InstData->Component.Get()); + USkeletalMeshComponent* Comp = Cast(InstData->SceneComponent.Get()); const FSkeletalMeshLODRenderData* LODData = InstData->CachedLODData; check(LODData); @@ -967,8 +983,8 @@ void UNiagaraDataInterfaceSkeletalMesh::GetTriCoordSkinnedData(FVectorVMContext& InterpParam.Init(Context); } - checkfSlow(InstData.Get(), TEXT("Skeletal Mesh Interface has invalid instance data. %s"), *GetPathName()); - checkfSlow(InstData->Mesh, TEXT("Skeletal Mesh Interface has invalid mesh. %s"), *GetPathName()); + checkf(InstData.Get(), TEXT("Skeletal Mesh Interface has invalid instance data. %s"), *GetPathName()); + checkf(InstData->bMeshValid, TEXT("Skeletal Mesh Interface has invalid mesh. %s"), *GetPathName()); //TODO: Replace this by storing off FTransforms and doing a proper lerp to get a final transform. //Also need to pull in a per particle interpolation factor. @@ -1243,8 +1259,8 @@ void UNiagaraDataInterfaceSkeletalMesh::GetTriCoordVertices(FVectorVMContext& Co SkinningHandlerType SkinningHandler; FNDIInputParam TriParam(Context); - checkfSlow(InstData.Get(), TEXT("Skeletal Mesh Interface has invalid instance data. %s"), *GetPathName()); - checkfSlow(InstData->Mesh, TEXT("Skeletal Mesh Interface has invalid mesh. %s"), *GetPathName()); + checkf(InstData.Get(), TEXT("Skeletal Mesh Interface has invalid instance data. %s"), *GetPathName()); + checkf(InstData->bMeshValid, TEXT("Skeletal Mesh Interface has invalid mesh. %s"), *GetPathName()); FNDIOutputParam OutV0(Context); FNDIOutputParam OutV1(Context); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_VertexSampling.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_VertexSampling.cpp index 5133fcfee5c9..07ccb1b554ea 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_VertexSampling.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_VertexSampling.cpp @@ -334,8 +334,11 @@ template<> FORCEINLINE int32 UNiagaraDataInterfaceSkeletalMesh::RandomFilteredVertIndex> (FNDIRandomHelper& RandHelper, int32 Instance, FSkeletalMeshAccessorHelper& Accessor, FNDISkeletalMesh_InstanceData* InstData) { + USkeletalMesh* SkelMesh = Accessor.Mesh; + check(SkelMesh); + int32 RegionIdx = RandHelper.RandRange(Instance, 0, InstData->SamplingRegionIndices.Num() - 1); - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = SkelMesh->GetSamplingInfo(); const FSkeletalMeshSamplingRegion& Region = SamplingInfo.GetRegion(InstData->SamplingRegionIndices[RegionIdx]); const FSkeletalMeshSamplingRegionBuiltData& RegionBuiltData = SamplingInfo.GetRegionBuiltData(InstData->SamplingRegionIndices[RegionIdx]); int32 Idx = RandHelper.RandRange(Instance, 0, RegionBuiltData.Vertices.Num() - 1); @@ -422,10 +425,13 @@ template<> FORCEINLINE_DEBUGGABLE int32 UNiagaraDataInterfaceSkeletalMesh::GetFilteredVertexCount (FSkeletalMeshAccessorHelper& Accessor, FNDISkeletalMesh_InstanceData* InstData) { + USkeletalMesh* SkelMesh = Accessor.Mesh; + check(SkelMesh); + int32 NumVerts = 0; for (int32 RegionIdx = 0; RegionIdx < InstData->SamplingRegionIndices.Num(); RegionIdx++) { - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = SkelMesh->GetSamplingInfo(); const FSkeletalMeshSamplingRegion& Region = SamplingInfo.GetRegion(InstData->SamplingRegionIndices[RegionIdx]); const FSkeletalMeshSamplingRegionBuiltData& RegionBuiltData = SamplingInfo.GetRegionBuiltData(InstData->SamplingRegionIndices[RegionIdx]); NumVerts += RegionBuiltData.Vertices.Num(); @@ -481,9 +487,12 @@ template<> FORCEINLINE_DEBUGGABLE int32 UNiagaraDataInterfaceSkeletalMesh::GetFilteredVertexAt (FSkeletalMeshAccessorHelper& Accessor, FNDISkeletalMesh_InstanceData* InstData, int32 FilteredIndex) { + USkeletalMesh* SkelMesh = Accessor.Mesh; + check(SkelMesh); + for (int32 RegionIdx = 0; RegionIdx < InstData->SamplingRegionIndices.Num(); RegionIdx++) { - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = SkelMesh->GetSamplingInfo(); const FSkeletalMeshSamplingRegion& Region = SamplingInfo.GetRegion(InstData->SamplingRegionIndices[RegionIdx]); const FSkeletalMeshSamplingRegionBuiltData& RegionBuiltData = SamplingInfo.GetRegionBuiltData(InstData->SamplingRegionIndices[RegionIdx]); if (FilteredIndex < RegionBuiltData.Vertices.Num()) @@ -538,7 +547,7 @@ void UNiagaraDataInterfaceSkeletalMesh::GetVertexColor(FVectorVMContext& Context FNDIOutputParam OutColor(Context); - USkeletalMeshComponent* Comp = Cast(InstData->Component.Get()); + USkeletalMeshComponent* Comp = Cast(InstData->SceneComponent.Get()); const FSkeletalMeshLODRenderData* LODData = InstData->CachedLODData; check(LODData); const FColorVertexBuffer& Colors = LODData->StaticVertexBuffers.ColorVertexBuffer; @@ -578,12 +587,12 @@ void UNiagaraDataInterfaceSkeletalMesh::GetVertexUV(FVectorVMContext& Context) FNDIInputParam VertParam(Context); FNDIInputParam UVSetParam(Context); - checkfSlow(InstData.Get(), TEXT("Skeletal Mesh Interface has invalid instance data. %s"), *GetPathName()); - checkfSlow(InstData->Mesh, TEXT("Skeletal Mesh Interface has invalid mesh. %s"), *GetPathName()); + checkf(InstData.Get(), TEXT("Skeletal Mesh Interface has invalid instance data. %s"), *GetPathName()); + checkf(InstData->bMeshValid, TEXT("Skeletal Mesh Interface has invalid mesh. %s"), *GetPathName()); FNDIOutputParam OutUV(Context); - USkeletalMeshComponent* Comp = Cast(InstData->Component.Get()); + USkeletalMeshComponent* Comp = Cast(InstData->SceneComponent.Get()); const FSkeletalMeshLODRenderData* LODData = InstData->CachedLODData; check(LODData); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraCommon.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraCommon.cpp index 5ae106c8ea79..edd943a9de3a 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraCommon.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraCommon.cpp @@ -1,17 +1,14 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraCommon.h" -#include "NiagaraDataSet.h" #include "NiagaraComponent.h" #include "NiagaraSystemInstance.h" #include "NiagaraParameterCollection.h" -#include "NiagaraComponent.h" +#include "NiagaraConstants.h" +#include "NiagaraCustomVersion.h" #include "NiagaraScriptSourceBase.h" #include "NiagaraStats.h" -#include "UObject/Linker.h" #include "UObject/Class.h" -#include "UObject/Package.h" -#include "Modules/ModuleManager.h" #include "NiagaraWorldManager.h" DECLARE_CYCLE_STAT(TEXT("Niagara - Utilities - PrepareRapidIterationParameters"), STAT_Niagara_Utilities_PrepareRapidIterationParameters, STATGROUP_Niagara); @@ -119,6 +116,7 @@ void FNiagaraSystemUpdateContext::CommitUpdate() if (Comp) { Comp->ReinitializeSystem(); + Comp->EndUpdateContextReset(); } } ComponentsToReInit.Empty(); @@ -128,6 +126,7 @@ void FNiagaraSystemUpdateContext::CommitUpdate() if (Comp) { Comp->ResetSystem(); + Comp->EndUpdateContextReset(); } } ComponentsToReset.Empty(); @@ -140,7 +139,15 @@ void FNiagaraSystemUpdateContext::AddAll(bool bReInit) UNiagaraComponent* Comp = *It; check(Comp); - bool bIsActive = Comp->IsActive() || Comp->IsRegisteredWithScalabilityManager(); + Comp->BeginUpdateContextReset(); + + bool bIsActive = (Comp->IsActive() && Comp->GetRequestedExecutionState() == ENiagaraExecutionState::Active) || Comp->IsRegisteredWithScalabilityManager(); + + if (bReInit) + { + //Always destroy the system sims on a reinit, even if we're not reactivating the component. + SystemSimsToDestroy.AddUnique(Comp->GetAsset()); + } if (bDestroyOnAdd) { @@ -151,6 +158,10 @@ void FNiagaraSystemUpdateContext::AddAll(bool bReInit) { AddInternal(Comp, bReInit); } + else + { + Comp->EndUpdateContextReset(); + } } } @@ -162,7 +173,15 @@ void FNiagaraSystemUpdateContext::Add(const UNiagaraSystem* System, bool bReInit check(Comp); if (Comp->GetAsset() == System) { - bool bIsActive = Comp->IsActive() || Comp->IsRegisteredWithScalabilityManager(); + Comp->BeginUpdateContextReset(); + + bool bIsActive = (Comp->IsActive() && Comp->GetRequestedExecutionState() == ENiagaraExecutionState::Active) || Comp->IsRegisteredWithScalabilityManager(); + + if (bReInit) + { + //Always destroy the system sims on a reinit, even if we're not reactivating the component. + SystemSimsToDestroy.AddUnique(Comp->GetAsset()); + } if (bDestroyOnAdd) { @@ -173,6 +192,10 @@ void FNiagaraSystemUpdateContext::Add(const UNiagaraSystem* System, bool bReInit { AddInternal(Comp, bReInit); } + else + { + Comp->EndUpdateContextReset(); + } } } } @@ -187,7 +210,15 @@ void FNiagaraSystemUpdateContext::Add(const UNiagaraEmitter* Emitter, bool bReIn FNiagaraSystemInstance* SystemInst = Comp->GetSystemInstance(); if (SystemInst && SystemInst->UsesEmitter(Emitter)) { - bool bIsActive = Comp->IsActive() || Comp->IsRegisteredWithScalabilityManager(); + Comp->BeginUpdateContextReset(); + + bool bIsActive = (Comp->IsActive() && Comp->GetRequestedExecutionState() == ENiagaraExecutionState::Active) || Comp->IsRegisteredWithScalabilityManager(); + + if (bReInit) + { + //Always destroy the system sims on a reinit, even if we're not reactivating the component. + SystemSimsToDestroy.AddUnique(Comp->GetAsset()); + } if (bDestroyOnAdd) { @@ -198,6 +229,10 @@ void FNiagaraSystemUpdateContext::Add(const UNiagaraEmitter* Emitter, bool bReIn { AddInternal(Comp, bReInit); } + else + { + Comp->EndUpdateContextReset(); + } } } } @@ -211,7 +246,15 @@ void FNiagaraSystemUpdateContext::Add(const UNiagaraScript* Script, bool bReInit UNiagaraSystem* System = Comp->GetAsset(); if (System && System->UsesScript(Script)) { - bool bIsActive = Comp->IsActive() || Comp->IsRegisteredWithScalabilityManager(); + Comp->BeginUpdateContextReset(); + + bool bIsActive = (Comp->IsActive() && Comp->GetRequestedExecutionState() == ENiagaraExecutionState::Active) || Comp->IsRegisteredWithScalabilityManager(); + + if (bReInit) + { + //Always destroy the system sims on a reinit, even if we're not reactivating the component. + SystemSimsToDestroy.AddUnique(Comp->GetAsset()); + } if (bDestroyOnAdd) { @@ -222,6 +265,10 @@ void FNiagaraSystemUpdateContext::Add(const UNiagaraScript* Script, bool bReInit { AddInternal(Comp, bReInit); } + else + { + Comp->EndUpdateContextReset(); + } } } } @@ -251,7 +298,14 @@ void FNiagaraSystemUpdateContext::Add(const UNiagaraParameterCollection* Collect FNiagaraSystemInstance* SystemInst = Comp->GetSystemInstance(); if (SystemInst && SystemInst->UsesCollection(Collection)) { - bool bIsActive = Comp->IsActive() || Comp->IsRegisteredWithScalabilityManager(); + Comp->BeginUpdateContextReset(); + bool bIsActive = (Comp->IsActive() && Comp->GetRequestedExecutionState() == ENiagaraExecutionState::Active) || Comp->IsRegisteredWithScalabilityManager(); + + if (bReInit) + { + //Always destroy the system sims on a reinit, even if we're not reactivating the component. + SystemSimsToDestroy.AddUnique(Comp->GetAsset()); + } if (bDestroyOnAdd) { @@ -262,6 +316,10 @@ void FNiagaraSystemUpdateContext::Add(const UNiagaraParameterCollection* Collect { AddInternal(Comp, bReInit); } + else + { + Comp->EndUpdateContextReset(); + } } } } @@ -272,7 +330,6 @@ void FNiagaraSystemUpdateContext::AddInternal(UNiagaraComponent* Comp, bool bReI if (bReInit) { ComponentsToReInit.AddUnique(Comp); - SystemSimsToDestroy.AddUnique(Comp->GetAsset()); } else { @@ -280,6 +337,223 @@ void FNiagaraSystemUpdateContext::AddInternal(UNiagaraComponent* Comp, bool bReI } } +////////////////////////////////////////////////////////////////////////// + +void FNiagaraVariableAttributeBinding::SetValue(const FName& InValue, const UNiagaraEmitter* InEmitter, ENiagaraRendererSourceDataMode InSourceMode) +{ + RootVariable.SetName(InValue); + + bool bIsRootParticleValue = RootVariable.IsInNameSpace(FNiagaraConstants::ParticleAttributeNamespace); + bool bIsRootUnaliasedEmitterValue = RootVariable.IsInNameSpace(FNiagaraConstants::EmitterNamespace); + bool bIsAliasedEmitterValue = InEmitter ? RootVariable.IsInNameSpace(InEmitter->GetUniqueEmitterName()) : false; + bool bIsRootSystemValue = RootVariable.IsInNameSpace(FNiagaraConstants::SystemNamespace); + bool bIsRootUserValue = RootVariable.IsInNameSpace(FNiagaraConstants::UserNamespace); + + // We clear out the namespace for the sourcemode so that we can keep the values up-to-date if you change the source mode. + if (bIsRootParticleValue && InSourceMode == ENiagaraRendererSourceDataMode::Particles) + { + RootVariable.SetName(FNiagaraConstants::GetAttributeAsParticleDataSetKey(RootVariable).GetName()); + BindingSourceMode = ENiagaraBindingSource::ImplicitFromSource; + } + else if (bIsRootUnaliasedEmitterValue && InSourceMode == ENiagaraRendererSourceDataMode::Emitter) + { + RootVariable.SetName(FNiagaraConstants::GetAttributeAsEmitterDataSetKey(RootVariable).GetName()); + BindingSourceMode = ENiagaraBindingSource::ImplicitFromSource; + } + else if ((InEmitter && bIsAliasedEmitterValue) && InSourceMode == ENiagaraRendererSourceDataMode::Emitter) + { + // First, replace unaliased emitter namespace with "Emitter" namespace + TMap Aliases; + Aliases.Add(InEmitter->GetUniqueEmitterName(), FNiagaraConstants::EmitterNamespace.ToString()); + RootVariable = FNiagaraVariable::ResolveAliases(RootVariable, Aliases); + + // Now strip out "Emitter" + RootVariable.SetName(FNiagaraConstants::GetAttributeAsEmitterDataSetKey(RootVariable).GetName()); + BindingSourceMode = ENiagaraBindingSource::ImplicitFromSource; + } + else if (bIsRootParticleValue) + { + BindingSourceMode = ENiagaraBindingSource::ExplicitParticles; + } + else if (bIsRootUnaliasedEmitterValue || bIsAliasedEmitterValue) + { + if (bIsRootUnaliasedEmitterValue && InEmitter) + { + TMap Aliases; + Aliases.Add(FNiagaraConstants::EmitterNamespace.ToString(), InEmitter->GetUniqueEmitterName()); + RootVariable = FNiagaraVariable::ResolveAliases(RootVariable, Aliases); + } + BindingSourceMode = ENiagaraBindingSource::ExplicitEmitter; + } + else if (bIsRootSystemValue) + { + BindingSourceMode = ENiagaraBindingSource::ExplicitSystem; + } + else if (bIsRootUserValue) + { + BindingSourceMode = ENiagaraBindingSource::ExplicitUser; + } + + CacheValues(InEmitter, InSourceMode); +} + +void FNiagaraVariableAttributeBinding::Setup(const FNiagaraVariableBase& InRootVar, const FNiagaraVariableBase& InDataSetVar, const FNiagaraVariable& InDefaultValue, ENiagaraRendererSourceDataMode InSourceMode) +{ + RootVariable = InRootVar; + if (InDefaultValue.IsDataAllocated() && InDefaultValue.GetType() == InRootVar.GetType()) + { + RootVariable.SetData(InDefaultValue.GetData()); + } + SetValue(InRootVar.GetName(), nullptr, InSourceMode); +} + +#if WITH_EDITORONLY_DATA +FString FNiagaraVariableAttributeBinding::GetDefaultValueString() const +{ + FString DefaultValueStr = RootVariable.GetName().ToString(); + + if (!RootVariable.GetName().IsValid() || RootVariable.IsDataAllocated() == true) + { + DefaultValueStr = RootVariable.GetType().ToString(RootVariable.GetData()); + DefaultValueStr.TrimEndInline(); + } + return DefaultValueStr; +} + +const FName& FNiagaraVariableAttributeBinding::GetName(ENiagaraRendererSourceDataMode InSourceMode) const +{ + return CachedDisplayName; +} +#endif + +void FNiagaraVariableAttributeBinding::PostLoad(ENiagaraRendererSourceDataMode InSourceMode) +{ +#if WITH_EDITORONLY_DATA + if (BoundVariable.IsValid()) + { + RootVariable.SetType(DataSetVariable.GetType()); //Sometimes the BoundVariable was bogus in the past. THe DataSet shouldn't be though. + SetValue(BoundVariable.GetName(), nullptr, InSourceMode); + BoundVariable = FNiagaraVariable(); + } +#endif + +} + +void FNiagaraVariableAttributeBinding::Dump() const +{ + UE_LOG(LogNiagara, Log, TEXT("PostLoad for FNiagaraVariableAttributeBinding....")); + UE_LOG(LogNiagara, Log, TEXT("ParamMapVariable: %s %s"), *ParamMapVariable.GetName().ToString(), *ParamMapVariable.GetType().GetName()); + UE_LOG(LogNiagara, Log, TEXT("DataSetVariable: %s %s"), *DataSetVariable.GetName().ToString(), *DataSetVariable.GetType().GetName()); + UE_LOG(LogNiagara, Log, TEXT("RootVariable: %s %s"), *RootVariable.GetName().ToString(), *RootVariable.GetType().GetName()); +#if WITH_EDITORONLY_DATA + UE_LOG(LogNiagara, Log, TEXT("BoundVariable: %s %s"), *BoundVariable.GetName().ToString(), *BoundVariable.GetType().GetName()); + UE_LOG(LogNiagara, Log, TEXT("CachedDisplayName: %s"), *CachedDisplayName.ToString()); +#endif + UE_LOG(LogNiagara, Log, TEXT("BindingSourceMode: %d bBindingExistsOnSource: %d bIsCachedParticleValue: %d"), (int32)BindingSourceMode.GetValue(), + bBindingExistsOnSource ? 1 : 0, bIsCachedParticleValue ? 1 : 0 ); +} + +void FNiagaraVariableAttributeBinding::ResetToDefault(const FNiagaraVariableAttributeBinding& InOther, const UNiagaraEmitter* InEmitter, ENiagaraRendererSourceDataMode InSourceMode) +{ + if (InOther.BindingSourceMode == ImplicitFromSource) + { + // The default may have been set with a different source mode, so we can't copy values over directly. Instead, we need to copy the implicit values over. + FNiagaraVariable TempVar = InOther.RootVariable; + if (InSourceMode == ENiagaraRendererSourceDataMode::Emitter && InOther.BindingSourceMode == ENiagaraBindingSource::ImplicitFromSource) + { + TempVar.SetName(*(FNiagaraConstants::EmitterNamespace.ToString() + TEXT(".") + InOther.DataSetVariable.GetName().ToString())); + } + else if (InSourceMode == ENiagaraRendererSourceDataMode::Particles && InOther.BindingSourceMode == ENiagaraBindingSource::ImplicitFromSource) + { + TempVar.SetName(*(FNiagaraConstants::ParticleAttributeNamespace.ToString() + TEXT(".") + InOther.DataSetVariable.GetName().ToString())); + } + + SetValue(TempVar.GetName(), nullptr, InSourceMode); + } + else + { + SetValue(InOther.RootVariable.GetName(), InEmitter, InSourceMode); + } +} + +bool FNiagaraVariableAttributeBinding::MatchesDefault(const FNiagaraVariableAttributeBinding& InOther, ENiagaraRendererSourceDataMode InSourceMode) const +{ + if (DataSetVariable.GetName() != InOther.DataSetVariable.GetName()) + return false; + if (RootVariable.GetName() != InOther.RootVariable.GetName()) + return false; + return true; +} + +void FNiagaraVariableAttributeBinding::CacheValues(const UNiagaraEmitter* InEmitter, ENiagaraRendererSourceDataMode InSourceMode) +{ + DataSetVariable = ParamMapVariable = (const FNiagaraVariableBase&)RootVariable; + bBindingExistsOnSource = false; + + // Decide if this is going to be bound to a particle attribute (needed for use by the renderers, for instance) + if (BindingSourceMode == ENiagaraBindingSource::ExplicitParticles || (InSourceMode == ENiagaraRendererSourceDataMode::Particles && BindingSourceMode == ENiagaraBindingSource::ImplicitFromSource)) + { + bIsCachedParticleValue = true; + } + else + { + bIsCachedParticleValue = false; + } + + // If this is an implicit variable, go ahead and expand the full namespace. RootVariable should be non-namespaced at this point. + if (InSourceMode == ENiagaraRendererSourceDataMode::Emitter && BindingSourceMode == ENiagaraBindingSource::ImplicitFromSource) + { + ParamMapVariable.SetName(*(FNiagaraConstants::EmitterNamespace.ToString() + TEXT(".") + DataSetVariable.GetName().ToString())); + } + else if (InSourceMode == ENiagaraRendererSourceDataMode::Particles && BindingSourceMode == ENiagaraBindingSource::ImplicitFromSource) + { + ParamMapVariable.SetName(*(FNiagaraConstants::ParticleAttributeNamespace.ToString() + TEXT(".") + DataSetVariable.GetName().ToString())); + } + +#if WITH_EDITORONLY_DATA + CachedDisplayName = ParamMapVariable.GetName(); +#endif + + // Now resolve if this variable actually exists. + if (InEmitter) + { + if (BindingSourceMode == ENiagaraBindingSource::ExplicitEmitter || (InSourceMode == ENiagaraRendererSourceDataMode::Emitter && BindingSourceMode == ENiagaraBindingSource::ImplicitFromSource)) + { + // Replace "Emitter" namespace with unaliased emitter namespace + TMap Aliases; + Aliases.Add(FNiagaraConstants::EmitterNamespace.ToString(), InEmitter->GetUniqueEmitterName()); + ParamMapVariable = FNiagaraVariable::ResolveAliases(ParamMapVariable, Aliases); + RootVariable = FNiagaraVariable::ResolveAliases(RootVariable, Aliases); + DataSetVariable = FNiagaraVariable::ResolveAliases(DataSetVariable, Aliases); + } + + if (BindingSourceMode == ENiagaraBindingSource::ExplicitParticles || (InSourceMode == ENiagaraRendererSourceDataMode::Particles && BindingSourceMode == ENiagaraBindingSource::ImplicitFromSource)) + bBindingExistsOnSource = InEmitter->CanObtainParticleAttribute(DataSetVariable); + else if (BindingSourceMode == ENiagaraBindingSource::ExplicitEmitter || (InSourceMode == ENiagaraRendererSourceDataMode::Emitter && BindingSourceMode == ENiagaraBindingSource::ImplicitFromSource)) + bBindingExistsOnSource = InEmitter->CanObtainEmitterAttribute(ParamMapVariable); + else if (BindingSourceMode == ENiagaraBindingSource::ExplicitSystem) + bBindingExistsOnSource = InEmitter->CanObtainSystemAttribute(ParamMapVariable); + else if (BindingSourceMode == ENiagaraBindingSource::ExplicitUser) + bBindingExistsOnSource = InEmitter->CanObtainUserVariable(ParamMapVariable); + } + +} + + +////////////////////////////////////////////////////////////////////////// +const FNiagaraVariableBase& FNiagaraMaterialAttributeBinding::GetParamMapBindableVariable() const +{ + return ResolvedNiagaraVariable; +} + + +void FNiagaraMaterialAttributeBinding::CacheValues(const UNiagaraEmitter* InEmitter) +{ + TMap Aliases; + Aliases.Add(FNiagaraConstants::EmitterNamespace.ToString(), InEmitter->GetUniqueEmitterName()); + ResolvedNiagaraVariable = FNiagaraVariable::ResolveAliases(NiagaraVariable, Aliases); +} + ////////////////////////////////////////////////////////////////////////// @@ -387,17 +661,50 @@ bool FNiagaraScriptDataInterfaceCompileInfo::CanExecuteOnTarget(ENiagaraSimTarge { return Obj->CanExecuteOnTarget(SimTarget); } - check(false); + UE_LOG(LogNiagara, Error, TEXT("Failed to call CanExecuteOnTarget for DataInterface \"%s\". Perhaps missing a plugin for your project?"), *Name.ToString()); return false; } UNiagaraDataInterface* FNiagaraScriptDataInterfaceCompileInfo::GetDefaultDataInterface() const { // Note that this can be called on non-game threads. We ensure that the data interface CDO object is already in existence at application init time, so we don't allow this to be auto-created. - UNiagaraDataInterface* Obj = CastChecked(const_cast(Type.GetClass())->GetDefaultObject(false)); - return Obj; + if (Type.IsDataInterface()) + { + const UClass* TargetClass = const_cast(Type.GetClass()); + if (TargetClass) + { + UNiagaraDataInterface* Obj = Cast(TargetClass->GetDefaultObject(false)); + if (Obj) + return Obj; + + UE_LOG(LogNiagara, Error, TEXT("Failed to create default object for class \"%s\". Perhaps missing a plugin for your project?"), *TargetClass->GetName()); + return nullptr; + } + + } + UE_LOG(LogNiagara, Error, TEXT("Failed to create default object for compiled variable \"%s\". Perhaps missing a plugin for your project?"), *this->Name.ToString()); + return nullptr; } +bool FNiagaraScriptDataInterfaceCompileInfo::NeedsPerInstanceBinding()const +{ + if (Name.ToString().StartsWith(TEXT("User."))) + return true; + UNiagaraDataInterface* Obj = GetDefaultDataInterface(); + if (Obj && Obj->PerInstanceDataSize() > 0) + return true; + return false; +} + +bool FNiagaraScriptDataInterfaceCompileInfo::MatchesClass(const UClass* InClass) const +{ + UNiagaraDataInterface* Obj = GetDefaultDataInterface(); + if (Obj && Obj->GetClass() == InClass) + return true; + return false; +} + + void FNiagaraUtilities::DumpHLSLText(const FString& SourceCode, const FString& DebugName) { UE_LOG(LogNiagara, Display, TEXT("Compile output as text: %s"), *DebugName); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponent.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponent.cpp index b4cabc2b4ba7..247aa462dcf5 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponent.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponent.cpp @@ -7,24 +7,22 @@ #include "NiagaraSystemInstance.h" #include "NiagaraEmitterInstance.h" #include "MeshBatch.h" -#include "SceneUtils.h" -#include "ComponentReregisterContext.h" #include "NiagaraConstants.h" #include "NiagaraStats.h" #include "NiagaraCommon.h" -#include "NiagaraEmitterInstance.h" #include "NiagaraDataInterface.h" -#include "NiagaraDataInterfaceStaticMesh.h" #include "UObject/NameTypes.h" -#include "NiagaraParameterCollection.h" #include "NiagaraWorldManager.h" #include "EngineUtils.h" #include "ProfilingDebugging/CsvProfiler.h" #include "Engine/CollisionProfile.h" #include "PrimitiveSceneInfo.h" +#include "NiagaraCrashReporterHandler.h" #include "NiagaraEmitterInstanceBatcher.h" #include "NiagaraDataSetAccessor.h" #include "NiagaraComponentSettings.h" +#include "NiagaraCustomVersion.h" +#include "Materials/MaterialInstanceDynamic.h" DECLARE_CYCLE_STAT(TEXT("Sceneproxy create (GT)"), STAT_NiagaraCreateSceneProxy, STATGROUP_Niagara); DECLARE_CYCLE_STAT(TEXT("Component Tick (GT)"), STAT_NiagaraComponentTick, STATGROUP_Niagara); @@ -217,7 +215,7 @@ void FNiagaraSceneProxy::CreateRenderers(const UNiagaraComponent* Component) FNiagaraRenderer* NewRenderer = nullptr; if (Properties->GetIsActive() && EmitterInst->GetData().IsInitialized() && !EmitterInst->IsDisabled()) { - NewRenderer = Properties->CreateEmitterRenderer(FeatureLevel, &EmitterInst.Get()); + NewRenderer = Properties->CreateEmitterRenderer(FeatureLevel, &EmitterInst.Get(), Component); bAlwaysHasVelocity |= Properties->bMotionBlurEnabled; } EmitterRenderers.Add(NewRenderer); @@ -456,8 +454,11 @@ UNiagaraComponent::UNiagaraComponent(const FObjectInitializer& ObjectInitializer , bDidAutoAttach(false) , bAllowScalability(true) , bIsCulledByScalability(false) + , bDuringUpdateContextReset(false) //, bIsChangingAutoAttachment(false) , ScalabilityManagerHandle(INDEX_NONE) + , ForceUpdateTransformTime(0.0f) + , CurrLocalBounds(ForceInit) { OverrideParameters.SetOwner(this); @@ -614,7 +615,7 @@ void UNiagaraComponent::TickComponent(float DeltaSeconds, enum ELevelTick TickTy if (AgeUpdateMode == ENiagaraAgeUpdateMode::TickDeltaTime) { - SystemInstance->ComponentTick(DeltaSeconds, ThisTickFunction->IsCompletionHandleValid() ? ThisTickFunction->GetCompletionHandle() : nullptr); + SystemInstance->ManualTick(DeltaSeconds, ThisTickFunction->IsCompletionHandleValid() ? ThisTickFunction->GetCompletionHandle() : nullptr); } else if(AgeUpdateMode == ENiagaraAgeUpdateMode::DesiredAge) { @@ -645,7 +646,7 @@ void UNiagaraComponent::TickComponent(float DeltaSeconds, enum ELevelTick TickTy { //Cannot do multiple tick off the game thread here without additional work. So we pass in null for the completion event which will force GT execution. //If this becomes a perf problem I can add a new path for the tick code to handle multiple ticks. - SystemInstance->ComponentTick(SeekDelta, nullptr); + SystemInstance->ManualTick(SeekDelta, nullptr); CurrentTime = FPlatformTime::Seconds(); } } @@ -668,19 +669,19 @@ void UNiagaraComponent::TickComponent(float DeltaSeconds, enum ELevelTick TickTy // When going back in time for a frame or more, reset and simulate a single frame. We ignore small negative changes to delta // time which can happen when controlling time with the timeline and the time snaps to a previous time when paused. SystemInstance->Reset(FNiagaraSystemInstance::EResetMode::ResetAll); - SystemInstance->ComponentTick(SeekDelta, nullptr); + SystemInstance->ManualTick(SeekDelta, nullptr); } } else if (AgeDiff < MaxForwardFrames * SeekDelta) { // Allow ticks between 0 and MaxForwardFrames, but don't ever send more then 2 x the seek delta. - SystemInstance->ComponentTick(FMath::Min(AgeDiff, 2 * SeekDelta), nullptr); + SystemInstance->ManualTick(FMath::Min(AgeDiff, 2 * SeekDelta), nullptr); } else { // When going forward in time for more than MaxForwardFrames, reset and simulate a single frame. SystemInstance->Reset(FNiagaraSystemInstance::EResetMode::ResetAll); - SystemInstance->ComponentTick(SeekDelta, nullptr); + SystemInstance->ManualTick(SeekDelta, nullptr); } LastHandledDesiredAge = DesiredAge; } @@ -815,12 +816,19 @@ bool UNiagaraComponent::InitializeSystem() { LLM_SCOPE(ELLMTag::Niagara); CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Effects); - FNiagaraSystemInstance::AllocateSystemInstance(this, SystemInstance); + + UWorld* World = GetWorld(); + check(World); + check(Asset); + + const bool bPooled = PoolingMethod != ENCPoolMethod::None; + FNiagaraSystemInstance::AllocateSystemInstance(SystemInstance, *World, *Asset, &OverrideParameters, this, TickBehavior, bPooled); //UE_LOG(LogNiagara, Log, TEXT("Create System: %p | %s\n"), SystemInstance.Get(), *GetAsset()->GetFullName()); #if WITH_EDITORONLY_DATA OnSystemInstanceChangedDelegate.Broadcast(); #endif SystemInstance->Init(bForceSolo); + SystemInstance->SetOnPostTick(FNiagaraSystemInstance::FOnPostTick::CreateUObject(this, &UNiagaraComponent::PostSystemTick_GameThread)); MarkRenderStateDirty(); return true; } @@ -836,6 +844,12 @@ void UNiagaraComponent::ActivateInternal(bool bReset /* = false */, bool bIsScal { bAwaitingActivationDueToNotReady = false; + // Reset our local bounds on reset + if (bReset) + { + CurrLocalBounds.Init(); + } + if (GbSuppressNiagaraSystems != 0) { UnregisterWithScalabilityManager(); @@ -868,7 +882,7 @@ void UNiagaraComponent::ActivateInternal(bool bReset /* = false */, bool bIsScal } // Should we force activation to fail? - if (UNiagaraComponentSettings::ShouldSupressActivation(Asset)) + if (UNiagaraComponentSettings::ShouldSuppressActivation(Asset)) { return; } @@ -966,7 +980,12 @@ void UNiagaraComponent::ActivateInternal(bool bReset /* = false */, bool bIsScal } #if WITH_EDITOR - ApplyOverridesToParameterStore(); + //TODO: Do this else where. I get needing to ensure params are correct from the component but these are stomping over runtime changes to the params in editor builds. + //For now we can bypass the worst of the impact by disallowing in game worlds. + if(!World->IsGameWorld()) + { + ApplyOverridesToParameterStore(); + } #endif FNiagaraSystemInstance::EResetMode ResetMode = FNiagaraSystemInstance::EResetMode::ResetSystem; @@ -1063,7 +1082,7 @@ void UNiagaraComponent::DeactivateImmediateInternal(bool bIsScalabilityCull) SCOPE_CYCLE_COUNTER(STAT_NiagaraComponentDeactivate); Super::Deactivate(); - bool bWasCulledByScalabiltiy = bIsCulledByScalability; + bool bWasCulledByScalability = bIsCulledByScalability; //UE_LOG(LogNiagara, Log, TEXT("DeactivateImmediate: %p - %s - %s"), this, *Asset->GetName(), bIsScalabilityCull ? TEXT("Scalability") : TEXT("")); @@ -1086,7 +1105,7 @@ void UNiagaraComponent::DeactivateImmediateInternal(bool bIsScalabilityCull) { SystemInstance->Deactivate(true); } - else if (bWasCulledByScalabiltiy && !bIsCulledByScalability)//We were culled by scalability but no longer, ensure we've handled completion correctly. E.g. returned to the pool etc. + else if (bWasCulledByScalability && !bIsCulledByScalability)//We were culled by scalability but no longer, ensure we've handled completion correctly. E.g. returned to the pool etc. { OnSystemComplete(); } @@ -1145,6 +1164,43 @@ void UNiagaraComponent::UnregisterWithScalabilityManager() ScalabilityManagerHandle = INDEX_NONE;//Just to be sure our state is unregistered. } +void UNiagaraComponent::PostSystemTick_GameThread() +{ + check(SystemInstance.IsValid()); // sanity + +#if WITH_EDITOR + if (SystemInstance->HandleNeedsUIResync()) + { + OnSynchronizedWithAssetParametersDelegate.Broadcast(); + } +#endif + + // Check if the system got completed + if (IsActive() && SystemInstance->IsComplete()) + { + OnSystemComplete(); + return; + } + + // NOTE: Since this is happening before scene visibility calculation, it's likely going to be off by a frame + SystemInstance->SetLastRenderTime(GetLastRenderTime()); + + MarkRenderDynamicDataDirty(); + + // Check to force update our transform based on a timer or bounds expanding beyond their previous local boundaries + const FBox NewLocalBounds = SystemInstance->GetLocalBounds(); + ForceUpdateTransformTime += GetWorld()->GetDeltaSeconds(); + if (!CurrLocalBounds.IsValid || + !CurrLocalBounds.IsInsideOrOn(NewLocalBounds.Min) || + !CurrLocalBounds.IsInsideOrOn(NewLocalBounds.Max) || + (ForceUpdateTransformTime > MaxTimeBeforeForceUpdateTransform)) + { + CurrLocalBounds = NewLocalBounds; + ForceUpdateTransformTime = 0.0f; + UpdateComponentToWorld(); + } +} + void UNiagaraComponent::OnSystemComplete() { //UE_LOG(LogNiagara, Log, TEXT("OnSystemComplete: %p - %s"), SystemInstance.Get(), *Asset->GetName()); @@ -1158,7 +1214,8 @@ void UNiagaraComponent::OnSystemComplete() //Don't really complete if we're being culled by scalability. //We want to stop ticking but not be reclaimed by the pools etc. - if (bIsCulledByScalability == false) + //We also want to skip this work if we're destroying during and update context reset. + if (bIsCulledByScalability == false && bDuringUpdateContextReset == false) { //UE_LOG(LogNiagara, Log, TEXT("OnSystemFinished.Broadcast(this);: { %p - %p - %s"), this, SystemInstance.Get(), *Asset->GetName()); OnSystemFinished.Broadcast(this); @@ -1249,7 +1306,7 @@ void UNiagaraComponent::OnPooledReuse(UWorld* NewWorld) if (SystemInstance != nullptr) { - SystemInstance->OnPooledReuse(); + SystemInstance->OnPooledReuse(*NewWorld); } } @@ -1439,6 +1496,8 @@ void UNiagaraComponent::SendRenderDynamicData_Concurrent() if (SystemInstance.IsValid() && SceneProxy) { + FNiagaraCrashReporterScope CRScope(SystemInstance.Get()); + #if STATS TStatId SystemStatID = GetAsset() ? GetAsset()->GetStatID(true, true) : TStatId(); FScopeCycleCounter SystemStatCounter(SystemStatID); @@ -1554,9 +1613,9 @@ FBoxSphereBounds UNiagaraComponent::CalcBounds(const FTransform& LocalToWorld) c } FBoxSphereBounds SystemBounds; - if (SystemInstance.IsValid()) + if (CurrLocalBounds.IsValid) { - SystemBounds = SystemInstance->GetLocalBounds(); + SystemBounds = CurrLocalBounds; SystemBounds.BoxExtent *= BoundsScale; SystemBounds.SphereRadius *= BoundsScale; } @@ -1569,11 +1628,76 @@ FBoxSphereBounds UNiagaraComponent::CalcBounds(const FTransform& LocalToWorld) c return SystemBounds.TransformBy(LocalToWorld); } +void UNiagaraComponent::UpdateEmitterMaterials() +{ + TArray NewEmitterMaterials; + + if (SystemInstance) + { + for (int32 i = 0; i < SystemInstance->GetEmitters().Num(); i++) + { + FNiagaraEmitterInstance* EmitterInst = &SystemInstance->GetEmitters()[i].Get(); + if (UNiagaraEmitter* Emitter = EmitterInst->GetCachedEmitter()) + { + + Emitter->ForEachEnabledRenderer( + [&](UNiagaraRendererProperties* Properties) + { + TArray UsedMaterials; + Properties->GetUsedMaterials(EmitterInst, UsedMaterials); + bool bCreateMidsForUsedMaterials = Properties->NeedsMIDsForMaterials(); + + uint32 Index = 0; + for (UMaterialInterface*& Mat : UsedMaterials) + { + if (Mat && bCreateMidsForUsedMaterials && !Mat->IsA()) + { + bool bFoundMatch = false; + for (int32 i = 0; i < EmitterMaterials.Num(); i++) + { + if (EmitterMaterials[i].EmitterRendererProperty == Properties && EmitterMaterials[i].Material ) + { + UMaterialInstanceDynamic* MatDyn = Cast< UMaterialInstanceDynamic>(EmitterMaterials[i].Material); + if (MatDyn && MatDyn->Parent == Mat) + { + bFoundMatch = true; + Mat = MatDyn; + NewEmitterMaterials.Add(EmitterMaterials[i]); + break; + } + } + } + + if (!bFoundMatch) + { + UE_LOG(LogNiagara, Log, TEXT("Create Dynamic Material for component %s"), *GetPathName()); + Mat = UMaterialInstanceDynamic::Create(Mat, this); + FNiagaraMaterialOverride Override; + Override.Material = Mat; + Override.EmitterRendererProperty = Properties; + Override.MaterialSubIndex = Index; + + NewEmitterMaterials.Add(Override); + } + } + Index++; + } + } + ); + } + } + } + + EmitterMaterials = NewEmitterMaterials; +} + FPrimitiveSceneProxy* UNiagaraComponent::CreateSceneProxy() { LLM_SCOPE(ELLMTag::Niagara); SCOPE_CYCLE_COUNTER(STAT_NiagaraCreateSceneProxy); SCOPE_CYCLE_COUNTER(STAT_NiagaraOverview_GT); + + UpdateEmitterMaterials(); // The constructor will set up the System renderers from the component. FNiagaraSceneProxy* Proxy = new FNiagaraSceneProxy(this); @@ -1597,11 +1721,35 @@ void UNiagaraComponent::GetUsedMaterials(TArray& OutMateria Emitter->ForEachEnabledRenderer( [&](UNiagaraRendererProperties* Properties) { - Properties->GetUsedMaterials(&Sim.Get(), OutMaterials); + + bool bCreateMidsForUsedMaterials = Properties->NeedsMIDsForMaterials(); + TArray Mats; + Properties->GetUsedMaterials(&Sim.Get(), Mats); + + if (bCreateMidsForUsedMaterials) + { + for (const FNiagaraMaterialOverride& Override : EmitterMaterials) + { + if (Override.EmitterRendererProperty == Properties) + { + for (int32 i = 0; i < Mats.Num(); i++) + { + if (i == Override.MaterialSubIndex) + { + Mats[i] = Override.Material; + continue; + } + } + } + } + } + + OutMaterials.Append(Mats); } ); } } + } void UNiagaraComponent::SetComponentTickEnabled(bool bEnabled) @@ -2002,10 +2150,10 @@ void UNiagaraComponent::PostLoad() for (const FNiagaraVariableBase& Var : ToAddNonUser) { - const FNiagaraVariant* FoundVar = InstanceParameterOverrides.Find(Var); + const FNiagaraVariant FoundVar = InstanceParameterOverrides.FindRef(Var); FNiagaraVariableBase UserVar = Var; FNiagaraUserRedirectionParameterStore::MakeUserVariable(UserVar); - InstanceParameterOverrides.Add(UserVar) = *FoundVar; + InstanceParameterOverrides.Emplace(UserVar, FoundVar); } for (const FNiagaraVariableBase& Var : ToRemoveNonUser) @@ -2295,6 +2443,11 @@ void UNiagaraComponent::ApplyOverridesToParameterStore() for (const auto& Pair : TemplateParameterOverrides) { + if (!FNiagaraUserRedirectionParameterStore::IsUserParameter(Pair.Key)) + { + continue; + } + const int32* ExistingParam = OverrideParameters.FindParameterOffset(Pair.Key); if (ExistingParam != nullptr) { @@ -2306,6 +2459,11 @@ void UNiagaraComponent::ApplyOverridesToParameterStore() { for (const auto& Pair : InstanceParameterOverrides) { + if (!FNiagaraUserRedirectionParameterStore::IsUserParameter(Pair.Key)) + { + continue; + } + const int32* ExistingParam = OverrideParameters.FindParameterOffset(Pair.Key); if (ExistingParam != nullptr) { @@ -2385,14 +2543,28 @@ void UNiagaraComponent::AssetExposedParametersChanged() bool UNiagaraComponent::HasParameterOverride(const FNiagaraVariableBase& InKey) const { + FNiagaraVariableBase UserVariable = InKey; + + if (Asset) + { + if (!Asset->GetExposedParameters().RedirectUserVariable(UserVariable)) + { + return false; + } + } + else if (!FNiagaraUserRedirectionParameterStore::IsUserParameter(UserVariable)) + { + return false; + } + if (IsTemplate()) { - const FNiagaraVariant* ThisValue = TemplateParameterOverrides.Find(InKey); + const FNiagaraVariant* ThisValue = TemplateParameterOverrides.Find(UserVariable); const FNiagaraVariant* ArchetypeValue = nullptr; if (const UNiagaraComponent* Archetype = Cast(GetArchetype())) { - ArchetypeValue = Archetype->TemplateParameterOverrides.Find(InKey); + ArchetypeValue = Archetype->TemplateParameterOverrides.Find(UserVariable); } if (ThisValue != nullptr && ArchetypeValue != nullptr) @@ -2408,7 +2580,7 @@ bool UNiagaraComponent::HasParameterOverride(const FNiagaraVariableBase& InKey) } else { - if (InstanceParameterOverrides.Contains(InKey)) + if (InstanceParameterOverrides.Contains(UserVariable)) { return true; } @@ -2424,42 +2596,31 @@ FNiagaraVariant UNiagaraComponent::FindParameterOverride(const FNiagaraVariableB return FNiagaraVariant(); } - if (Asset->GetExposedParameters().FindParameterOffset(InKey) == nullptr) + FNiagaraVariableBase UserVariable = InKey; + + const FNiagaraUserRedirectionParameterStore& ParameterStore = Asset->GetExposedParameters(); + + if (!ParameterStore.RedirectUserVariable(UserVariable)) { return FNiagaraVariant(); } - FNiagaraVariableBase RedirectedVar = Asset->GetExposedParameters().FindRedirection(InKey); + if (ParameterStore.FindParameterOffset(UserVariable) == nullptr) + { + return FNiagaraVariant(); + } if (!IsTemplate()) { - // Check both user and non-user keys - { - const FNiagaraVariant* Value = InstanceParameterOverrides.Find(InKey); - if (Value != nullptr) - { - return *Value; - } - } - { - const FNiagaraVariant* Value = InstanceParameterOverrides.Find(RedirectedVar); - if (Value != nullptr) - { - return *Value; - } - } - } - - // Check both user and non-user keys - { - const FNiagaraVariant* Value = TemplateParameterOverrides.Find(InKey); + const FNiagaraVariant* Value = InstanceParameterOverrides.Find(UserVariable); if (Value != nullptr) { return *Value; } } + { - const FNiagaraVariant* Value = TemplateParameterOverrides.Find(RedirectedVar); + const FNiagaraVariant* Value = TemplateParameterOverrides.Find(UserVariable); if (Value != nullptr) { return *Value; @@ -2494,49 +2655,57 @@ void UNiagaraComponent::SetParameterOverride(const FNiagaraVariableBase& InKey, } // we want to be sure we're storing data based on the fully qualified key name (i.e. taking the user redirection into account) - const FNiagaraVariableBase ParameterKey = OverrideParameters.FindRedirection(InKey); + FNiagaraVariableBase UserVariable = InKey; + if (!OverrideParameters.RedirectUserVariable(UserVariable)) + { + return; + } if (IsTemplate()) { - TemplateParameterOverrides.Add(ParameterKey, InValue); + TemplateParameterOverrides.Add(UserVariable, InValue); } else { - InstanceParameterOverrides.Add(ParameterKey, InValue); + InstanceParameterOverrides.Add(UserVariable, InValue); } - SetOverrideParameterStoreValue(ParameterKey, InValue); + SetOverrideParameterStoreValue(UserVariable, InValue); } void UNiagaraComponent::RemoveParameterOverride(const FNiagaraVariableBase& InKey) { // we want to be sure we're storing data based on the fully qualified key name (i.e. taking the user redirection into account) - const FNiagaraVariableBase ParameterKey = OverrideParameters.FindRedirection(InKey); + FNiagaraVariableBase UserVariable = InKey; + if (!OverrideParameters.RedirectUserVariable(UserVariable)) + { + return; + } if (!IsTemplate()) { - InstanceParameterOverrides.Remove(ParameterKey); + InstanceParameterOverrides.Remove(UserVariable); } else { - TemplateParameterOverrides.Remove(ParameterKey); + TemplateParameterOverrides.Remove(UserVariable); // we are an archetype, but check if we have an archetype and inherit the value from there const UNiagaraComponent* Archetype = Cast(GetArchetype()); if (Archetype != nullptr) { - FNiagaraVariant ArchetypeValue = Archetype->FindParameterOverride(ParameterKey); + FNiagaraVariant ArchetypeValue = Archetype->FindParameterOverride(UserVariable); if (ArchetypeValue.IsValid()) { // defined in archetype, reset value to that - if (ParameterKey.IsDataInterface()) + if (UserVariable.IsDataInterface()) { UNiagaraDataInterface* DataInterface = DuplicateObject(ArchetypeValue.GetDataInterface(), this); - TemplateParameterOverrides.Add(ParameterKey, FNiagaraVariant(DataInterface)); + TemplateParameterOverrides.Add(UserVariable, FNiagaraVariant(DataInterface)); } else { - TemplateParameterOverrides.Add(ParameterKey, ArchetypeValue); + TemplateParameterOverrides.Add(UserVariable, ArchetypeValue); } } } @@ -2659,6 +2828,9 @@ void UNiagaraComponent::SetAsset(UNiagaraSystem* InAsset) Asset->GetExposedParameters().RemoveOnChangedHandler(AssetExposedParametersChangedHandle); } #endif + + UnregisterWithScalabilityManager(); + Asset = InAsset; #if WITH_EDITOR diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponentPool.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponentPool.cpp index 7a034e86ec80..da6a99966de5 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponentPool.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponentPool.cpp @@ -61,7 +61,7 @@ FNCPool::FNCPool() } -void FNCPool::Cleanup() +void FNCPool::Cleanup(bool bFreeOnly) { for (FNCPoolElement& Elem : FreeElements) { @@ -75,30 +75,33 @@ void FNCPool::Cleanup() UE_LOG(LogNiagara, Error, TEXT("Free element in the NiagaraComponentPool was null. Someone must be keeping a reference to a NC that has been freed to the pool and then are manually destroying it.")); } } - - for (UNiagaraComponent* NC : InUseComponents_Auto) - { - //It's possible for people to manually destroy these so we have to guard against it. Though we warn about it in UNiagaraComponent::BeginDestroy - if (NC) - { - NC->PoolingMethod = ENCPoolMethod::None; //Reset so we don't trigger warnings about destroying pooled NCs. - NC->DestroyComponent(); - } - } - - for (UNiagaraComponent* NC : InUseComponents_Manual) - { - //It's possible for people to manually destroy these so we have to guard against it. Though we warn about it in UNiagaraComponent::BeginDestroy - if (NC) - { - NC->PoolingMethod = ENCPoolMethod::None; //Reset so we don't trigger warnings about destroying pooled NCs. - NC->DestroyComponent(); - } - } - FreeElements.Empty(); - InUseComponents_Auto.Empty(); - InUseComponents_Manual.Empty(); + + if (bFreeOnly == false) + { + for (UNiagaraComponent* NC : InUseComponents_Auto) + { + //It's possible for people to manually destroy these so we have to guard against it. Though we warn about it in UNiagaraComponent::BeginDestroy + if (NC) + { + NC->PoolingMethod = ENCPoolMethod::None; //Reset so we don't trigger warnings about destroying pooled NCs. + NC->DestroyComponent(); + } + } + + for (UNiagaraComponent* NC : InUseComponents_Manual) + { + //It's possible for people to manually destroy these so we have to guard against it. Though we warn about it in UNiagaraComponent::BeginDestroy + if (NC) + { + NC->PoolingMethod = ENCPoolMethod::None; //Reset so we don't trigger warnings about destroying pooled NCs. + NC->DestroyComponent(); + } + } + + InUseComponents_Auto.Empty(); + InUseComponents_Manual.Empty(); + } } UNiagaraComponent* FNCPool::Acquire(UWorld* World, UNiagaraSystem* Template, ENCPoolMethod PoolingMethod, bool bForceNew) @@ -153,6 +156,7 @@ UNiagaraComponent* FNCPool::Acquire(UWorld* World, UNiagaraSystem* Template, ENC void FNCPool::Reclaim(UNiagaraComponent* Component, const float CurrentTimeSeconds) { check(Component); + check(Component->GetAsset()); #if ENABLE_NC_POOL_DEBUGGING int32 InUseIdx = INDEX_NONE; @@ -315,17 +319,26 @@ UNiagaraComponentPool::~UNiagaraComponentPool() Cleanup(); } -void UNiagaraComponentPool::Cleanup() +void UNiagaraComponentPool::Cleanup(bool bFreeOnly) { for (TPair& Pool : WorldParticleSystemPools) { FNiagaraCrashReporterScope CRScope(Pool.Key);//In practice this may be null by now :( - Pool.Value.Cleanup(); + Pool.Value.Cleanup(bFreeOnly); } WorldParticleSystemPools.Empty(); } +void UNiagaraComponentPool::ClearPool(UNiagaraSystem* System) +{ + FNCPool* NCPool = WorldParticleSystemPools.Find(System); + if (NCPool) + { + NCPool->Cleanup(true); + } +} + void UNiagaraComponentPool::PrimePool(UNiagaraSystem* Template, UWorld* World) { check(IsInGameThread()); @@ -455,16 +468,17 @@ void UNiagaraComponentPool::ReclaimWorldParticleSystem(UNiagaraComponent* Compon { check(IsInGameThread()); - FNiagaraCrashReporterScope CRScope(Component->GetAsset()); + UNiagaraSystem* Asset = Component->GetAsset(); + FNiagaraCrashReporterScope CRScope(Asset); //If this component has been already destroyed we don't add it back to the pool. Just warn so users can fix it. if (Component->IsPendingKill()) { - UE_LOG(LogNiagara, Log, TEXT("Pooled NC has been destroyed! Possibly via a DestroyComponent() call. You should not destroy components set to auto destroy manually. \nJust deactivate them and allow them to destroy themselves or be reclaimed by the pool if pooling is enabled. | NC: %p |\t System: %s"), Component, *Component->GetAsset()->GetFullName()); + UE_LOG(LogNiagara, Log, TEXT("Pooled NC has been destroyed! Possibly via a DestroyComponent() call. You should not destroy components set to auto destroy manually. \nJust deactivate them and allow them to destroy themselves or be reclaimed by the pool if pooling is enabled. | NC: %p |\t System: %s"), Component, Asset ? *Asset->GetFullName() : TEXT("(nullptr)")); return; } - if (GbEnableNiagaraSystemPooling) + if (GbEnableNiagaraSystemPooling && Asset != nullptr) { float CurrentTime = Component->GetWorld()->GetTimeSeconds(); @@ -474,16 +488,16 @@ void UNiagaraComponentPool::ReclaimWorldParticleSystem(UNiagaraComponent* Compon LastParticleSytemPoolCleanTime = CurrentTime; for (TPair& Pair : WorldParticleSystemPools) { - Pair.Value.KillUnusedComponents(CurrentTime - GNiagaraSystemPoolKillUnusedTime, Component->GetAsset()); + Pair.Value.KillUnusedComponents(CurrentTime - GNiagaraSystemPoolKillUnusedTime, Asset); } } - FNCPool* NCPool = WorldParticleSystemPools.Find(Component->GetAsset()); + FNCPool* NCPool = WorldParticleSystemPools.Find(Asset); if (!NCPool) { UE_LOG(LogNiagara, Warning, TEXT("WorldNC Pool trying to reclaim a system for which it doesn't have a pool! Likely because SetAsset() has been called on this NC. | World: %p | NC: %p | Sys: %s"), Component->GetWorld(), Component, *Component->GetAsset()->GetFullName()); //Just add the new pool and reclaim to that one. - NCPool = &WorldParticleSystemPools.Add(Component->GetAsset()); + NCPool = &WorldParticleSystemPools.Add(Asset); } NCPool->Reclaim(Component, CurrentTime); @@ -577,6 +591,11 @@ void UNiagaraComponentPool::PooledComponentDestroyed(UNiagaraComponent* Componen Component->PoolingMethod = ENCPoolMethod::None; } +void UNiagaraComponentPool::RemoveComponentsBySystem(UNiagaraSystem* System) +{ + WorldParticleSystemPools.Remove(System); +} + void UNiagaraComponentPool::Dump() { #if ENABLE_NC_POOL_DEBUGGING diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponentRendererProperties.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponentRendererProperties.cpp index 2470472c994b..61bc6a814a91 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponentRendererProperties.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponentRendererProperties.cpp @@ -86,6 +86,7 @@ NIAGARA_API FNiagaraTypeDefinition UNiagaraComponentRendererProperties::ToNiagar return FNiagaraTypeDefinition(); } + FNiagaraTypeDefinition UNiagaraComponentRendererProperties::GetFColorDef() { static UPackage* CoreUObjectPkg = FindObjectChecked(nullptr, TEXT("/Script/CoreUObject")); @@ -111,6 +112,37 @@ UNiagaraComponentRendererProperties::UNiagaraComponentRendererProperties() { } + +void UNiagaraComponentRendererProperties::PostLoad() +{ + Super::PostLoad(); + ENiagaraRendererSourceDataMode InSourceMode = ENiagaraRendererSourceDataMode::Particles; + for (FNiagaraComponentPropertyBinding& Binding : PropertyBindings) + { + Binding.AttributeBinding.PostLoad(InSourceMode); + } + EnabledBinding.PostLoad(InSourceMode); + + + PostLoadBindings(ENiagaraRendererSourceDataMode::Particles); +} + + +void UNiagaraComponentRendererProperties::UpdateSourceModeDerivates(ENiagaraRendererSourceDataMode InSourceMode, bool bFromPropertyEdit) +{ + UNiagaraEmitter* SrcEmitter = GetTypedOuter(); + if (SrcEmitter) + { + EnabledBinding.CacheValues(SrcEmitter, InSourceMode); + for (FNiagaraComponentPropertyBinding& Binding : PropertyBindings) + { + Binding.AttributeBinding.CacheValues(SrcEmitter, InSourceMode); + } + } + + Super::UpdateSourceModeDerivates(InSourceMode); +} + void UNiagaraComponentRendererProperties::PostInitProperties() { Super::PostInitProperties(); @@ -123,13 +155,19 @@ void UNiagaraComponentRendererProperties::PostInitProperties() ComponentRendererPropertiesToDeferredInit.Add(this); return; } - else if (EnabledBinding.BoundVariable.GetName() == NAME_None) + else if (!EnabledBinding.IsValid()) { EnabledBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_COMPONENTS_ENABLED); } } } + +void UNiagaraComponentRendererProperties::CacheFromCompiledData(const FNiagaraDataSetCompiledData* CompiledData) +{ + UpdateSourceModeDerivates(ENiagaraRendererSourceDataMode::Particles); +} + void UNiagaraComponentRendererProperties::PostDuplicate(bool bDuplicateForPIE) { if (ComponentType) @@ -153,7 +191,7 @@ void UNiagaraComponentRendererProperties::InitCDOPropertiesAfterModuleStartup() { if (WeakComponentRendererProperties.Get()) { - if (WeakComponentRendererProperties->EnabledBinding.BoundVariable.GetName() == NAME_None) + if (!WeakComponentRendererProperties->EnabledBinding.IsValid()) { WeakComponentRendererProperties->EnabledBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_COMPONENTS_ENABLED); } @@ -161,12 +199,12 @@ void UNiagaraComponentRendererProperties::InitCDOPropertiesAfterModuleStartup() } } -FNiagaraRenderer* UNiagaraComponentRendererProperties::CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter) +FNiagaraRenderer* UNiagaraComponentRendererProperties::CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) { EmitterPtr = Emitter->GetCachedEmitter(); FNiagaraRenderer* NewRenderer = new FNiagaraRendererComponents(FeatureLevel, this, Emitter); - NewRenderer->Initialize(this, Emitter); + NewRenderer->Initialize(this, Emitter, InComponent); return NewRenderer; } @@ -199,14 +237,12 @@ void UNiagaraComponentRendererProperties::PostEditChangeProperty(struct FPropert CreateTemplateComponent(); FNiagaraComponentPropertyBinding PositionBinding; - PositionBinding.AttributeBinding.BoundVariable = SYS_PARAM_PARTICLES_POSITION; - PositionBinding.AttributeBinding.DataSetVariable = FNiagaraConstants::GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_POSITION); + PositionBinding.AttributeBinding.Setup(SYS_PARAM_PARTICLES_POSITION, FNiagaraConstants::GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_POSITION), SYS_PARAM_PARTICLES_POSITION); PositionBinding.PropertyName = FName("RelativeLocation"); PropertyBindings.Add(PositionBinding); FNiagaraComponentPropertyBinding ScaleBinding; - ScaleBinding.AttributeBinding.BoundVariable = SYS_PARAM_PARTICLES_SCALE; - ScaleBinding.AttributeBinding.DataSetVariable = FNiagaraConstants::GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_SCALE); + ScaleBinding.AttributeBinding.Setup(SYS_PARAM_PARTICLES_SCALE, FNiagaraConstants::GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_SCALE), SYS_PARAM_PARTICLES_SCALE); ScaleBinding.PropertyName = FName("RelativeScale3D"); PropertyBindings.Add(ScaleBinding); } @@ -301,9 +337,9 @@ const TArray& UNiagaraComponentRendererProperties::GetBoundAtt } for (const FNiagaraComponentPropertyBinding& PropertyBinding : PropertyBindings) { - if (PropertyBinding.AttributeBinding.BoundVariable.IsValid()) + if (PropertyBinding.AttributeBinding.IsValid()) { - CurrentBoundAttributes.Add(PropertyBinding.AttributeBinding.BoundVariable); + CurrentBoundAttributes.Add(PropertyBinding.AttributeBinding.GetParamMapBindableVariable()); } } return CurrentBoundAttributes; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponentSettings.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponentSettings.cpp index 06917ef61643..609023b896c7 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponentSettings.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponentSettings.cpp @@ -2,12 +2,12 @@ #include "NiagaraComponentSettings.h" -int32 UNiagaraComponentSettings::bAllowSupressActivation = 0; +int32 UNiagaraComponentSettings::bAllowSuppressActivation = 0; int32 UNiagaraComponentSettings::bAllowForceAutoPooling = 0; static FAutoConsoleVariableRef CVarNiagaraUseSupressActivateList( TEXT("fx.Niagara.UseSupressActivateList"), - UNiagaraComponentSettings::bAllowSupressActivation, + UNiagaraComponentSettings::bAllowSuppressActivation, TEXT("When a component is activated we will check the surpession list."), ECVF_Default ); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraConstants.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraConstants.cpp index 3e14cbcedbe2..793ca5c5752f 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraConstants.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraConstants.cpp @@ -114,6 +114,7 @@ void FNiagaraConstants::Init() SwitchParameters.Add(SYS_PARAM_EMITTER_OVERRIDE_GLOBAL_SPAWN_COUNT_SCALE); SwitchParameters.Add(SYS_PARAM_EMITTER_SIMULATION_TARGET); SwitchParameters.Add(SYS_PARAM_SCRIPT_USAGE); + SwitchParameters.Add(SYS_PARAM_SCRIPT_CONTEXT); } if (UpdatedSystemParameters.Num() == 0) @@ -233,11 +234,16 @@ void FNiagaraConstants::Init() Attributes.Add(SYS_PARAM_PARTICLES_LIGHT_RADIUS); Attributes.Add(SYS_PARAM_PARTICLES_LIGHT_ENABLED); Attributes.Add(SYS_PARAM_PARTICLES_VISIBILITY_TAG); + Attributes.Add(SYS_PARAM_PARTICLES_COMPONENTS_ENABLED); Attributes.Add(SYS_PARAM_PARTICLES_RIBBONID); Attributes.Add(SYS_PARAM_PARTICLES_RIBBONWIDTH); Attributes.Add(SYS_PARAM_PARTICLES_RIBBONTWIST); Attributes.Add(SYS_PARAM_PARTICLES_RIBBONFACING); Attributes.Add(SYS_PARAM_PARTICLES_RIBBONLINKORDER); + Attributes.Add(SYS_PARAM_PARTICLES_RIBBONU0OVERRIDE); + Attributes.Add(SYS_PARAM_PARTICLES_RIBBONV0RANGEOVERRIDE); + Attributes.Add(SYS_PARAM_PARTICLES_RIBBONU1OVERRIDE); + Attributes.Add(SYS_PARAM_PARTICLES_RIBBONV1RANGEOVERRIDE); Attributes.Add(SYS_PARAM_INSTANCE_ALIVE); @@ -245,35 +251,40 @@ void FNiagaraConstants::Init() if (AttrDataSetKeyMap.Num() == 0) { - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_POSITION, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_POSITION)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_VELOCITY, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_VELOCITY)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_COLOR, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_COLOR)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_SPRITE_ROTATION, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_SPRITE_ROTATION)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_NORMALIZED_AGE, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_NORMALIZED_AGE)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_SPRITE_SIZE, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_SPRITE_SIZE)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_SPRITE_FACING, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_SPRITE_FACING)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_SPRITE_ALIGNMENT, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_SPRITE_ALIGNMENT)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_SUB_IMAGE_INDEX, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_SUB_IMAGE_INDEX)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_1, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_1)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_2, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_2)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_3, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_3)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_SCALE, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_SCALE)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIFETIME, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_LIFETIME)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_MESH_ORIENTATION, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_MESH_ORIENTATION)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_CAMERA_OFFSET, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_CAMERA_OFFSET)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_UV_SCALE, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_UV_SCALE)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_MATERIAL_RANDOM, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_MATERIAL_RANDOM)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIGHT_ENABLED, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_LIGHT_ENABLED)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIGHT_RADIUS, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_LIGHT_RADIUS)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIGHT_EXPONENT, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_LIGHT_EXPONENT)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_VISIBILITY_TAG, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_VISIBILITY_TAG)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONID, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_RIBBONID)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONWIDTH, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_RIBBONWIDTH)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONTWIST, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_RIBBONTWIST)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONFACING, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_RIBBONFACING)); - AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONLINKORDER, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_RIBBONLINKORDER)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_POSITION, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_POSITION)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_VELOCITY, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_VELOCITY)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_COLOR, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_COLOR)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_SPRITE_ROTATION, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_SPRITE_ROTATION)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_NORMALIZED_AGE, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_NORMALIZED_AGE)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_SPRITE_SIZE, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_SPRITE_SIZE)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_SPRITE_FACING, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_SPRITE_FACING)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_SPRITE_ALIGNMENT, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_SPRITE_ALIGNMENT)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_SUB_IMAGE_INDEX, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_SUB_IMAGE_INDEX)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_1, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_1)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_2, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_2)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_3, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_3)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_SCALE, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_SCALE)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIFETIME, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_LIFETIME)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_MESH_ORIENTATION, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_MESH_ORIENTATION)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_CAMERA_OFFSET, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_CAMERA_OFFSET)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_UV_SCALE, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_UV_SCALE)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_MATERIAL_RANDOM, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_MATERIAL_RANDOM)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIGHT_ENABLED, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_LIGHT_ENABLED)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIGHT_RADIUS, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_LIGHT_RADIUS)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIGHT_EXPONENT, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_LIGHT_EXPONENT)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_VISIBILITY_TAG, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_VISIBILITY_TAG)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_COMPONENTS_ENABLED, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_COMPONENTS_ENABLED)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONID, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_RIBBONID)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONWIDTH, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_RIBBONWIDTH)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONTWIST, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_RIBBONTWIST)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONFACING, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_RIBBONFACING)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONLINKORDER, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_RIBBONLINKORDER)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONU0OVERRIDE, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_RIBBONU0OVERRIDE)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONV0RANGEOVERRIDE, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_RIBBONV0RANGEOVERRIDE)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONU1OVERRIDE, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_RIBBONU1OVERRIDE)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONV1RANGEOVERRIDE, GetAttributeAsParticleDataSetKey(SYS_PARAM_PARTICLES_RIBBONV1RANGEOVERRIDE)); } if (AttrDefaultsStrMap.Num() == 0) @@ -397,6 +408,11 @@ void FNiagaraConstants::Init() Var.SetValue(0.0f); AttrDefaultsValueMap.Add(SYS_PARAM_PARTICLES_VISIBILITY_TAG, Var); + AttrDefaultsStrMap.Add(SYS_PARAM_PARTICLES_COMPONENTS_ENABLED, TEXT("true")); + Var = SYS_PARAM_PARTICLES_COMPONENTS_ENABLED; + Var.SetValue(true); + AttrDefaultsValueMap.Add(SYS_PARAM_PARTICLES_COMPONENTS_ENABLED, Var); + AttrDefaultsStrMap.Add(SYS_PARAM_PARTICLES_RIBBONID, TEXT("0")); Var = SYS_PARAM_PARTICLES_RIBBONID; Var.SetValue(FNiagaraID()); @@ -421,6 +437,26 @@ void FNiagaraConstants::Init() Var = SYS_PARAM_PARTICLES_RIBBONLINKORDER; Var.SetValue(0.0f); AttrDefaultsValueMap.Add(SYS_PARAM_PARTICLES_RIBBONLINKORDER, Var); + + AttrDefaultsStrMap.Add(SYS_PARAM_PARTICLES_RIBBONU0OVERRIDE, TEXT("0")); + Var = SYS_PARAM_PARTICLES_RIBBONU0OVERRIDE; + Var.SetValue(0.0f); + AttrDefaultsValueMap.Add(SYS_PARAM_PARTICLES_RIBBONU0OVERRIDE, Var); + + AttrDefaultsStrMap.Add(SYS_PARAM_PARTICLES_RIBBONV0RANGEOVERRIDE, TEXT("0.0, 1.0")); + Var = SYS_PARAM_PARTICLES_RIBBONV0RANGEOVERRIDE; + Var.SetValue(FVector2D(0.0f, 1.0f)); + AttrDefaultsValueMap.Add(SYS_PARAM_PARTICLES_RIBBONV0RANGEOVERRIDE, Var); + + AttrDefaultsStrMap.Add(SYS_PARAM_PARTICLES_RIBBONU1OVERRIDE, TEXT("0")); + Var = SYS_PARAM_PARTICLES_RIBBONU1OVERRIDE; + Var.SetValue(0.0f); + AttrDefaultsValueMap.Add(SYS_PARAM_PARTICLES_RIBBONU1OVERRIDE, Var); + + AttrDefaultsStrMap.Add(SYS_PARAM_PARTICLES_RIBBONV1RANGEOVERRIDE, TEXT("0.0, 1.0")); + Var = SYS_PARAM_PARTICLES_RIBBONV1RANGEOVERRIDE; + Var.SetValue(FVector2D(0.0f, 1.0f)); + AttrDefaultsValueMap.Add(SYS_PARAM_PARTICLES_RIBBONV1RANGEOVERRIDE, Var); } if (AttrDescStrMap.Num() == 0) @@ -451,11 +487,16 @@ void FNiagaraConstants::Init() AttrDescStrMap.Add(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING, LOCTEXT("LightVolumetricScatteringParamDesc", "Used to drive the volumetric scattering intensity of the light when using a Light renderer.")); AttrDescStrMap.Add(SYS_PARAM_INSTANCE_ALIVE, LOCTEXT("AliveParamDesc", "Used to determine whether or not this particle instance is still valid or if it can be deleted.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_VISIBILITY_TAG, LOCTEXT("VisibilityTag", "Used for selecting renderers to use when rendering this particle. Without this, the particle will render in all renderers")); + AttrDescStrMap.Add(SYS_PARAM_PARTICLES_COMPONENTS_ENABLED, LOCTEXT("ComponentRenderEnabledParamDesc", "Used to check if component rendering should be enabled on a per-particle basis. Without this, the each particle will spawn a component.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONID, LOCTEXT("RibbonIDDesc", "Sets the ribbon id for a particle. Particles with the same ribbon id will be connected into a ribbon.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONWIDTH, LOCTEXT("RibbonWidthDesc", "Sets the ribbon width for a particle, in UE4 units.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONTWIST, LOCTEXT("RibbonTwistDesc", "Sets the ribbon twist for a particle, in degrees.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONFACING, LOCTEXT("RibbonFacingDesc", "Sets the facing vector of the ribbon at the particle position, or the side vector the ribbon's width is extended along, depending on the selected facing mode.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONLINKORDER, LOCTEXT("RibbonLinkOrderDesc", "Explicit order for linking particles within a ribbon. Particles of the same ribbon id will be connected into a ribbon in incrementing order of this attribute value.")); + AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONU0OVERRIDE, LOCTEXT("RibbonU0OverrideDesc", "Overrides the U component of the UV0 texture coordinate of a ribbon particle.")); + AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONV0RANGEOVERRIDE, LOCTEXT("RibbonV0RangeOverrideDesc", "Overrives the V range across the width of a ribbon for the UV0 texture coordinate of a particle.")); + AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONU1OVERRIDE, LOCTEXT("RibbonU1OverrideDesc", "Overrides the U component of the UV1 texture coordinate of a ribbon particle.")); + AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONV1RANGEOVERRIDE, LOCTEXT("RibbonV1RangeOverrideDesc", "Overrives the V range across the width of a ribbon for the UV1 texture coordinate of a particle.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_ID, LOCTEXT("IDDesc", "Engine managed particle attribute that is a persistent ID for each particle.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_UNIQUE_ID, LOCTEXT("UniqueIDDesc", "Engine managed particle attribute that is a unique ID for each particle. The ID is incremented for each new particle spawned.")); } @@ -618,7 +659,7 @@ FNiagaraVariable FNiagaraConstants::GetAttributeWithDefaultValue(const FNiagaraV return FNiagaraVariable(); } -FNiagaraVariable FNiagaraConstants::GetAttributeAsDataSetKey(const FNiagaraVariable& InVar) +FNiagaraVariable FNiagaraConstants::GetAttributeAsParticleDataSetKey(const FNiagaraVariable& InVar) { FNiagaraVariable OutVar = InVar; FString DataSetName = InVar.GetName().ToString(); @@ -627,6 +668,14 @@ FNiagaraVariable FNiagaraConstants::GetAttributeAsDataSetKey(const FNiagaraVaria return OutVar; } +FNiagaraVariable FNiagaraConstants::GetAttributeAsEmitterDataSetKey(const FNiagaraVariable& InVar) +{ + FNiagaraVariable OutVar = InVar; + FString DataSetName = InVar.GetName().ToString(); + DataSetName.RemoveFromStart(TEXT("Emitter.")); + OutVar.SetName(*DataSetName); + return OutVar; +} FNiagaraVariableAttributeBinding FNiagaraConstants::GetAttributeDefaultBinding(const FNiagaraVariable& InVar) { if (AttrDefaultsValueMap.Num() == 0) @@ -635,15 +684,8 @@ FNiagaraVariableAttributeBinding FNiagaraConstants::GetAttributeDefaultBinding(c } FNiagaraVariableAttributeBinding Binding; - Binding.BoundVariable = InVar; - Binding.DataSetVariable = InVar; const FNiagaraVariable* FoundVar = AttrDataSetKeyMap.Find(InVar); - if (FoundVar) - { - Binding.DataSetVariable = *FoundVar; - } - - Binding.DefaultValueIfNonExistent = GetAttributeWithDefaultValue(InVar); + Binding.Setup(InVar, FoundVar ? *FoundVar : InVar, GetAttributeWithDefaultValue(InVar)); return Binding; } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArray.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArray.cpp index 958aabc4af7d..5b36d3a62a11 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArray.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArray.cpp @@ -49,9 +49,11 @@ bool UNiagaraDataInterfaceArray::CopyToInternal(UNiagaraDataInterface* Destinati { return false; } + UNiagaraDataInterfaceArray* OtherTyped = CastChecked(Destination); + OtherTyped->MaxElements = MaxElements; if (ensureMsgf(Impl.IsValid(), TEXT("Impl should always be valid for %s"), *GetNameSafe(GetClass()))) { - return Impl->CopyToInternal(CastChecked(Destination)->Impl.Get()); + return Impl->CopyToInternal(OtherTyped->Impl.Get()); } return true; } @@ -62,9 +64,16 @@ bool UNiagaraDataInterfaceArray::Equals(const UNiagaraDataInterface* Other) cons { return false; } + + const UNiagaraDataInterfaceArray* OtherTyped = CastChecked(Other); + if (OtherTyped->MaxElements != MaxElements) + { + return false; + } + if (ensureMsgf(Impl.IsValid(), TEXT("Impl should always be valid for %s"), *GetNameSafe(GetClass()))) { - return Impl->Equals(CastChecked(Other)->Impl.Get()); + return Impl->Equals(OtherTyped->Impl.Get()); } return true; } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayFloat.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayFloat.cpp index fdc1085ff730..c592721af0b7 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayFloat.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayFloat.cpp @@ -10,7 +10,6 @@ struct FNDIArrayImplHelper : public FNDIArrayImplHelperBase static constexpr TCHAR const* HLSLValueTypeName = TEXT("float"); static constexpr TCHAR const* HLSLBufferTypeName = TEXT("float"); static constexpr EPixelFormat PixelFormat = PF_R32_FLOAT; - static FRHIShaderResourceView* GetDummyBuffer() { return FNiagaraRenderer::GetDummyFloatBuffer(); } static const FNiagaraTypeDefinition& GetTypeDefinition() { return FNiagaraTypeDefinition::GetFloatDef(); } static const float GetDefaultValue() { return 0.0f; } }; @@ -21,7 +20,6 @@ struct FNDIArrayImplHelper : public FNDIArrayImplHelperBase : public FNDIArrayImplHelperBase static constexpr TCHAR const* HLSLValueTypeName = TEXT("float3"); static constexpr TCHAR const* HLSLBufferTypeName = TEXT("float"); //-OPT: Current we have no float3 pixel format, when we add one update this to use it static constexpr EPixelFormat PixelFormat = PF_R32_FLOAT; - static FRHIShaderResourceView* GetDummyBuffer() { return FNiagaraRenderer::GetDummyFloatBuffer(); } static const FNiagaraTypeDefinition& GetTypeDefinition() { return FNiagaraTypeDefinition::GetVec3Def(); } static const FVector GetDefaultValue() { return FVector::ZeroVector; } @@ -54,7 +51,6 @@ struct FNDIArrayImplHelper : public FNDIArrayImplHelperBase static constexpr TCHAR const* HLSLValueTypeName = TEXT("float4"); static constexpr TCHAR const* HLSLBufferTypeName = TEXT("float4"); static constexpr EPixelFormat PixelFormat = PF_A32B32G32R32F; - static FRHIShaderResourceView* GetDummyBuffer() { return FNiagaraRenderer::GetDummyFloat4Buffer(); } static const FNiagaraTypeDefinition& GetTypeDefinition() { return FNiagaraTypeDefinition::GetVec4Def(); } static const FVector4 GetDefaultValue() { return FVector4(ForceInitToZero); } }; @@ -65,7 +61,6 @@ struct FNDIArrayImplHelper : public FNDIArrayImplHelperBase : public FNDIArrayImplHelperBase static constexpr TCHAR const* HLSLValueTypeName = TEXT("float4"); static constexpr TCHAR const* HLSLBufferTypeName = TEXT("float4"); static constexpr EPixelFormat PixelFormat = PF_A32B32G32R32F; - static FRHIShaderResourceView* GetDummyBuffer() { return FNiagaraRenderer::GetDummyFloat4Buffer(); } static const FNiagaraTypeDefinition& GetTypeDefinition() { return FNiagaraTypeDefinition::GetQuatDef(); } static const FQuat GetDefaultValue() { return FQuat::Identity; } }; @@ -85,40 +79,40 @@ UNiagaraDataInterfaceArrayFloat::UNiagaraDataInterfaceArrayFloat(FObjectInitiali : UNiagaraDataInterfaceArray(ObjectInitializer) { Proxy.Reset(new FNiagaraDataInterfaceProxyArrayImpl()); - Impl.Reset(new FNiagaraDataInterfaceArrayImpl(Proxy, FloatData, ArrayRWGuard)); + Impl.Reset(new FNiagaraDataInterfaceArrayImpl(this, FloatData)); } UNiagaraDataInterfaceArrayFloat2::UNiagaraDataInterfaceArrayFloat2(FObjectInitializer const& ObjectInitializer) : UNiagaraDataInterfaceArray(ObjectInitializer) { Proxy.Reset(new FNiagaraDataInterfaceProxyArrayImpl()); - Impl.Reset(new FNiagaraDataInterfaceArrayImpl(Proxy, FloatData, ArrayRWGuard)); + Impl.Reset(new FNiagaraDataInterfaceArrayImpl(this, FloatData)); } UNiagaraDataInterfaceArrayFloat3::UNiagaraDataInterfaceArrayFloat3(FObjectInitializer const& ObjectInitializer) : UNiagaraDataInterfaceArray(ObjectInitializer) { Proxy.Reset(new FNiagaraDataInterfaceProxyArrayImpl()); - Impl.Reset(new FNiagaraDataInterfaceArrayImpl(Proxy, FloatData, ArrayRWGuard)); + Impl.Reset(new FNiagaraDataInterfaceArrayImpl(this, FloatData)); } UNiagaraDataInterfaceArrayFloat4::UNiagaraDataInterfaceArrayFloat4(FObjectInitializer const& ObjectInitializer) : UNiagaraDataInterfaceArray(ObjectInitializer) { Proxy.Reset(new FNiagaraDataInterfaceProxyArrayImpl()); - Impl.Reset(new FNiagaraDataInterfaceArrayImpl(Proxy, FloatData, ArrayRWGuard)); + Impl.Reset(new FNiagaraDataInterfaceArrayImpl(this, FloatData)); } UNiagaraDataInterfaceArrayColor::UNiagaraDataInterfaceArrayColor(FObjectInitializer const& ObjectInitializer) : UNiagaraDataInterfaceArray(ObjectInitializer) { Proxy.Reset(new FNiagaraDataInterfaceProxyArrayImpl()); - Impl.Reset(new FNiagaraDataInterfaceArrayImpl(Proxy, ColorData, ArrayRWGuard)); + Impl.Reset(new FNiagaraDataInterfaceArrayImpl(this, ColorData)); } UNiagaraDataInterfaceArrayQuat::UNiagaraDataInterfaceArrayQuat(FObjectInitializer const& ObjectInitializer) : UNiagaraDataInterfaceArray(ObjectInitializer) { Proxy.Reset(new FNiagaraDataInterfaceProxyArrayImpl()); - Impl.Reset(new FNiagaraDataInterfaceArrayImpl(Proxy, QuatData, ArrayRWGuard)); + Impl.Reset(new FNiagaraDataInterfaceArrayImpl(this, QuatData)); } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayFunctionLibrary.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayFunctionLibrary.cpp index 5a7d13bb7362..be64910c0ee0 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayFunctionLibrary.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayFunctionLibrary.cpp @@ -75,6 +75,16 @@ void UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayInt32(UNiagaraCom } } +void UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayBool(UNiagaraComponent* NiagaraSystem, FName OverrideName, const TArray& ArrayData) +{ + if (UNiagaraDataInterfaceArrayBool* ArrayDI = UNiagaraFunctionLibrary::GetDataInterface(NiagaraSystem, OverrideName)) + { + FRWScopeLock WriteLock(ArrayDI->ArrayRWGuard, SLT_Write); + ArrayDI->BoolData = ArrayData; + ArrayDI->UpdateGPU(); + } +} + TArray UNiagaraDataInterfaceArrayFunctionLibrary::GetNiagaraArrayFloat(UNiagaraComponent* NiagaraSystem, FName OverrideName) { if (UNiagaraDataInterfaceArrayFloat* ArrayDI = UNiagaraFunctionLibrary::GetDataInterface(NiagaraSystem, OverrideName)) @@ -145,3 +155,13 @@ TArray UNiagaraDataInterfaceArrayFunctionLibrary::GetNiagaraArrayInt32(UN return TArray(); } +TArray UNiagaraDataInterfaceArrayFunctionLibrary::GetNiagaraArrayBool(UNiagaraComponent* NiagaraSystem, FName OverrideName) +{ + if (UNiagaraDataInterfaceArrayBool* ArrayDI = UNiagaraFunctionLibrary::GetDataInterface(NiagaraSystem, OverrideName)) + { + FRWScopeLock ReadLock(ArrayDI->ArrayRWGuard, SLT_ReadOnly); + return ArrayDI->BoolData; + } + return TArray(); +} + diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayImpl.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayImpl.cpp index 6f38874ebe62..6007e67dde50 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayImpl.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayImpl.cpp @@ -6,6 +6,12 @@ const FName FNiagaraDataInterfaceArrayImplHelper::Function_GetNumName(TEXT("GetN const FName FNiagaraDataInterfaceArrayImplHelper::Function_IsValidIndexName(TEXT("IsValidIndex")); const FName FNiagaraDataInterfaceArrayImplHelper::Function_GetValueName(TEXT("GetValue")); +const FName FNiagaraDataInterfaceArrayImplHelper::Function_Reset(TEXT("Reset")); +const FName FNiagaraDataInterfaceArrayImplHelper::Function_SetNumValue(TEXT("SetNum")); +const FName FNiagaraDataInterfaceArrayImplHelper::Function_SetValueName(TEXT("SetValue")); +const FName FNiagaraDataInterfaceArrayImplHelper::Function_PushValueName(TEXT("PushValue")); +const FName FNiagaraDataInterfaceArrayImplHelper::Function_PopValueName(TEXT("PopValue")); + FString FNiagaraDataInterfaceArrayImplHelper::GetBufferName(const FString& InterfaceName) { return TEXT("ArrayBuffer_") + InterfaceName; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayInt.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayInt.cpp index 4fc415aa5193..99f64fe3e9b0 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayInt.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceArrayInt.cpp @@ -10,7 +10,6 @@ struct FNDIArrayImplHelper : public FNDIArrayImplHelperBase static constexpr TCHAR const* HLSLValueTypeName = TEXT("int"); static constexpr TCHAR const* HLSLBufferTypeName = TEXT("int"); static constexpr EPixelFormat PixelFormat = PF_R32_SINT; - static FRHIShaderResourceView* GetDummyBuffer() { return FNiagaraRenderer::GetDummyIntBuffer(); } static const FNiagaraTypeDefinition& GetTypeDefinition() { return FNiagaraTypeDefinition::GetIntDef(); } static const int32 GetDefaultValue() { return 0; } }; @@ -22,7 +21,6 @@ struct FNDIArrayImplHelper : public FNDIArrayImplHelperBase static constexpr TCHAR const* HLSLValueTypeName = TEXT("bool"); static constexpr TCHAR const* HLSLBufferTypeName = TEXT("bool"); static constexpr EPixelFormat PixelFormat = PF_R8_UINT; - static FRHIShaderResourceView* GetDummyBuffer() { return FNiagaraRenderer::GetDummyIntBuffer(); } static const FNiagaraTypeDefinition& GetTypeDefinition() { return FNiagaraTypeDefinition::GetBoolDef(); } static const bool GetDefaultValue() { return false; } }; @@ -31,7 +29,7 @@ UNiagaraDataInterfaceArrayInt32::UNiagaraDataInterfaceArrayInt32(FObjectInitiali : UNiagaraDataInterfaceArray(ObjectInitializer) { Proxy.Reset(new FNiagaraDataInterfaceProxyArrayImpl()); - Impl.Reset(new FNiagaraDataInterfaceArrayImpl(Proxy, IntData, ArrayRWGuard)); + Impl.Reset(new FNiagaraDataInterfaceArrayImpl(this, IntData)); } UNiagaraDataInterfaceArrayBool::UNiagaraDataInterfaceArrayBool(FObjectInitializer const& ObjectInitializer) @@ -40,5 +38,5 @@ UNiagaraDataInterfaceArrayBool::UNiagaraDataInterfaceArrayBool(FObjectInitialize static_assert(sizeof(bool) == sizeof(uint8), "Bool != 1 byte this will mean the GPU array does not match in size"); Proxy.Reset(new FNiagaraDataInterfaceProxyArrayImpl()); - Impl.Reset(new FNiagaraDataInterfaceArrayImpl(Proxy, BoolData, ArrayRWGuard)); + Impl.Reset(new FNiagaraDataInterfaceArrayImpl(this, BoolData)); } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceAudioPlayer.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceAudioPlayer.cpp index 3c34ed03b6b7..2f5559091895 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceAudioPlayer.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceAudioPlayer.cpp @@ -4,13 +4,29 @@ #include "NiagaraTypes.h" #include "NiagaraCustomVersion.h" +#include "NiagaraStats.h" #include "Internationalization/Internationalization.h" #include "NiagaraSystemInstance.h" #include "NiagaraWorldManager.h" #include "Kismet/GameplayStatics.h" #include "Sound/SoundBase.h" +#include "Components/AudioComponent.h" + +DECLARE_CYCLE_STAT(TEXT("Audio DI update persistent sound"), STAT_NiagaraAudioDIUpdateSound, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Audio DI create persistent sound"), STAT_NiagaraAudioDICreateSound, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Audio DI stop persistent sound"), STAT_NiagaraAudioDIStopSound, STATGROUP_Niagara); const FName UNiagaraDataInterfaceAudioPlayer::PlayAudioName(TEXT("PlayAudioAtLocation")); +const FName UNiagaraDataInterfaceAudioPlayer::PlayPersistentAudioName(TEXT("PlayPersistentAudio")); +const FName UNiagaraDataInterfaceAudioPlayer::SetPersistentAudioVolumeName(TEXT("UpdateAudioVolume")); +const FName UNiagaraDataInterfaceAudioPlayer::SetPersistentAudioPitchName(TEXT("UpdateAudioPitch")); +const FName UNiagaraDataInterfaceAudioPlayer::SetPersistentAudioLocationName(TEXT("UpdateAudioLocation")); +const FName UNiagaraDataInterfaceAudioPlayer::SetPersistentAudioRotationName(TEXT("UpdateAudioRotation")); +const FName UNiagaraDataInterfaceAudioPlayer::SetPersistentAudioBoolParamName(TEXT("SetBooleanParameter")); +const FName UNiagaraDataInterfaceAudioPlayer::SetPersistentAudioIntegerParamName(TEXT("SetIntegerParameter")); +const FName UNiagaraDataInterfaceAudioPlayer::SetPersistentAudioFloatParamName(TEXT("SetFloatParameter")); +const FName UNiagaraDataInterfaceAudioPlayer::PausePersistentAudioName(TEXT("SetPaused")); + /** Async task to play the audio on the game thread and isolate from the niagara tick @@ -94,6 +110,13 @@ bool UNiagaraDataInterfaceAudioPlayer::InitPerInstanceData(void* PerInstanceData void UNiagaraDataInterfaceAudioPlayer::DestroyPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) { FAudioPlayerInterface_InstanceData* InstData = (FAudioPlayerInterface_InstanceData*)PerInstanceData; + for (const auto& Entry : InstData->PersistentAudioMapping) + { + if (Entry.Value.IsValid()) + { + Entry.Value->Stop(); + } + } InstData->~FAudioPlayerInterface_InstanceData(); } @@ -117,6 +140,8 @@ bool UNiagaraDataInterfaceAudioPlayer::PerInstanceTick(void* PerInstanceData, FN PIData->Attenuation.Reset(); PIData->Concurrency.Reset(); } + + PIData->ParameterNames = ParameterNames; return false; } @@ -124,23 +149,40 @@ bool UNiagaraDataInterfaceAudioPlayer::PerInstanceTickPostSimulate(void* PerInst { FAudioPlayerInterface_InstanceData* PIData = (FAudioPlayerInterface_InstanceData*) PerInstanceData; UNiagaraSystem* System = SystemInstance->GetSystem(); - if (!PIData->GatheredData.IsEmpty() && System) + if (!PIData->PlayAudioQueue.IsEmpty() && System) { //Drain the queue into an array here TArray Data; FAudioParticleData Value; - while (PIData->GatheredData.Dequeue(Value)) + while (PIData->PlayAudioQueue.Dequeue(Value)) { Data.Add(Value); if (PIData->MaxPlaysPerTick > 0 && Data.Num() >= PIData->MaxPlaysPerTick) { // discard the rest of the queue if over the tick limit - PIData->GatheredData.Empty(); + PIData->PlayAudioQueue.Empty(); break; } } TGraphTask::CreateTask().ConstructAndDispatchWhenReady(PIData->SoundToPlay, PIData->Attenuation, PIData->Concurrency, Data, SystemInstance->GetWorldManager()->GetWorld()); } + + // process the persistent audio updates + FPersistentAudioParticleData Value; + while (PIData->PersistentAudioActionQueue.Dequeue(Value)) + { + UAudioComponent* AudioComponent = nullptr; + if (Value.AudioHandle > 0) + { + auto MappedValue = PIData->PersistentAudioMapping.Find(Value.AudioHandle); + if (MappedValue && MappedValue->IsValid()) + { + AudioComponent = MappedValue->Get(); + } + } + // since we are in the game thread here, it is safe for the callback to access the audio component + Value.UpdateCallback(PIData, AudioComponent, SystemInstance); + } return false; } @@ -176,6 +218,143 @@ void UNiagaraDataInterfaceAudioPlayer::GetFunctions(TArray InstData(Context); + + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam NameIndexParam(Context); + FNDIInputParam ValueParam(Context); + checkfSlow(InstData.Get(), TEXT("Audio player interface has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.NumInstances; ++i) + { + int32 Handle = AudioHandleInParam.GetAndAdvance(); + int32 NameIndex = NameIndexParam.GetAndAdvance(); + bool Value = ValueParam.GetAndAdvance(); + + if (Handle > 0 && InstData->ParameterNames.IsValidIndex(NameIndex)) + { + FName ParameterName = InstData->ParameterNames[NameIndex]; + FPersistentAudioParticleData AudioData; + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [ParameterName, Value](FAudioPlayerInterface_InstanceData*, UAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + if (AudioComponent && AudioComponent->IsPlaying()) + { + AudioComponent->SetBoolParameter(ParameterName, Value); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + } +} + +void UNiagaraDataInterfaceAudioPlayer::SetParameterInteger(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam NameIndexParam(Context); + FNDIInputParam ValueParam(Context); + checkfSlow(InstData.Get(), TEXT("Audio player interface has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.NumInstances; ++i) + { + int32 Handle = AudioHandleInParam.GetAndAdvance(); + int32 NameIndex = NameIndexParam.GetAndAdvance(); + int32 Value = ValueParam.GetAndAdvance(); + + if (Handle > 0 && InstData->ParameterNames.IsValidIndex(NameIndex)) + { + FName ParameterName = InstData->ParameterNames[NameIndex]; + FPersistentAudioParticleData AudioData; + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [ParameterName, Value](FAudioPlayerInterface_InstanceData*, UAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + if (AudioComponent && AudioComponent->IsPlaying()) + { + AudioComponent->SetIntParameter(ParameterName, Value); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + } +} + +void UNiagaraDataInterfaceAudioPlayer::SetParameterFloat(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam NameIndexParam(Context); + FNDIInputParam ValueParam(Context); + checkfSlow(InstData.Get(), TEXT("Audio player interface has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.NumInstances; ++i) + { + int32 Handle = AudioHandleInParam.GetAndAdvance(); + int32 NameIndex = NameIndexParam.GetAndAdvance(); + float Value = ValueParam.GetAndAdvance(); + + if (Handle > 0 && InstData->ParameterNames.IsValidIndex(NameIndex)) + { + FName ParameterName = InstData->ParameterNames[NameIndex]; + FPersistentAudioParticleData AudioData; + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [ParameterName, Value](FAudioPlayerInterface_InstanceData*, UAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + if (AudioComponent && AudioComponent->IsPlaying()) + { + AudioComponent->SetFloatParameter(ParameterName, Value); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + } +} + +void UNiagaraDataInterfaceAudioPlayer::UpdateVolume(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam VolumeParam(Context); + checkfSlow(InstData.Get(), TEXT("Audio player interface has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.NumInstances; ++i) + { + int32 Handle = AudioHandleInParam.GetAndAdvance(); + float Volume = VolumeParam.GetAndAdvance(); + + if (Handle > 0) + { + FPersistentAudioParticleData AudioData; + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [Volume](FAudioPlayerInterface_InstanceData*, UAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + if (AudioComponent && AudioComponent->IsPlaying()) + { + AudioComponent->SetVolumeMultiplier(Volume); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + } +} + +void UNiagaraDataInterfaceAudioPlayer::UpdatePitch(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam PitchParam(Context); + checkfSlow(InstData.Get(), TEXT("Audio player interface has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.NumInstances; ++i) + { + int32 Handle = AudioHandleInParam.GetAndAdvance(); + float Pitch = PitchParam.GetAndAdvance(); + + if (Handle > 0) + { + FPersistentAudioParticleData AudioData; + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [Pitch](FAudioPlayerInterface_InstanceData*, UAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + if (AudioComponent && AudioComponent->IsPlaying()) + { + AudioComponent->SetPitchMultiplier(Pitch); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + } +} + +void UNiagaraDataInterfaceAudioPlayer::UpdateLocation(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam LocationParam(Context); + checkfSlow(InstData.Get(), TEXT("Audio player interface has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.NumInstances; ++i) + { + int32 Handle = AudioHandleInParam.GetAndAdvance(); + FVector Location = LocationParam.GetAndAdvance(); + + if (Handle > 0) + { + FPersistentAudioParticleData AudioData; + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [Location](FAudioPlayerInterface_InstanceData*, UAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + if (AudioComponent && AudioComponent->IsPlaying()) + { + AudioComponent->SetWorldLocation(Location); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + } +} + +void UNiagaraDataInterfaceAudioPlayer::UpdateRotation(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam RotationParam(Context); + checkfSlow(InstData.Get(), TEXT("Audio player interface has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.NumInstances; ++i) + { + int32 Handle = AudioHandleInParam.GetAndAdvance(); + FVector Rotation = RotationParam.GetAndAdvance(); + + if (Handle > 0) + { + FPersistentAudioParticleData AudioData; + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [Rotation](FAudioPlayerInterface_InstanceData*, UAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + if (AudioComponent && AudioComponent->IsPlaying()) + { + FRotator NewRotator(Rotation.X, Rotation.Y, Rotation.Z); + AudioComponent->SetWorldRotation(NewRotator); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + } +} + +void UNiagaraDataInterfaceAudioPlayer::SetPausedState(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam PausedParam(Context); + checkfSlow(InstData.Get(), TEXT("Audio player interface has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.NumInstances; ++i) + { + int32 Handle = AudioHandleInParam.GetAndAdvance(); + bool IsPaused = PausedParam.GetAndAdvance(); + + if (Handle > 0) + { + FPersistentAudioParticleData AudioData; + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [IsPaused](FAudioPlayerInterface_InstanceData*, UAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + if (AudioComponent) + { + AudioComponent->SetPaused(IsPaused); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + } +} + +void UNiagaraDataInterfaceAudioPlayer::PlayOneShotAudio(FVectorVMContext& Context) { VectorVM::FUserPtrHandler InstData(Context); @@ -232,12 +698,97 @@ void UNiagaraDataInterfaceAudioPlayer::StoreData(FVectorVMContext& Context) FNiagaraBool Valid; if (ValidSoundData && ShouldPlay) { - Valid.SetValue(InstData->GatheredData.Enqueue(Data)); + Valid.SetValue(InstData->PlayAudioQueue.Enqueue(Data)); } *OutSample.GetDestAndAdvance() = Valid; } } +void UNiagaraDataInterfaceAudioPlayer::PlayPersistentAudio(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam PlayAudioParam(Context); + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam PositionParam(Context); + FNDIInputParam RotationParam(Context); + FNDIInputParam VolumeParam(Context); + FNDIInputParam PitchParam(Context); + FNDIInputParam StartTimeParam(Context); + FNDIInputParam FadeInParam(Context); + FNDIInputParam FadeOutParam(Context); + + FNDIOutputParam AudioHandleOutParam(Context); + + checkfSlow(InstData.Get(), TEXT("Audio player interface has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.NumInstances; ++i) + { + bool ShouldPlay = PlayAudioParam.GetAndAdvance(); + int32 Handle = AudioHandleInParam.GetAndAdvance(); + FVector Position = PositionParam.GetAndAdvance(); + FVector InRot = RotationParam.GetAndAdvance(); + FRotator Rotation = FRotator(InRot.X, InRot.Y, InRot.Z); + float Volume = VolumeParam.GetAndAdvance(); + float Pitch = PitchParam.GetAndAdvance(); + float StartTime = StartTimeParam.GetAndAdvance(); + float FadeIn = FadeInParam.GetAndAdvance(); + float FadeOut = FadeOutParam.GetAndAdvance(); + + FPersistentAudioParticleData AudioData; + if (ShouldPlay) + { + if (Handle <= 0) + { + // play a new sound + Handle = InstData->HandleCount.Increment(); + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [Handle, Position, Rotation, Volume, Pitch, StartTime, FadeIn](FAudioPlayerInterface_InstanceData* InstanceData, UAudioComponent*, FNiagaraSystemInstance* SystemInstance) + { + SCOPE_CYCLE_COUNTER(STAT_NiagaraAudioDICreateSound); + USceneComponent* NiagaraComponent = SystemInstance->GetAttachComponent(); + if (NiagaraComponent && InstanceData->SoundToPlay.IsValid()) + { + UAudioComponent* AudioComponent = UGameplayStatics::SpawnSoundAttached(InstanceData->SoundToPlay.Get(), NiagaraComponent, NAME_None, Position, Rotation, EAttachLocation::KeepWorldPosition, true, Volume, Pitch, StartTime, InstanceData->Attenuation.Get(), InstanceData->Concurrency.Get(), true); + if (FadeIn > 0.0) + { + AudioComponent->FadeIn(FadeIn, Volume, StartTime); + } + InstanceData->PersistentAudioMapping.Add(Handle, AudioComponent); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + AudioHandleOutParam.SetAndAdvance(Handle); + continue; + } + + if (Handle > 0) + { + // stop sound + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [Handle, FadeOut](FAudioPlayerInterface_InstanceData* InstanceData, UAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + SCOPE_CYCLE_COUNTER(STAT_NiagaraAudioDIStopSound); + if (AudioComponent && AudioComponent->IsPlaying()) + { + if (FadeOut > 0.0) + { + AudioComponent->FadeOut(FadeOut, 0); + } + else + { + AudioComponent->Stop(); + } + InstanceData->PersistentAudioMapping.Remove(Handle); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + AudioHandleOutParam.SetAndAdvance(0); + } +} + bool UNiagaraDataInterfaceAudioPlayer::CopyToInternal(UNiagaraDataInterface* Destination) const { if (!Super::CopyToInternal(Destination)) @@ -251,5 +802,6 @@ bool UNiagaraDataInterfaceAudioPlayer::CopyToInternal(UNiagaraDataInterface* Des OtherTyped->Concurrency = Concurrency; OtherTyped->bLimitPlaysPerTick = bLimitPlaysPerTick; OtherTyped->MaxPlaysPerTick = MaxPlaysPerTick; + OtherTyped->ParameterNames = ParameterNames; return true; } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCamera.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCamera.cpp index aabea8474b3d..04144eaa339d 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCamera.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCamera.cpp @@ -38,6 +38,8 @@ const FName UNiagaraDataInterfaceCamera::GetClipSpaceTransformsName(TEXT("GetCli const FName UNiagaraDataInterfaceCamera::GetViewSpaceTransformsName(TEXT("GetViewSpaceTransformsGPU")); const FName UNiagaraDataInterfaceCamera::GetCameraPropertiesName(TEXT("GetCameraPropertiesCPU/GPU")); const FName UNiagaraDataInterfaceCamera::GetFieldOfViewName(TEXT("GetFieldOfView")); +const FName UNiagaraDataInterfaceCamera::CalculateDistancesName(TEXT("CalculateParticleDistancesCPU")); +const FName UNiagaraDataInterfaceCamera::QueryClosestName(TEXT("QueryClosestParticlesCPU")); const FName UNiagaraDataInterfaceCamera::GetTAAJitterName(TEXT("GetTAAJitter")); UNiagaraDataInterfaceCamera::UNiagaraDataInterfaceCamera(FObjectInitializer const& ObjectInitializer) @@ -69,7 +71,17 @@ bool UNiagaraDataInterfaceCamera::PerInstanceTick(void* PerInstanceData, FNiagar { return true; } - + + // calculate the distance for each particle and sort by distance (if required) + PIData->ParticlesSortedByDistance.Empty(); + FDistanceData DistanceData; + while (PIData->DistanceSortQueue.Dequeue(DistanceData)) + { + PIData->ParticlesSortedByDistance.Add(DistanceData); + } + PIData->ParticlesSortedByDistance.StableSort([](const FDistanceData& A, const FDistanceData& B) { return A.DistanceSquared < B.DistanceSquared; }); + + // grab the current camera data UWorld* World = SystemInstance->GetWorldManager()->GetWorld(); if (World && PlayerControllerIndex < World->GetNumPlayerControllers()) { @@ -191,6 +203,33 @@ void UNiagaraDataInterfaceCamera::GetFunctions(TArray Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Right Vector World"))); OutFunctions.Add(Sig); + Sig = FNiagaraFunctionSignature(); + Sig.Name = QueryClosestName; +#if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("Niagara", "QueryClosestDescription", "This function checks the previously calculated distance of each particle and then returns true for the closest particles and false for the other ones."); +#endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bSupportsGPU = false; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Camera interface"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIDDef(), TEXT("Particle ID"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Max Valid Results"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Is Closest"))); + OutFunctions.Add(Sig); + + Sig = FNiagaraFunctionSignature(); + Sig.Name = CalculateDistancesName; +#if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("Niagara", "CalculateDistancesDescription", "This function compares the particle position against the camera position and stores the result to be queried in the next frame."); +#endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bSupportsGPU = false; + Sig.bRequiresExecPin = true; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Camera interface"))); + OutFunctions.Add(Sig); + + Sig = FNiagaraFunctionSignature(); Sig.Name = GetTAAJitterName; #if WITH_EDITORONLY_DATA @@ -222,7 +261,7 @@ bool UNiagaraDataInterfaceCamera::GetFunctionHLSL(const FNiagaraDataInterfaceGPU Out_ScreenToViewSpace = View.ScreenToViewSpace; Out_Current_TAAJitter = View.TemporalAAJitter.xy; Out_Previous_TAAJitter = View.TemporalAAJitter.zw; - } + } )"); OutHLSL += FString::Format(FormatSample, ArgsSample); return true; @@ -303,6 +342,8 @@ bool UNiagaraDataInterfaceCamera::GetFunctionHLSL(const FNiagaraDataInterfaceGPU return false; } +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCamera, GetClosestParticles); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCamera, CalculateParticleDistances); DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCamera, GetCameraFOV); DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCamera, GetCameraProperties); DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCamera, GetViewPropertiesGPU); @@ -315,6 +356,14 @@ void UNiagaraDataInterfaceCamera::GetVMExternalFunction(const FVMExternalFunctio { NDI_FUNC_BINDER(UNiagaraDataInterfaceCamera, GetCameraFOV)::Bind(this, OutFunc); } + else if (BindingInfo.Name == CalculateDistancesName) + { + NDI_FUNC_BINDER(UNiagaraDataInterfaceCamera, CalculateParticleDistances)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == QueryClosestName) + { + NDI_FUNC_BINDER(UNiagaraDataInterfaceCamera, GetClosestParticles)::Bind(this, OutFunc); + } else if (BindingInfo.Name == GetCameraPropertiesName) { NDI_FUNC_BINDER(UNiagaraDataInterfaceCamera, GetCameraProperties)::Bind(this, OutFunc); @@ -404,6 +453,57 @@ void UNiagaraDataInterfaceCamera::GetCameraProperties(FVectorVMContext& Context) } } +void UNiagaraDataInterfaceCamera::GetClosestParticles(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam ParticleIDParam(Context); + FNDIInputParam CountParam(Context); + FNDIOutputParam ResultOutParam(Context); + + int32 Count = Context.NumInstances > 0 ? CountParam.GetAndAdvance() : 0; + if (Count == 0 || InstData->ParticlesSortedByDistance.Num() == 0) + { + for (int32 i = 0; i < Context.NumInstances; ++i) + { + ResultOutParam.SetAndAdvance(false); + } + return; + } + + // grab the IDs of the closest n particles + TSet ClosestParticleIDs; + for (int32 i = 0; i < Count; ++i) + { + ClosestParticleIDs.Add(InstData->ParticlesSortedByDistance[i].ParticleID); + } + + // Assign each particles their result + for (int32 i = 0; i < Context.NumInstances; ++i) + { + FNiagaraID ParticleID = ParticleIDParam.GetAndAdvance(); + ResultOutParam.SetAndAdvance(ClosestParticleIDs.Contains(ParticleID)); + } +} + +void UNiagaraDataInterfaceCamera::CalculateParticleDistances(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam IDParam(Context); + FNDIInputParam ParticlePosParam(Context); + + FVector CameraPos = InstData->CameraLocation; + for (int32 i = 0; i < Context.NumInstances; ++i) + { + FDistanceData DistanceData; + FVector ParticlePos = ParticlePosParam.GetAndAdvance(); + DistanceData.ParticleID = IDParam.GetAndAdvance(); + DistanceData.DistanceSquared = (ParticlePos - CameraPos).SizeSquared(); + InstData->DistanceSortQueue.Enqueue(DistanceData); + } +} + ETickingGroup UNiagaraDataInterfaceCamera::CalculateTickGroup(const void* PerInstanceData) const { if (!bRequireCurrentFrameData) @@ -448,7 +548,7 @@ void UNiagaraDataInterfaceCamera::GetFeedback(UNiagaraSystem* Asset, UNiagaraCom { for (const auto& Info : Script->GetVMExecutableData().DataInterfaceInfo) { - if (Info.GetDefaultDataInterface()->GetClass() == GetClass()) + if (Info.MatchesClass(GetClass())) { for (const auto& Func : Info.RegisteredFunctions) { @@ -467,12 +567,13 @@ void UNiagaraDataInterfaceCamera::GetFeedback(UNiagaraSystem* Asset, UNiagaraCom { FNiagaraDataInterfaceFeedback CPUAccessNotAllowedWarning( LOCTEXT("CPUCameraAccessWarning", "The cpu camera is bound to a player controller and will therefore not work correctly in the Niagara viewport.\nTo correctly preview the effect, use it in the level editor or switch to a GPU emitter."), - LOCTEXT("CPUCameraAccessWarningSummary", "Camera properties accessed on CPU emitter!"), + LOCTEXT("CPUCameraAccessWarningSummary", "Camera properties cannot be previewed on CPU emitters!"), FNiagaraDataInterfaceFix()); Warnings.Add(CPUAccessNotAllowedWarning); } } + #endif // ------- Dummy implementations for CPU execution ------------ diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCollisionQuery.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCollisionQuery.cpp index 0ef63f67348c..5e058cd14be2 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCollisionQuery.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCollisionQuery.cpp @@ -46,7 +46,7 @@ bool UNiagaraDataInterfaceCollisionQuery::InitPerInstanceData(void* PerInstanceD PIData->SystemInstance = InSystemInstance; if (InSystemInstance) { - PIData->CollisionBatch.Init(InSystemInstance->GetId(), InSystemInstance->GetComponent()->GetWorld()); + PIData->CollisionBatch.Init(InSystemInstance->GetId(), InSystemInstance->GetWorld()); } return true; } @@ -210,6 +210,13 @@ bool UNiagaraDataInterfaceCollisionQuery::UpgradeFunctionCall(FNiagaraFunctionSi { bool bWasChanged = false; + // The distance field query got a new output at some point, but there exists no custom version for it + if (FunctionSignature.Name == UNiagaraDataInterfaceCollisionQuery::DistanceFieldName && FunctionSignature.Outputs.Num() == 2) + { + FunctionSignature.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("IsDistanceFieldValid"))); + bWasChanged = true; + } + // Early out for version matching if (FunctionSignature.FunctionVersion == FNiagaraCollisionDIFunctionVersion::LatestVersion) { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceColorCurve.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceColorCurve.cpp index 23a6da2003b7..f237bcc71893 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceColorCurve.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceColorCurve.cpp @@ -77,7 +77,7 @@ void UNiagaraDataInterfaceColorCurve::UpdateTimeRanges() LUTMinTime = FMath::Min(BlueCurve.GetNumKeys() > 0 ? BlueCurve.GetFirstKey().Time : LUTMinTime, LUTMinTime); LUTMinTime = FMath::Min(AlphaCurve.GetNumKeys() > 0 ? AlphaCurve.GetFirstKey().Time : LUTMinTime, LUTMinTime); - LUTMaxTime = FLT_MIN; + LUTMaxTime = -FLT_MAX; LUTMaxTime = FMath::Max(RedCurve.GetNumKeys() > 0 ? RedCurve.GetLastKey().Time : LUTMaxTime, LUTMaxTime); LUTMaxTime = FMath::Max(GreenCurve.GetNumKeys() > 0 ? GreenCurve.GetLastKey().Time : LUTMaxTime, LUTMaxTime); LUTMaxTime = FMath::Max(BlueCurve.GetNumKeys() > 0 ? BlueCurve.GetLastKey().Time : LUTMaxTime, LUTMaxTime); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceGrid2DCollection.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceGrid2DCollection.cpp index 9907b3a39711..882307a84807 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceGrid2DCollection.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceGrid2DCollection.cpp @@ -25,6 +25,7 @@ const FName UNiagaraDataInterfaceGrid2DCollection::GetValueFunctionName("GetGrid const FName UNiagaraDataInterfaceGrid2DCollection::SampleGridFunctionName("SampleGrid"); +FNiagaraVariableBase UNiagaraDataInterfaceGrid2DCollection::ExposedRTVar; /*--------------------------------------------------------------------------------------------------------------------------*/ struct FNiagaraDataInterfaceParametersCS_Grid2DCollection : public FNiagaraDataInterfaceParametersCS @@ -54,7 +55,7 @@ public: FRHIComputeShader* ComputeShaderRHI = Context.Shader.GetComputeShader(); FNiagaraDataInterfaceProxyGrid2DCollectionProxy* VFDI = static_cast(Context.DataInterface); - FGrid2DCollectionRWInstanceData_RenderThread* ProxyData = VFDI->SystemInstancesToProxyData_RT.Find(Context.SystemInstance); + FGrid2DCollectionRWInstanceData_RenderThread* ProxyData = VFDI->SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); check(ProxyData); int NumCellsTmp[2]; @@ -149,6 +150,7 @@ void UNiagaraDataInterfaceGrid2DCollection::PostInitProperties() if (HasAnyFlags(RF_ClassDefaultObject)) { FNiagaraTypeRegistry::Register(FNiagaraTypeDefinition(GetClass()), /*bCanBeParameter*/ true, /*bCanBePayload*/ false, /*bIsUserDefined*/ false); + UNiagaraDataInterfaceGrid2DCollection::ExposedRTVar = FNiagaraVariableBase(FNiagaraTypeDefinition(UTexture::StaticClass()), TEXT("RenderTarget")); } } @@ -210,6 +212,7 @@ void UNiagaraDataInterfaceGrid2DCollection::GetFunctions(TArray(Other); - return OtherTyped != nullptr && OtherTyped->RenderTargetUserParameter == RenderTargetUserParameter; + return OtherTyped != nullptr && OtherTyped->RenderTargetUserParameter == RenderTargetUserParameter && OtherTyped->bCreateRenderTarget == bCreateRenderTarget; } void UNiagaraDataInterfaceGrid2DCollection::GetParameterDefinitionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) @@ -361,6 +371,7 @@ bool UNiagaraDataInterfaceGrid2DCollection::CopyToInternal(UNiagaraDataInterface UNiagaraDataInterfaceGrid2DCollection* OtherTyped = CastChecked(Destination); OtherTyped->RenderTargetUserParameter = RenderTargetUserParameter; + OtherTyped->bCreateRenderTarget = bCreateRenderTarget; return true; } @@ -422,36 +433,48 @@ bool UNiagaraDataInterfaceGrid2DCollection::InitPerInstanceData(void* PerInstanc FTextureResource* RT_Resource = NULL; + InstanceData->TargetTexture = nullptr; + if (UObject* UserParamObject = InstanceData->RTUserParamBinding.Init(SystemInstance->GetInstanceParameters(), RenderTargetUserParameter.Parameter)) { - if (UTextureRenderTarget2D* TargetTexture = Cast(UserParamObject)) - { - // resize RT to match what we need for the output - TargetTexture->RenderTargetFormat = RTF_R32f; - TargetTexture->ClearColor = FLinearColor(0, 0, 0, 0); - TargetTexture->bAutoGenerateMips = false; - TargetTexture->InitAutoFormat(NumCellsX * NumTilesX, NumCellsY * NumTilesY); - TargetTexture->UpdateResourceImmediate(true); - - if (TargetTexture->Resource) - { - RT_Resource = TargetTexture->Resource; - } - } - else + InstanceData->TargetTexture = Cast(UserParamObject); + if (!InstanceData->TargetTexture) { UE_LOG(LogNiagara, Error, TEXT("Only UTextureRenderTarget2D are valid on %s"), *FNiagaraUtilities::SystemInstanceIDToString(SystemInstance->GetId())); } } + if (!InstanceData->TargetTexture && bCreateRenderTarget != 0) + { + InstanceData->TargetTexture = NewObject(this); + FNiagaraSystemInstanceID SysID = SystemInstance->GetId(); + ManagedRenderTargets.Add(SysID) = InstanceData->TargetTexture; + } + + if (InstanceData->TargetTexture) + { + // resize RT to match what we need for the output + InstanceData->TargetTexture->RenderTargetFormat = RTF_R32f; + InstanceData->TargetTexture->ClearColor = FLinearColor(.5, 0, 0, 0); + InstanceData->TargetTexture->bAutoGenerateMips = false; + InstanceData->TargetTexture->InitAutoFormat(NumCellsX * NumTilesX, NumCellsY * NumTilesY); + InstanceData->TargetTexture->UpdateResourceImmediate(true); + + if (InstanceData->TargetTexture->Resource) + { + RT_Resource = InstanceData->TargetTexture->Resource; + } + } + // Push Updates to Proxy. FNiagaraDataInterfaceProxyGrid2DCollectionProxy* RT_Proxy = GetProxyAs(); ENQUEUE_RENDER_COMMAND(FUpdateData)( - [RT_Resource, RT_Proxy, InstanceID = SystemInstance->GetId(), RT_InstanceData=*InstanceData, RT_OutputShaderStages=OutputShaderStages, RT_IterationShaderStages= IterationShaderStages](FRHICommandListImmediate& RHICmdList) + [GridColl = this, TexPtr = InstanceData->TargetTexture, RT_Resource, RT_Proxy, InstanceID = SystemInstance->GetId(), RT_InstanceData=*InstanceData, RT_OutputShaderStages=OutputShaderStages, RT_IterationShaderStages= IterationShaderStages](FRHICommandListImmediate& RHICmdList) { check(!RT_Proxy->SystemInstancesToProxyData_RT.Contains(InstanceID)); FGrid2DCollectionRWInstanceData_RenderThread* TargetData = &RT_Proxy->SystemInstancesToProxyData_RT.Add(InstanceID); + TargetData->DebugTargetTexture = TexPtr; TargetData->NumCells = RT_InstanceData.NumCells; TargetData->NumTiles = RT_InstanceData.NumTiles; TargetData->CellSize = RT_InstanceData.CellSize; @@ -493,6 +516,10 @@ void UNiagaraDataInterfaceGrid2DCollection::DestroyPerInstanceData(void* PerInst RT_Proxy->SystemInstancesToProxyData_RT.Remove(InstanceID); } ); + + // Make sure to clear out the reference to the render target if we created one. + FNiagaraSystemInstanceID SysId = SystemInstance->GetId(); + ManagedRenderTargets.Remove(SysId); } bool UNiagaraDataInterfaceGrid2DCollection::PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) @@ -504,28 +531,9 @@ bool UNiagaraDataInterfaceGrid2DCollection::PerInstanceTick(void* PerInstanceDat bool NeedsReset = false; if (UObject* UserParamObject = InstanceData->RTUserParamBinding.Init(SystemInstance->GetInstanceParameters(), RenderTargetUserParameter.Parameter)) { - if (UTextureRenderTarget2D* TargetTexture = Cast(UserParamObject)) + if (UTextureRenderTarget2D* LocalTargetTexture = Cast(UserParamObject)) { - int32 RTSizeX = InstanceData->NumCells.X * InstanceData->NumTiles.X; - int32 RTSizeY = InstanceData->NumCells.Y * InstanceData->NumTiles.Y; - - if (TargetTexture->SizeX != RTSizeX || TargetTexture->SizeY != RTSizeY || TargetTexture->RenderTargetFormat != RTF_R32f) - { - // resize RT to match what we need for the output - TargetTexture->RenderTargetFormat = RTF_R32f; - TargetTexture->ClearColor = FLinearColor(0,0,0,0); - TargetTexture->bAutoGenerateMips = false; - TargetTexture->InitAutoFormat(RTSizeX, RTSizeY); - TargetTexture->UpdateResourceImmediate(true); - //TargetTexture->InitCustomFormat(InstanceData->NumCells.X * InstanceData->NumTiles.X, InstanceData->NumCells.Y * InstanceData->NumTiles.Y, PF_R32_FLOAT, false); - - if (TargetTexture->Resource) - { - NeedsReset = true; - } - } - - RT_Resource = TargetTexture->Resource; + InstanceData->TargetTexture = LocalTargetTexture; } else { @@ -533,12 +541,35 @@ bool UNiagaraDataInterfaceGrid2DCollection::PerInstanceTick(void* PerInstanceDat } } + if (InstanceData->TargetTexture) + { + int32 RTSizeX = InstanceData->NumCells.X * InstanceData->NumTiles.X; + int32 RTSizeY = InstanceData->NumCells.Y * InstanceData->NumTiles.Y; + + if (InstanceData->TargetTexture->SizeX != RTSizeX || InstanceData->TargetTexture->SizeY != RTSizeY || InstanceData->TargetTexture->RenderTargetFormat != RTF_R32f) + { + // resize RT to match what we need for the output + InstanceData->TargetTexture->RenderTargetFormat = RTF_R32f; + InstanceData->TargetTexture->ClearColor = FLinearColor(0.5,0,0,0); + InstanceData->TargetTexture->bAutoGenerateMips = false; + InstanceData->TargetTexture->InitAutoFormat(RTSizeX, RTSizeY); + InstanceData->TargetTexture->UpdateResourceImmediate(true); + //TargetTexture->InitCustomFormat(InstanceData->NumCells.X * InstanceData->NumTiles.X, InstanceData->NumCells.Y * InstanceData->NumTiles.Y, PF_R32_FLOAT, false); + + if (InstanceData->TargetTexture->Resource) + { + NeedsReset = true; + } + } + RT_Resource = InstanceData->TargetTexture->Resource; + } + FNiagaraDataInterfaceProxyGrid2DCollectionProxy* RT_Proxy = GetProxyAs(); ENQUEUE_RENDER_COMMAND(FUpdateData)( - [RT_Resource, RT_Proxy, InstanceID = SystemInstance->GetId()](FRHICommandListImmediate& RHICmdList) + [GridColl = this,TexPtr = InstanceData->TargetTexture, RT_Resource, RT_Proxy, InstanceID = SystemInstance->GetId()](FRHICommandListImmediate& RHICmdList) { FGrid2DCollectionRWInstanceData_RenderThread* TargetData = RT_Proxy->SystemInstancesToProxyData_RT.Find(InstanceID); - + TargetData->DebugTargetTexture = TexPtr; if (RT_Resource && RT_Resource->TextureRHI.IsValid()) { TargetData->RenderTargetToCopyTo = RT_Resource->TextureRHI; @@ -553,6 +584,23 @@ bool UNiagaraDataInterfaceGrid2DCollection::PerInstanceTick(void* PerInstanceDat return NeedsReset; } +void UNiagaraDataInterfaceGrid2DCollection::GetExposedVariables(TArray& OutVariables) const +{ + OutVariables.Emplace(ExposedRTVar); +} + +bool UNiagaraDataInterfaceGrid2DCollection::GetExposedVariableValue(const FNiagaraVariableBase& InVariable, void* InPerInstanceData, FNiagaraSystemInstance* InSystemInstance, void* OutData) const +{ + FGrid2DCollectionRWInstanceData_GameThread* InstanceData = static_cast(InPerInstanceData); + if (InVariable.IsValid() && InVariable == ExposedRTVar && InstanceData && InstanceData->TargetTexture) + { + UObject** Var = (UObject**)OutData; + *Var = InstanceData->TargetTexture; + return true; + } + return false; +} + UFUNCTION(BlueprintCallable, Category = Niagara) bool UNiagaraDataInterfaceGrid2DCollection::FillTexture2D(const UNiagaraComponent *Component, UTextureRenderTarget2D *Dest, int AttributeIndex) { @@ -756,6 +804,19 @@ void UNiagaraDataInterfaceGrid2DCollection::GetCellSize(FVectorVMContext& Contex } } +void UNiagaraDataInterfaceGrid2DCollection::GetNumCells(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + VectorVM::FExternalFuncRegisterHandler OutNumCellsX(Context); + VectorVM::FExternalFuncRegisterHandler OutNumCellsY(Context); + + for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) + { + *OutNumCellsX.GetDestAndAdvance() = InstData->NumCells.X; + *OutNumCellsY.GetDestAndAdvance() = InstData->NumCells.Y; + } +} + void FGrid2DCollectionRWInstanceData_RenderThread::BeginSimulate() { for (TUniquePtr& Buffer : Buffers) @@ -781,12 +842,12 @@ void FGrid2DCollectionRWInstanceData_RenderThread::EndSimulate() DestinationData = nullptr; } -void FNiagaraDataInterfaceProxyGrid2DCollectionProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNiagaraDataInterfaceProxyGrid2DCollectionProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) { // #todo(dmp): Context doesnt need to specify if a stage is output or not since we moved pre/post stage to the DI itself. Not sure which design is better for the future if (Context.IsOutputStage) { - FGrid2DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstance); + FGrid2DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); ProxyData->BeginSimulate(); @@ -804,18 +865,18 @@ void FNiagaraDataInterfaceProxyGrid2DCollectionProxy::PreStage(FRHICommandList& } } -void FNiagaraDataInterfaceProxyGrid2DCollectionProxy::PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNiagaraDataInterfaceProxyGrid2DCollectionProxy::PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) { if (Context.IsOutputStage) { - FGrid2DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstance); + FGrid2DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); ProxyData->EndSimulate(); } } -void FNiagaraDataInterfaceProxyGrid2DCollectionProxy::PostSimulate(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNiagaraDataInterfaceProxyGrid2DCollectionProxy::PostSimulate(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) { - FGrid2DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstance); + FGrid2DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); if (ProxyData->RenderTargetToCopyTo != nullptr && ProxyData->CurrentData != nullptr && ProxyData->CurrentData->GridBuffer.Buffer != nullptr) { @@ -829,9 +890,9 @@ void FNiagaraDataInterfaceProxyGrid2DCollectionProxy::PostSimulate(FRHICommandLi } } -void FNiagaraDataInterfaceProxyGrid2DCollectionProxy::ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNiagaraDataInterfaceProxyGrid2DCollectionProxy::ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) { - FGrid2DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstance); + FGrid2DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); if (!ProxyData) { return; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceGrid2DCollectionReader.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceGrid2DCollectionReader.cpp index 83196f59d307..48f450abc737 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceGrid2DCollectionReader.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceGrid2DCollectionReader.cpp @@ -54,13 +54,13 @@ public: // #todo(dmp): read this from instance data and correct proxy from the reader's proxy FNiagaraDataInterfaceProxyGrid2DCollectionReaderProxy* ReaderDIProxy = static_cast(Context.DataInterface); - FGrid2DCollectionReaderInstanceData_RenderThread* ReaderProxyData = ReaderDIProxy->SystemInstancesToProxyData_RT.Find(Context.SystemInstance); + FGrid2DCollectionReaderInstanceData_RenderThread* ReaderProxyData = ReaderDIProxy->SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); FGrid2DCollectionRWInstanceData_RenderThread* Grid2DProxyData = nullptr; if (ReaderProxyData && ReaderProxyData->GPUContext && ReaderProxyData->ProxyToUse) { - Grid2DProxyData = ReaderProxyData->ProxyToUse->SystemInstancesToProxyData_RT.Find(Context.SystemInstance); + Grid2DProxyData = ReaderProxyData->ProxyToUse->SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); } // no proxy data so fill with dummy values diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceGrid3DCollection.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceGrid3DCollection.cpp index 242d71a49dde..8185456cbb61 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceGrid3DCollection.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceGrid3DCollection.cpp @@ -4,11 +4,11 @@ #include "ShaderParameterUtils.h" #include "ClearQuad.h" #include "TextureResource.h" -#include "Engine/Texture2D.h" #include "NiagaraEmitterInstanceBatcher.h" #include "NiagaraSystemInstance.h" #include "NiagaraRenderer.h" #include "Engine/VolumeTexture.h" +#include "Engine/TextureRenderTargetVolume.h" #define LOCTEXT_NAMESPACE "NiagaraDataInterfaceGrid3DCollection" @@ -55,7 +55,7 @@ public: FRHIComputeShader* ComputeShaderRHI = Context.Shader.GetComputeShader(); FNiagaraDataInterfaceProxyGrid3DCollectionProxy* VFDI = static_cast(Context.DataInterface); - FGrid3DCollectionRWInstanceData_RenderThread* ProxyData = VFDI->SystemInstancesToProxyData_RT.Find(Context.SystemInstance); + FGrid3DCollectionRWInstanceData_RenderThread* ProxyData = VFDI->SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); check(ProxyData); int NumCellsTmp[3]; @@ -89,7 +89,7 @@ public: { InputGridBuffer = FNiagaraRenderer::GetDummyTextureReadBuffer2D(); } - SetSRVParameter(RHICmdList, Context.Shader.GetComputeShader(), GridParam, InputGridBuffer); + SetSRVParameter(RHICmdList, ComputeShaderRHI, GridParam, InputGridBuffer); } if ( OutputGridParam.IsUAVBound() ) @@ -112,7 +112,8 @@ public: { if (OutputGridParam.IsBound()) { - OutputGridParam.UnsetUAV(RHICmdList, Context.Shader.GetComputeShader()); + FRHIComputeShader* ComputeShaderRHI = Context.Shader.GetComputeShader(); + OutputGridParam.UnsetUAV(RHICmdList, ComputeShaderRHI); } } @@ -137,11 +138,12 @@ IMPLEMENT_NIAGARA_DI_PARAMETER(UNiagaraDataInterfaceGrid3DCollection, FNiagaraDa UNiagaraDataInterfaceGrid3DCollection::UNiagaraDataInterfaceGrid3DCollection(FObjectInitializer const& ObjectInitializer) : Super(ObjectInitializer) , NumAttributes(1) - { Proxy.Reset(new FNiagaraDataInterfaceProxyGrid3DCollectionProxy()); -} + FNiagaraTypeDefinition Def(UObject::StaticClass()); + RenderTargetUserParameter.Parameter.SetType(Def); +} void UNiagaraDataInterfaceGrid3DCollection::PostInitProperties() { @@ -245,7 +247,9 @@ bool UNiagaraDataInterfaceGrid3DCollection::Equals(const UNiagaraDataInterface* } const UNiagaraDataInterfaceGrid3DCollection* OtherTyped = CastChecked(Other); - return OtherTyped != nullptr && OtherTyped->NumAttributes == NumAttributes; + return OtherTyped != nullptr && + OtherTyped->NumAttributes == NumAttributes && + OtherTyped->RenderTargetUserParameter == RenderTargetUserParameter; } void UNiagaraDataInterfaceGrid3DCollection::GetParameterDefinitionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) @@ -352,6 +356,7 @@ bool UNiagaraDataInterfaceGrid3DCollection::CopyToInternal(UNiagaraDataInterface UNiagaraDataInterfaceGrid3DCollection* OtherTyped = CastChecked(Destination); OtherTyped->NumAttributes = NumAttributes; + OtherTyped->RenderTargetUserParameter = RenderTargetUserParameter; return true; } @@ -463,10 +468,33 @@ bool UNiagaraDataInterfaceGrid3DCollection::InitPerInstanceData(void* PerInstanc check(InstanceData->NumTiles.Y > 0); check(InstanceData->NumTiles.Z > 0); + FTextureResource* RT_Resource = nullptr; + + if (UObject* UserParamObject = InstanceData->RTUserParamBinding.Init(SystemInstance->GetInstanceParameters(), RenderTargetUserParameter.Parameter)) + { + if (UTextureRenderTargetVolume* TargetTexture = Cast(UserParamObject)) + { + // resize RT to match what we need for the output + TargetTexture->OverrideFormat = PF_R32_FLOAT; + TargetTexture->ClearColor = FLinearColor(0, 0, 0, 0); + TargetTexture->InitAutoFormat(InstanceData->NumCells.X * InstanceData->NumTiles.X, InstanceData->NumCells.Y * InstanceData->NumTiles.Y, InstanceData->NumCells.Z * InstanceData->NumTiles.Z); + TargetTexture->UpdateResourceImmediate(true); + + if (TargetTexture->Resource) + { + RT_Resource = TargetTexture->Resource; + } + } + else + { + UE_LOG(LogNiagara, Error, TEXT("Only UTextureRenderTarget2D are valid on %s"), *FNiagaraUtilities::SystemInstanceIDToString(SystemInstance->GetId())); + } + } + // Push Updates to Proxy. FNiagaraDataInterfaceProxyGrid3DCollectionProxy* RT_Proxy = GetProxyAs(); ENQUEUE_RENDER_COMMAND(FUpdateData)( - [RT_Proxy, InstanceID = SystemInstance->GetId(), RT_InstanceData=*InstanceData, RT_OutputShaderStages=OutputShaderStages, RT_IterationShaderStages= IterationShaderStages](FRHICommandListImmediate& RHICmdList) + [RT_Proxy, RT_Resource, InstanceID = SystemInstance->GetId(), RT_InstanceData=*InstanceData, RT_OutputShaderStages=OutputShaderStages, RT_IterationShaderStages= IterationShaderStages](FRHICommandListImmediate& RHICmdList) { check(!RT_Proxy->SystemInstancesToProxyData_RT.Contains(InstanceID)); FGrid3DCollectionRWInstanceData_RenderThread* TargetData = &RT_Proxy->SystemInstancesToProxyData_RT.Add(InstanceID); @@ -480,6 +508,15 @@ bool UNiagaraDataInterfaceGrid3DCollection::InitPerInstanceData(void* PerInstanc RT_Proxy->IterationSimulationStages_DEPRECATED = RT_IterationShaderStages; RT_Proxy->SetElementCount(TargetData->NumCells.X * TargetData->NumCells.Y * TargetData->NumCells.Z); + + if (RT_Resource && RT_Resource->TextureRHI.IsValid()) + { + TargetData->RenderTargetToCopyTo = RT_Resource->TextureRHI; + } + else + { + TargetData->RenderTargetToCopyTo = nullptr; + } }); return true; @@ -505,6 +542,63 @@ void UNiagaraDataInterfaceGrid3DCollection::DestroyPerInstanceData(void* PerInst } +bool UNiagaraDataInterfaceGrid3DCollection::PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) +{ + FGrid3DCollectionRWInstanceData_GameThread* InstanceData = SystemInstancesToProxyData_GT.FindRef(SystemInstance->GetId()); + + FTextureResource* RT_Resource = nullptr; + + bool NeedsReset = false; + if (UObject* UserParamObject = InstanceData->RTUserParamBinding.Init(SystemInstance->GetInstanceParameters(), RenderTargetUserParameter.Parameter)) + { + if (UTextureRenderTargetVolume* TargetTexture = Cast(UserParamObject)) + { + int32 RTSizeX = InstanceData->NumCells.X * InstanceData->NumTiles.X; + int32 RTSizeY = InstanceData->NumCells.Y * InstanceData->NumTiles.Y; + int32 RTSizeZ = InstanceData->NumCells.Z * InstanceData->NumTiles.Z; + + if (TargetTexture->SizeX != RTSizeX || TargetTexture->SizeY != RTSizeY || TargetTexture->SizeZ != RTSizeZ) + { + // resize RT to match what we need for the output + TargetTexture->OverrideFormat = PF_R32_FLOAT; + TargetTexture->ClearColor = FLinearColor(0, 0, 0, 0); + TargetTexture->InitAutoFormat(RTSizeX, RTSizeY, RTSizeZ); + TargetTexture->UpdateResourceImmediate(true); + + if (TargetTexture->Resource) + { + NeedsReset = true; + } + } + + RT_Resource = TargetTexture->Resource; + } + else + { + UE_LOG(LogNiagara, Error, TEXT("Only UTextureRenderTarget2D are valid on %s"), *FNiagaraUtilities::SystemInstanceIDToString(SystemInstance->GetId())); + } + } + + FNiagaraDataInterfaceProxyGrid3DCollectionProxy* RT_Proxy = GetProxyAs(); + ENQUEUE_RENDER_COMMAND(FUpdateData)( + [RT_Resource, RT_Proxy, InstanceID = SystemInstance->GetId()](FRHICommandListImmediate& RHICmdList) + { + FGrid3DCollectionRWInstanceData_RenderThread* TargetData = RT_Proxy->SystemInstancesToProxyData_RT.Find(InstanceID); + + if (RT_Resource && RT_Resource->TextureRHI.IsValid()) + { + TargetData->RenderTargetToCopyTo = RT_Resource->TextureRHI; + } + else + { + TargetData->RenderTargetToCopyTo = nullptr; + } + + }); + + return NeedsReset; +} + UFUNCTION(BlueprintCallable, Category = Niagara) bool UNiagaraDataInterfaceGrid3DCollection::FillVolumeTexture(const UNiagaraComponent *Component, UVolumeTexture*Dest, int AttributeIndex) { @@ -761,12 +855,12 @@ void FGrid3DCollectionRWInstanceData_RenderThread::EndSimulate() DestinationData = nullptr; } -void FNiagaraDataInterfaceProxyGrid3DCollectionProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNiagaraDataInterfaceProxyGrid3DCollectionProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) { // #todo(dmp): Context doesnt need to specify if a stage is output or not since we moved pre/post stage to the DI itself. Not sure which design is better for the future if (Context.IsOutputStage) { - FGrid3DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstance); + FGrid3DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); ProxyData->BeginSimulate(); @@ -790,19 +884,35 @@ void FNiagaraDataInterfaceProxyGrid3DCollectionProxy::PreStage(FRHICommandList& } } -void FNiagaraDataInterfaceProxyGrid3DCollectionProxy::PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNiagaraDataInterfaceProxyGrid3DCollectionProxy::PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) { if (Context.IsOutputStage) { - FGrid3DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstance); + FGrid3DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); ProxyData->EndSimulate(); } } -void FNiagaraDataInterfaceProxyGrid3DCollectionProxy::ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNiagaraDataInterfaceProxyGrid3DCollectionProxy::PostSimulate(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) +{ + FGrid3DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); + + if (ProxyData->RenderTargetToCopyTo != nullptr && ProxyData->CurrentData != nullptr && ProxyData->CurrentData->GridBuffer.Buffer != nullptr) + { + RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ProxyData->CurrentData->GridBuffer.Buffer); + RHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, ProxyData->RenderTargetToCopyTo); + + FRHICopyTextureInfo CopyInfo; + RHICmdList.CopyTexture(ProxyData->CurrentData->GridBuffer.Buffer, ProxyData->RenderTargetToCopyTo, CopyInfo); + + RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ProxyData->RenderTargetToCopyTo); + } +} + +void FNiagaraDataInterfaceProxyGrid3DCollectionProxy::ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) { - FGrid3DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstance); + FGrid3DCollectionRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); if (!ProxyData) { return; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceLandscape.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceLandscape.cpp index dd39151b14c6..9d9fe0a8b003 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceLandscape.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceLandscape.cpp @@ -296,9 +296,9 @@ public: FNiagaraDataInterfaceProxyLandscape* RT_Proxy = static_cast(Context.DataInterface); - if (RT_Proxy && RT_Proxy->SystemInstancesToProxyData_RT.Find(Context.SystemInstance)) + if (RT_Proxy && RT_Proxy->SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID)) { - FNDILandscapeData_RenderThread* ProxyData = RT_Proxy->SystemInstancesToProxyData_RT.Find(Context.SystemInstance); + FNDILandscapeData_RenderThread* ProxyData = RT_Proxy->SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); if (ProxyData->LandscapeTextureBuffer) { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceNeighborGrid3D.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceNeighborGrid3D.cpp index 608c81664aa2..12e7e0a34bc4 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceNeighborGrid3D.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceNeighborGrid3D.cpp @@ -55,7 +55,7 @@ public: FRHIComputeShader* ComputeShaderRHI = Context.Shader.GetComputeShader(); FNiagaraDataInterfaceProxyNeighborGrid3D* VFDI = static_cast(Context.DataInterface); - NeighborGrid3DRWInstanceData* ProxyData = VFDI->SystemInstancesToProxyData.Find(Context.SystemInstance); + NeighborGrid3DRWInstanceData* ProxyData = VFDI->SystemInstancesToProxyData.Find(Context.SystemInstanceID); float CellSizeTmp[3]; if (!ProxyData) @@ -65,8 +65,8 @@ public: SetShaderValue(RHICmdList, ComputeShaderRHI, CellSizeParam, CellSizeTmp); SetShaderValue(RHICmdList, ComputeShaderRHI, MaxNeighborsPerCellParam, 0); SetShaderValue(RHICmdList, ComputeShaderRHI, WorldBBoxSizeParam, FVector(0.0f, 0.0f, 0.0f)); - SetSRVParameter(RHICmdList, Context.Shader.GetComputeShader(), ParticleNeighborsGridParam, FNiagaraRenderer::GetDummyIntBuffer()); - SetSRVParameter(RHICmdList, Context.Shader.GetComputeShader(), ParticleNeighborCountGridParam, FNiagaraRenderer::GetDummyIntBuffer()); + SetSRVParameter(RHICmdList, ComputeShaderRHI, ParticleNeighborsGridParam, FNiagaraRenderer::GetDummyIntBuffer()); + SetSRVParameter(RHICmdList, ComputeShaderRHI, ParticleNeighborCountGridParam, FNiagaraRenderer::GetDummyIntBuffer()); if (OutputParticleNeighborsGridParam.IsUAVBound()) { RHICmdList.SetUAVParameter(ComputeShaderRHI, OutputParticleNeighborsGridParam.GetUAVIndex(), Context.Batcher->GetEmptyRWBufferFromPool(RHICmdList, PF_R32_SINT)); @@ -96,13 +96,13 @@ public: if (ParticleNeighborsGridParam.IsBound()) { RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, EResourceTransitionPipeline::EComputeToCompute, ProxyData->NeighborhoodBuffer.UAV); - SetSRVParameter(RHICmdList, Context.Shader.GetComputeShader(), ParticleNeighborsGridParam, ProxyData->NeighborhoodBuffer.SRV); + SetSRVParameter(RHICmdList, ComputeShaderRHI, ParticleNeighborsGridParam, ProxyData->NeighborhoodBuffer.SRV); } if (ParticleNeighborCountGridParam.IsBound()) { RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, EResourceTransitionPipeline::EComputeToCompute, ProxyData->NeighborhoodCountBuffer.UAV); - SetSRVParameter(RHICmdList, Context.Shader.GetComputeShader(), ParticleNeighborCountGridParam, ProxyData->NeighborhoodCountBuffer.SRV); + SetSRVParameter(RHICmdList, ComputeShaderRHI, ParticleNeighborCountGridParam, ProxyData->NeighborhoodCountBuffer.SRV); } if (OutputParticleNeighborsGridParam.IsUAVBound()) @@ -117,19 +117,19 @@ public: } else { - SetSRVParameter(RHICmdList, Context.Shader.GetComputeShader(), ParticleNeighborsGridParam, FNiagaraRenderer::GetDummyIntBuffer()); - SetSRVParameter(RHICmdList, Context.Shader.GetComputeShader(), ParticleNeighborCountGridParam, FNiagaraRenderer::GetDummyIntBuffer()); + SetSRVParameter(RHICmdList, ComputeShaderRHI, ParticleNeighborsGridParam, FNiagaraRenderer::GetDummyIntBuffer()); + SetSRVParameter(RHICmdList, ComputeShaderRHI, ParticleNeighborCountGridParam, FNiagaraRenderer::GetDummyIntBuffer()); if (OutputParticleNeighborsGridParam.IsBound()) { RHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, EResourceTransitionPipeline::EComputeToCompute, ProxyData->NeighborhoodBuffer.UAV); - OutputParticleNeighborsGridParam.SetBuffer(RHICmdList, Context.Shader.GetComputeShader(), ProxyData->NeighborhoodBuffer); + OutputParticleNeighborsGridParam.SetBuffer(RHICmdList, ComputeShaderRHI, ProxyData->NeighborhoodBuffer); } if (OutputParticleNeighborCountGridParam.IsBound()) { RHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, EResourceTransitionPipeline::EComputeToCompute, ProxyData->NeighborhoodCountBuffer.UAV); - OutputParticleNeighborCountGridParam.SetBuffer(RHICmdList, Context.Shader.GetComputeShader(), ProxyData->NeighborhoodCountBuffer); + OutputParticleNeighborCountGridParam.SetBuffer(RHICmdList, ComputeShaderRHI, ProxyData->NeighborhoodCountBuffer); } } // Note: There is a flush in PreEditChange to make sure everything is synced up at this point @@ -553,11 +553,11 @@ void UNiagaraDataInterfaceNeighborGrid3D::DestroyPerInstanceData(void* PerInstan ); } -void FNiagaraDataInterfaceProxyNeighborGrid3D::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) +void FNiagaraDataInterfaceProxyNeighborGrid3D::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) { if (Context.IsOutputStage) { - NeighborGrid3DRWInstanceData* ProxyData = SystemInstancesToProxyData.Find(Context.SystemInstance); + NeighborGrid3DRWInstanceData* ProxyData = SystemInstancesToProxyData.Find(Context.SystemInstanceID); SCOPED_DRAW_EVENT(RHICmdList, NiagaraNeighborGrid3DClearNeighborInfo); ERHIFeatureLevel::Type FeatureLevel = Context.Batcher->GetFeatureLevel(); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceParticleRead.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceParticleRead.cpp index 1241e984df10..d4433dac6456 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceParticleRead.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceParticleRead.cpp @@ -416,7 +416,7 @@ struct FNiagaraDataInterfaceParametersCS_ParticleRead : public FNiagaraDataInter FNiagaraDataInterfaceProxyParticleRead* Proxy = static_cast(Context.DataInterface); check(Proxy); - FNDIParticleRead_RenderInstanceData* InstanceData = Proxy->GetRenderDataForSystem(Context.SystemInstance); + FNDIParticleRead_RenderInstanceData* InstanceData = Proxy->GetRenderDataForSystem(Context.SystemInstanceID); if (!InstanceData) { SetErrorParams(RHICmdList, ComputeShader, false); @@ -1120,169 +1120,115 @@ void UNiagaraDataInterfaceParticleRead::GetVMExternalFunction(const FVMExternalF // if (BindingInfo.Name == GetIntByIDFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetIntDef())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadInt)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadInt)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetIntDef()); } else if (BindingInfo.Name == GetBoolByIDFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetBoolDef())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadBool)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadBool)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetBoolDef()); } else if (BindingInfo.Name == GetFloatByIDFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetFloatDef()) - || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfDef())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadFloat)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadFloat)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetFloatDef()) + || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfDef())); } else if (BindingInfo.Name == GetVec2ByIDFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetVec2Def()) - || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec2Def())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadVector2)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadVector2)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetVec2Def()) + || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec2Def())); } else if (BindingInfo.Name == GetVec3ByIDFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetVec3Def()) - || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec3Def())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadVector3)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadVector3)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetVec3Def()) + || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec3Def())); } else if (BindingInfo.Name == GetVec4ByIDFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetVec4Def()) - || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec4Def())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadVector4)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadVector4)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetVec4Def()) + || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec4Def())); } else if (BindingInfo.Name == GetColorByIDFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetColorDef()) - || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec4Def())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadColor)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadColor)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetColorDef()) + || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec4Def())); } else if (BindingInfo.Name == GetQuatByIDFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetQuatDef()) - || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec4Def())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadQuat)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadQuat)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetQuatDef()) + || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec4Def())); } else if (BindingInfo.Name == GetIDByIDFunctionName) { + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadID)::Bind(this, OutFunc, AttributeToRead); FNiagaraVariable VariableToRead(FNiagaraTypeDefinition::GetIDDef(), AttributeToRead); - if (PIData->EmitterInstance->GetData().GetVariables().Find(VariableToRead) != INDEX_NONE) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadID)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + bBindSuccessful = PIData->EmitterInstance->GetData().GetVariables().Find(VariableToRead) != INDEX_NONE; } // // Get attribute by index // else if (BindingInfo.Name == GetIntByIndexFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetIntDef())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadIntByIndex)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadIntByIndex)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetIntDef()); } else if (BindingInfo.Name == GetBoolByIndexFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetBoolDef())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadBoolByIndex)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadBoolByIndex)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetBoolDef()); } else if (BindingInfo.Name == GetFloatByIndexFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetFloatDef()) - || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfDef())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadFloatByIndex)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadFloatByIndex)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetFloatDef()) + || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfDef())); } else if (BindingInfo.Name == GetVec2ByIndexFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetVec2Def()) - || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec2Def())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadVector2ByIndex)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadVector2ByIndex)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetVec2Def()) + || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec2Def())); } else if (BindingInfo.Name == GetVec3ByIndexFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetVec3Def()) - || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec3Def())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadVector3ByIndex)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadVector3ByIndex)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetVec3Def()) + || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec3Def())); } else if (BindingInfo.Name == GetVec4ByIndexFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetVec4Def()) - || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec4Def())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadVector4ByIndex)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadVector4ByIndex)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetVec4Def()) + || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec4Def())); } else if (BindingInfo.Name == GetColorByIndexFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetColorDef()) - || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec4Def())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadColorByIndex)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadColorByIndex)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetColorDef()) + || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec4Def())); } else if (BindingInfo.Name == GetQuatByIndexFunctionName) { - if (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetQuatDef()) - || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec4Def())) - { - NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadQuatByIndex)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadQuatByIndex)::Bind(this, OutFunc, AttributeToRead); + bBindSuccessful = (HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetQuatDef()) + || HasMatchingVariable(EmitterVariables, AttributeToRead, FNiagaraTypeDefinition::GetHalfVec4Def())); } else if (BindingInfo.Name == GetIDByIndexFunctionName) - { - FNiagaraVariable VariableToRead(FNiagaraTypeDefinition::GetIDDef(), AttributeToRead); - if (PIData->EmitterInstance->GetData().GetVariables().Find(VariableToRead) != INDEX_NONE) { NDI_FUNC_BINDER(UNiagaraDataInterfaceParticleRead, ReadIDByIndex)::Bind(this, OutFunc, AttributeToRead); - bBindSuccessful = true; - } + FNiagaraVariable VariableToRead(FNiagaraTypeDefinition::GetIDDef(), AttributeToRead); + bBindSuccessful = PIData->EmitterInstance->GetData().GetVariables().Find(VariableToRead) != INDEX_NONE; } if (!bBindSuccessful) { - UE_LOG(LogNiagara, Error, TEXT("Failed to bind VMExternalFunction '%s' with attribute '%s'! Check that the attribute is named correctly."), *BindingInfo.Name.ToString(), *AttributeToRead.ToString()); + UE_LOG(LogNiagara, Warning, TEXT("Failed to bind VMExternalFunction '%s' with attribute '%s'! Check that the attribute is named correctly."), *BindingInfo.Name.ToString(), *AttributeToRead.ToString()); } } @@ -1445,31 +1391,33 @@ FORCEINLINE void ReadWithCheck(FVectorVMContext& Context, FName AttributeToRead, const auto ValueData = FNiagaraDataSetAccessor::CreateReader(EmitterInstance->GetData(), AttributeToRead); const auto IDData = FNiagaraDataSetAccessor::CreateReader(EmitterInstance->GetData(), ParticleReadIDName); - - bWriteDummyData = false; - - for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) + if (IDData.IsValid() && ValueData.IsValid()) { - FNiagaraID ParticleID = Params.GetID(); - bool bValid = false; - T Value = Default; + bWriteDummyData = false; - if (ParticleID.Index >= 0 && ParticleID.Index < IDTable.Num()) + for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) { - int32 ParticleIndex = IDTable[ParticleID.Index]; - if (ParticleIndex >= 0 && ParticleIndex < NumSourceInstances) + FNiagaraID ParticleID = Params.GetID(); + bool bValid = false; + T Value = Default; + + if (ParticleID.Index >= 0 && ParticleID.Index < IDTable.Num()) { - FNiagaraID ActualID = IDData.GetSafe(ParticleIndex, NIAGARA_INVALID_ID); - if (ActualID == ParticleID) + int32 ParticleIndex = IDTable[ParticleID.Index]; + if (ParticleIndex >= 0 && ParticleIndex < NumSourceInstances) { - Value = ValueData.GetSafe(ParticleIndex, Default); - bValid = true; + FNiagaraID ActualID = IDData.GetSafe(ParticleIndex, NIAGARA_INVALID_ID); + if (ActualID == ParticleID) + { + Value = ValueData.GetSafe(ParticleIndex, Default); + bValid = true; + } } } - } - Params.SetValid(bValid); - Params.SetValue(Value); + Params.SetValid(bValid); + Params.SetValue(Value); + } } } } @@ -1569,29 +1517,29 @@ FORCEINLINE void ReadByIndexWithCheck(FVectorVMContext& Context, FName Attribute const FNiagaraDataBuffer* CurrentData = EmitterInstance->GetData().GetCurrentData();//TODO: We should really be grabbing these during instance data tick and adding a read ref. Releasing that on PostTick. if (CurrentData && CurrentData->GetNumInstances() > 0 && EmitterInstance->GetGPUContext() == nullptr) { - const TArray& IDTable = CurrentData->GetIDTable(); int32 NumSourceInstances = (int32)CurrentData->GetNumInstances(); const auto ValueData = FNiagaraDataSetAccessor::CreateReader(EmitterInstance->GetData(), AttributeToRead); - const auto IDData = FNiagaraDataSetAccessor::CreateReader(EmitterInstance->GetData(), ParticleReadIDName); - - - bWriteDummyData = false; - - for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) + + if (ValueData.IsValid()) { - int32 ParticleIndex = Params.GetIndex(); + bWriteDummyData = false; - T Value = Default; - bool bValid = false; - if (ParticleIndex >= 0 && ParticleIndex < NumSourceInstances) + for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) { - Value = ValueData.GetSafe(ParticleIndex, Default); - bValid = true; - } + int32 ParticleIndex = Params.GetIndex(); - Params.SetValid(bValid); - Params.SetValue(Value); + T Value = Default; + bool bValid = false; + if (ParticleIndex >= 0 && ParticleIndex < NumSourceInstances) + { + Value = ValueData.GetSafe(ParticleIndex, Default); + bValid = true; + } + + Params.SetValid(bValid); + Params.SetValue(Value); + } } } } @@ -2137,7 +2085,7 @@ void UNiagaraDataInterfaceParticleRead::GetFeedback(UNiagaraSystem* Asset, UNiag { for (const auto& DIInfo : Script->GetVMExecutableData().DataInterfaceInfo) { - if (DIInfo.GetDefaultDataInterface()->GetClass() == GetClass()) + if (DIInfo.MatchesClass(GetClass())) { for (const auto& Func : DIInfo.RegisteredFunctions) { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceRW.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceRW.cpp index b4a5e3e295b1..e4f353c2b623 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceRW.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceRW.cpp @@ -28,6 +28,8 @@ NIAGARA_API extern const FName IndexToUnitStaggeredYFunctionName("IndexToUnitSta NIAGARA_API extern const FName IndexToLinearFunctionName("IndexToLinear"); NIAGARA_API extern const FName LinearToIndexFunctionName("LinearToIndex"); +NIAGARA_API extern const FName ExecutionIndexToUnitFunctionName("ExecutionIndexToUnit"); +NIAGARA_API extern const FName ExecutionIndexToGridIndexFunctionName("ExecutionIndexToGridIndex"); UNiagaraDataInterfaceRWBase::UNiagaraDataInterfaceRWBase(FObjectInitializer const& ObjectInitializer) : Super(ObjectInitializer) @@ -185,6 +187,30 @@ void UNiagaraDataInterfaceGrid3D::GetFunctions(TArray OutFunctions.Add(Sig); } + { + FNiagaraFunctionSignature Sig; + Sig.Name = ExecutionIndexToUnitFunctionName; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Grid"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Unit"))); + + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + OutFunctions.Add(Sig); + } + + { + FNiagaraFunctionSignature Sig; + Sig.Name = ExecutionIndexToGridIndexFunctionName; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Grid"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("IndexX"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("IndexY"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("IndexZ"))); + + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + OutFunctions.Add(Sig); + } + { FNiagaraFunctionSignature Sig; Sig.Name = NumCellsFunctionName; @@ -220,6 +246,8 @@ void UNiagaraDataInterfaceGrid3D::GetVMExternalFunction(const FVMExternalFunctio else if (BindingInfo.Name == IndexToUnitFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } else if (BindingInfo.Name == IndexToLinearFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } else if (BindingInfo.Name == LinearToIndexFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } + else if (BindingInfo.Name == ExecutionIndexToUnitFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } + else if (BindingInfo.Name == ExecutionIndexToGridIndexFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } else if (BindingInfo.Name == CellSizeFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } } @@ -365,6 +393,38 @@ bool UNiagaraDataInterfaceGrid3D::GetFunctionHLSL(const FNiagaraDataInterfaceGPU OutHLSL += FString::Format(FormatSample, ArgsDeclarations); return true; } + else if (FunctionInfo.DefinitionName == ExecutionIndexToUnitFunctionName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {FunctionName}(out float3 Out_Unit) + { + const uint Linear = GDispatchThreadId.x; + const uint IndexX = Linear % {NumCellsName}.x; + const uint IndexY = (Linear / {NumCellsName}.x) % {NumCellsName}.y; + const uint IndexZ = Linear / ({NumCellsName}.x * {NumCellsName}.y); + + Out_Unit = (float3(IndexX, IndexY, IndexZ) + .5) / {NumCellsName}; + } + )"); + + OutHLSL += FString::Format(FormatSample, ArgsDeclarations); + return true; + } + else if (FunctionInfo.DefinitionName == ExecutionIndexToGridIndexFunctionName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {FunctionName}(out int Out_IndexX, out int Out_IndexY, out int Out_IndexZ) + { + const uint Linear = GDispatchThreadId.x; + Out_IndexX = Linear % {NumCellsName}.x; + Out_IndexY = (Linear / {NumCellsName}.x) % {NumCellsName}.y; + Out_IndexZ = Linear / ({NumCellsName}.x * {NumCellsName}.y); + } + )"); + + OutHLSL += FString::Format(FormatSample, ArgsDeclarations); + return true; + } else if (FunctionInfo.DefinitionName == CellSizeFunctionName) { static const TCHAR *FormatSample = TEXT(R"( @@ -549,10 +609,21 @@ void UNiagaraDataInterfaceGrid2D::GetFunctions(TArray { FNiagaraFunctionSignature Sig; - Sig.Name = NumCellsFunctionName; + Sig.Name = ExecutionIndexToUnitFunctionName; Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Grid"))); - Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("NumCellsX"))); - Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("NumCellsY"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec2Def(), TEXT("Unit"))); + + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + OutFunctions.Add(Sig); + } + + { + FNiagaraFunctionSignature Sig; + Sig.Name = ExecutionIndexToGridIndexFunctionName; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Grid"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("IndexX"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("IndexY"))); Sig.bMemberFunction = true; Sig.bRequiresContext = false; @@ -583,6 +654,8 @@ void UNiagaraDataInterfaceGrid2D::GetVMExternalFunction(const FVMExternalFunctio else if (BindingInfo.Name == IndexToUnitStaggeredXFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } else if (BindingInfo.Name == IndexToUnitStaggeredYFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } else if (BindingInfo.Name == IndexToLinearFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } + else if (BindingInfo.Name == ExecutionIndexToUnitFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } + else if (BindingInfo.Name == ExecutionIndexToGridIndexFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } else if (BindingInfo.Name == LinearToIndexFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } else if (BindingInfo.Name == CellSizeFunctionName) { OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceRWBase::EmptyVMFunction); } } @@ -752,6 +825,36 @@ bool UNiagaraDataInterfaceGrid2D::GetFunctionHLSL(const FNiagaraDataInterfaceGPU OutHLSL += FString::Format(FormatSample, ArgsDeclarations); return true; } + else if (FunctionInfo.DefinitionName == ExecutionIndexToUnitFunctionName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {FunctionName}(out float2 Out_Unit) + { + const uint Linear = GDispatchThreadId.x; + const uint IndexX = Linear % {NumCellsName}.x; + const uint IndexY = Linear / {NumCellsName}.x; + + Out_Unit = (float2(IndexX, IndexY) + .5) / float2({NumCellsName}); + } + )"); + + OutHLSL += FString::Format(FormatSample, ArgsDeclarations); + return true; + } + else if (FunctionInfo.DefinitionName == ExecutionIndexToGridIndexFunctionName) + { + static const TCHAR* FormatSample = TEXT(R"( + void {FunctionName}(out int Out_IndexX, out int Out_IndexY) + { + const uint Linear = GDispatchThreadId.x; + Out_IndexX = Linear % {NumCellsName}.x; + Out_IndexY = Linear / {NumCellsName}.x; + } + )"); + + OutHLSL += FString::Format(FormatSample, ArgsDeclarations); + return true; + } else if (FunctionInfo.DefinitionName == CellSizeFunctionName) { static const TCHAR *FormatSample = TEXT(R"( diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceRenderTarget2D.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceRenderTarget2D.cpp new file mode 100644 index 000000000000..104c44e86f0e --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceRenderTarget2D.cpp @@ -0,0 +1,498 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#include "NiagaraDataInterfaceRenderTarget2D.h" +#include "NiagaraShader.h" +#include "ShaderParameterUtils.h" +#include "ClearQuad.h" +#include "TextureResource.h" +#include "Engine/Texture2D.h" +#include "NiagaraEmitterInstanceBatcher.h" +#include "NiagaraSystemInstance.h" +#include "NiagaraRenderer.h" +#include "Engine/TextureRenderTarget2D.h" + +#define LOCTEXT_NAMESPACE "NiagaraDataInterfaceRenderTarget2D" + +const FString UNiagaraDataInterfaceRenderTarget2D::SizeName(TEXT("Size_")); + +const FString UNiagaraDataInterfaceRenderTarget2D::OutputName(TEXT("Output_")); + + +// Global VM function names, also used by the shaders code generation methods. +const FName UNiagaraDataInterfaceRenderTarget2D::SetValueFunctionName("SetRenderTargetValue"); +const FName UNiagaraDataInterfaceRenderTarget2D::GetValueFunctionName("GetRenderTargetValue"); +const FName UNiagaraDataInterfaceRenderTarget2D::SetSizeFunctionName("SetRenderTargetSize"); +const FName UNiagaraDataInterfaceRenderTarget2D::GetSizeFunctionName("GetRenderTargetSize"); + + +FNiagaraVariableBase UNiagaraDataInterfaceRenderTarget2D::ExposedRTVar; + + +/*--------------------------------------------------------------------------------------------------------------------------*/ +struct FNiagaraDataInterfaceParametersCS_RenderTarget2D : public FNiagaraDataInterfaceParametersCS +{ + DECLARE_TYPE_LAYOUT(FNiagaraDataInterfaceParametersCS_RenderTarget2D, NonVirtual); +public: + void Bind(const FNiagaraDataInterfaceGPUParamInfo& ParameterInfo, const class FShaderParameterMap& ParameterMap) + { + SizeParam.Bind(ParameterMap, *(UNiagaraDataInterfaceRenderTarget2D::SizeName + ParameterInfo.DataInterfaceHLSLSymbol)); + + OutputParam.Bind(ParameterMap, *(UNiagaraDataInterfaceRenderTarget2D::OutputName + ParameterInfo.DataInterfaceHLSLSymbol)); + + } + + void Set(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) const + { + check(IsInRenderingThread()); + + // Get shader and DI + FRHIComputeShader* ComputeShaderRHI = Context.Shader.GetComputeShader(); + FNiagaraDataInterfaceProxyRenderTarget2DProxy* VFDI = static_cast(Context.DataInterface); + + FRenderTarget2DRWInstanceData_RenderThread* ProxyData = VFDI->SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); + check(ProxyData); + + int SizeTmp[2]; + SizeTmp[0] = ProxyData->Size.X; + SizeTmp[1] = ProxyData->Size.Y; + SetShaderValue(RHICmdList, ComputeShaderRHI, SizeParam, SizeTmp); + + + if ( OutputParam.IsUAVBound()) + { + + FRHIUnorderedAccessView* OutputUAV = nullptr; + + if (Context.IsOutputStage && ProxyData->UAV.IsValid()) + { + OutputUAV = ProxyData->UAV; + RHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, ProxyData->RenderTargetToCopyTo); + RHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, EResourceTransitionPipeline::EComputeToCompute, OutputUAV); + } + else + { + OutputUAV = Context.Batcher->GetEmptyRWTextureFromPool(RHICmdList, EPixelFormat::PF_A16B16G16R16); + } + RHICmdList.SetUAVParameter(ComputeShaderRHI, OutputParam.GetUAVIndex(), OutputUAV); + } + } + + void Unset(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) const + { + if (OutputParam.IsBound()) + { + FNiagaraDataInterfaceProxyRenderTarget2DProxy* VFDI = static_cast(Context.DataInterface); + FRenderTarget2DRWInstanceData_RenderThread* ProxyData = VFDI->SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); + OutputParam.UnsetUAV(RHICmdList, Context.Shader.GetComputeShader()); + FRHIUnorderedAccessView* OutputUAV = nullptr; + if (ProxyData) + { + RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ProxyData->RenderTargetToCopyTo); + RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, EResourceTransitionPipeline::EComputeToCompute, OutputUAV); + } + } + } + +private: + + LAYOUT_FIELD(FShaderParameter, SizeParam); + LAYOUT_FIELD(FRWShaderParameter, OutputParam); +}; + +IMPLEMENT_TYPE_LAYOUT(FNiagaraDataInterfaceParametersCS_RenderTarget2D); + +IMPLEMENT_NIAGARA_DI_PARAMETER(UNiagaraDataInterfaceRenderTarget2D, FNiagaraDataInterfaceParametersCS_RenderTarget2D); + + +UNiagaraDataInterfaceRenderTarget2D::UNiagaraDataInterfaceRenderTarget2D(FObjectInitializer const& ObjectInitializer) + : Super(ObjectInitializer) +{ + Proxy.Reset(new FNiagaraDataInterfaceProxyRenderTarget2DProxy()); + + //FNiagaraTypeDefinition Def(UObject::StaticClass()); + //RenderTargetUserParameter.Parameter.SetType(Def); +} + + +void UNiagaraDataInterfaceRenderTarget2D::PostInitProperties() +{ + Super::PostInitProperties(); + + //Can we register data interfaces as regular types and fold them into the FNiagaraVariable framework for UI and function calls etc? + if (HasAnyFlags(RF_ClassDefaultObject)) + { + FNiagaraTypeRegistry::Register(FNiagaraTypeDefinition(GetClass()), /*bCanBeParameter*/ true, /*bCanBePayload*/ false, /*bIsUserDefined*/ false); + + ExposedRTVar = FNiagaraVariableBase(FNiagaraTypeDefinition(UTexture::StaticClass()), TEXT("RenderTarget")); + } +} + +void UNiagaraDataInterfaceRenderTarget2D::GetFunctions(TArray& OutFunctions) +{ + Super::GetFunctions(OutFunctions); + + const int32 EmitterSystemOnlyBitmask = ENiagaraScriptUsageMask::Emitter | ENiagaraScriptUsageMask::System; + + { + FNiagaraFunctionSignature Sig; + Sig.Name = GetSizeFunctionName; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("RenderTarget"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Width"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Height"))); + + Sig.bExperimental = true; + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + OutFunctions.Add(Sig); + } + + { + FNiagaraFunctionSignature Sig; + Sig.Name = SetSizeFunctionName; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("RenderTarget"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Width"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Height"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Success"))); + + Sig.ModuleUsageBitmask = EmitterSystemOnlyBitmask; + Sig.bExperimental = true; + Sig.bMemberFunction = true; + Sig.bRequiresExecPin = true; + Sig.bRequiresContext = false; + Sig.bSupportsCPU = true; + Sig.bSupportsGPU = false; + OutFunctions.Add(Sig); + } + + { + FNiagaraFunctionSignature Sig; + Sig.Name = SetValueFunctionName; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("RenderTarget"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("IndexX"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("IndexY"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetColorDef(), TEXT("Value"))); + + Sig.bExperimental = true; + Sig.bMemberFunction = true; + Sig.bRequiresExecPin = true; + Sig.bRequiresContext = false; + Sig.bWriteFunction = true; + Sig.bSupportsCPU = false; + Sig.bSupportsGPU = true; + OutFunctions.Add(Sig); + } + + +} + + +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceRenderTarget2D, GetSize); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceRenderTarget2D, SetSize); +void UNiagaraDataInterfaceRenderTarget2D::GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction &OutFunc) +{ + Super::GetVMExternalFunction(BindingInfo, InstanceData, OutFunc); + if (BindingInfo.Name == GetSizeFunctionName) + { + check(BindingInfo.GetNumInputs() == 1 && BindingInfo.GetNumOutputs() == 2); + NDI_FUNC_BINDER(UNiagaraDataInterfaceRenderTarget2D, GetSize)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SetSizeFunctionName) + { + check(BindingInfo.GetNumInputs() == 3 && BindingInfo.GetNumOutputs() == 1); + NDI_FUNC_BINDER(UNiagaraDataInterfaceRenderTarget2D, SetSize)::Bind(this, OutFunc); + } + +} + +bool UNiagaraDataInterfaceRenderTarget2D::Equals(const UNiagaraDataInterface* Other) const +{ + if (!Super::Equals(Other)) + { + return false; + } + //const UNiagaraDataInterfaceRenderTarget2D* OtherTyped = CastChecked(Other); + + //return OtherTyped != nullptr && OtherTyped->RenderTargetUserParameter == RenderTargetUserParameter && OtherTyped->bCreateRenderTarget == bCreateRenderTarget; + return true; +} + +void UNiagaraDataInterfaceRenderTarget2D::GetParameterDefinitionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) +{ + Super::GetParameterDefinitionHLSL(ParamInfo, OutHLSL); + + static const TCHAR *FormatDeclarations = TEXT(R"( + RWTexture2D RW{OutputName}; + )"); + TMap ArgsDeclarations = { + { TEXT("OutputName"), OutputName + ParamInfo.DataInterfaceHLSLSymbol }, + }; + OutHLSL += FString::Format(FormatDeclarations, ArgsDeclarations); +} + +bool UNiagaraDataInterfaceRenderTarget2D::GetFunctionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, const FNiagaraDataInterfaceGeneratedFunction& FunctionInfo, int FunctionInstanceIndex, FString& OutHLSL) +{ + bool ParentRet = Super::GetFunctionHLSL(ParamInfo, FunctionInfo, FunctionInstanceIndex, OutHLSL); + if (ParentRet) + { + return true; + } + if (FunctionInfo.DefinitionName == SetValueFunctionName) + { + static const TCHAR* FormatBounds = TEXT(R"( + void {FunctionName}(int In_IndexX, int In_IndexY, float4 In_Value) + { + RW{Output}[int2(In_IndexX, In_IndexY)] = In_Value; + } + )"); + TMap ArgsBounds = { + {TEXT("FunctionName"), FunctionInfo.InstanceName}, + {TEXT("Output"), OutputName + ParamInfo.DataInterfaceHLSLSymbol}, + }; + OutHLSL += FString::Format(FormatBounds, ArgsBounds); + return true; + } + if (FunctionInfo.DefinitionName == GetSizeFunctionName) + { + static const TCHAR* FormatBounds = TEXT(R"( + void {FunctionName}(out int Out_Width, out int Out_Height) + { + uint BufferWidth = 0U; + uint BufferHeight = 0U; + RW{Output}.GetDimensions(BufferWidth, BufferHeight); + + Out_Width = (int) BufferWidth; + Out_Height = (int) BufferHeight; + } + )"); + TMap ArgsBounds = { + {TEXT("FunctionName"), FunctionInfo.InstanceName}, + {TEXT("Output"), OutputName + ParamInfo.DataInterfaceHLSLSymbol}, + }; + OutHLSL += FString::Format(FormatBounds, ArgsBounds); + return true; + } + + return false; +} + +bool UNiagaraDataInterfaceRenderTarget2D::CopyToInternal(UNiagaraDataInterface* Destination) const +{ + if (!Super::CopyToInternal(Destination)) + { + return false; + } + + UNiagaraDataInterfaceRenderTarget2D* OtherTyped = CastChecked(Destination); + + return true; +} + +bool UNiagaraDataInterfaceRenderTarget2D::InitPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) +{ + check(Proxy); + + FRenderTarget2DRWInstanceData_GameThread* InstanceData = new (PerInstanceData) FRenderTarget2DRWInstanceData_GameThread(); + if (!InstanceData->TargetTexture) + { + InstanceData->TargetTexture = NewObject(this); + InstanceData->TargetTexture->bCanCreateUAV = true; + InstanceData->TargetTexture->RenderTargetFormat = RTF_RGBA16f; + InstanceData->TargetTexture->ClearColor = FLinearColor(0.0, 0, 0, 0); + InstanceData->TargetTexture->bAutoGenerateMips = false; + FNiagaraSystemInstanceID SysID = SystemInstance->GetId(); + ManagedRenderTargets.Add(SysID) = InstanceData->TargetTexture; + } + + // Push Updates to Proxy. + FNiagaraDataInterfaceProxyRenderTarget2DProxy* RT_Proxy = GetProxyAs(); + ENQUEUE_RENDER_COMMAND(FUpdateData)( + [GridColl = this, TexPtr = InstanceData->TargetTexture, RT_Proxy, InstanceID = SystemInstance->GetId(), RT_InstanceData=*InstanceData](FRHICommandListImmediate& RHICmdList) + { + check(!RT_Proxy->SystemInstancesToProxyData_RT.Contains(InstanceID)); + FRenderTarget2DRWInstanceData_RenderThread* TargetData = &RT_Proxy->SystemInstancesToProxyData_RT.Add(InstanceID); + + TargetData->DebugTargetTexture = TexPtr; + TargetData->Size = RT_InstanceData.Size; + + RT_Proxy->SetElementCount(TargetData->Size.X * TargetData->Size.Y); + TargetData->RenderTargetToCopyTo = nullptr; + }); + return true; +} + + +void UNiagaraDataInterfaceRenderTarget2D::DestroyPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) +{ + FRenderTarget2DRWInstanceData_GameThread* InstanceData = static_cast(PerInstanceData); + + InstanceData->~FRenderTarget2DRWInstanceData_GameThread(); + + FNiagaraDataInterfaceProxyRenderTarget2DProxy* RT_Proxy = GetProxyAs(); + ENQUEUE_RENDER_COMMAND(FNiagaraDIDestroyInstanceData) ( + [RT_Proxy, InstanceID=SystemInstance->GetId(), Batcher=SystemInstance->GetBatcher()](FRHICommandListImmediate& CmdList) + { + //check(ThisProxy->SystemInstancesToProxyData.Contains(InstanceID)); + RT_Proxy->SystemInstancesToProxyData_RT.Remove(InstanceID); + } + ); + + // Make sure to clear out the reference to the render target if we created one. + FNiagaraSystemInstanceID SysId = SystemInstance->GetId(); + ManagedRenderTargets.Remove(SysId); +} + + +void UNiagaraDataInterfaceRenderTarget2D::GetExposedVariables(TArray& OutVariables) const +{ + OutVariables.Emplace(ExposedRTVar); +} + +bool UNiagaraDataInterfaceRenderTarget2D::GetExposedVariableValue(const FNiagaraVariableBase& InVariable, void* InPerInstanceData, FNiagaraSystemInstance* InSystemInstance, void* OutData) const +{ + FRenderTarget2DRWInstanceData_GameThread* InstanceData = static_cast(InPerInstanceData); + if (InVariable.IsValid() && InVariable == ExposedRTVar && InstanceData && InstanceData->TargetTexture) + { + UObject** Var = (UObject**)OutData; + *Var = InstanceData->TargetTexture; + return true; + } + return false; +} + + + +void UNiagaraDataInterfaceRenderTarget2D::SetSize(FVectorVMContext& Context) +{ + // This should only be called from a system or emitter script due to a need for only setting up initially. + VectorVM::FUserPtrHandler InstData(Context); + VectorVM::FExternalFuncInputHandler InSizeX(Context); + VectorVM::FExternalFuncInputHandler InSizeY(Context); + VectorVM::FExternalFuncRegisterHandler OutSuccess(Context); + + for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) + { + int SizeX = InSizeX.GetAndAdvance(); + int SizeY = InSizeY.GetAndAdvance(); + bool bSuccess = (InstData.Get() != nullptr && Context.NumInstances == 1 && SizeX >= 0 && SizeY >= 0); + *OutSuccess.GetDestAndAdvance() = bSuccess; + if (bSuccess) + { + InstData->Size.X = SizeX; + InstData->Size.Y = SizeY; + } + } +} + +void UNiagaraDataInterfaceRenderTarget2D::GetSize(FVectorVMContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + VectorVM::FExternalFuncRegisterHandler OutSizeX(Context); + VectorVM::FExternalFuncRegisterHandler OutSizeY(Context); + + for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) + { + *OutSizeX.GetDestAndAdvance() = InstData->Size.X; + *OutSizeY.GetDestAndAdvance() = InstData->Size.Y; + } +} + + +bool UNiagaraDataInterfaceRenderTarget2D::PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) +{ + FRenderTarget2DRWInstanceData_GameThread* InstanceData = static_cast(PerInstanceData); + return false; +} + +bool UNiagaraDataInterfaceRenderTarget2D::PerInstanceTickPostSimulate(void* PerInstanceData, FNiagaraSystemInstance* InSystemInstance, float DeltaSeconds) +{ + FRenderTarget2DRWInstanceData_GameThread* InstanceData = static_cast(PerInstanceData); + bool bNeedsReset = false; + + { + + FTextureResource* RT_Resource = nullptr; + + + bool bUpdateRT = true; + if (InstanceData->TargetTexture) + { + int32 RTSizeX = FMath::Max(InstanceData->Size.X, 1); + int32 RTSizeY = FMath::Max(InstanceData->Size.Y, 1); + + if (InstanceData->TargetTexture->SizeX != RTSizeX || InstanceData->TargetTexture->SizeY != RTSizeY || InstanceData->TargetTexture->RenderTargetFormat != RTF_RGBA16f) + { + // resize RT to match what we need for the output + InstanceData->TargetTexture->InitAutoFormat(RTSizeX, RTSizeY); + InstanceData->TargetTexture->UpdateResourceImmediate(true); + bUpdateRT = true; + //TargetTexture->InitCustomFormat(InstanceData->Size.X * InstanceData->NumTiles.X, InstanceData->Size.Y * InstanceData->NumTiles.Y, PF_R32_FLOAT, false); + + /*if (InstanceData->TargetTexture->Resource) + { + bNeedsReset = true; + }*/ + } + RT_Resource = InstanceData->TargetTexture->Resource; + } + + if (bUpdateRT) + { + FNiagaraDataInterfaceProxyRenderTarget2DProxy* RT_Proxy = GetProxyAs(); + ENQUEUE_RENDER_COMMAND(FUpdateData)( + [GridColl = this, TexPtr = InstanceData->TargetTexture, RT_Resource, RT_Proxy, InstanceID = InSystemInstance->GetId(), RT_InstanceData = *InstanceData](FRHICommandListImmediate& RHICmdList) + { + FRenderTarget2DRWInstanceData_RenderThread* TargetData = RT_Proxy->SystemInstancesToProxyData_RT.Find(InstanceID); + if (!TargetData) + return; + + TargetData->DebugTargetTexture = TexPtr; + TargetData->Size = RT_InstanceData.Size; + RT_Proxy->SetElementCount(TargetData->Size.X* TargetData->Size.Y); + if (RT_Resource && RT_Resource->TextureRHI.IsValid()) + { + //if (TargetData->RenderTargetToCopyTo != RT_Resource->TextureRHI) + { + TargetData->RenderTargetToCopyTo =RT_Resource->TextureRHI; // TexPtr->TextureReference.TextureReferenceRHI crashes when creating the UAV + TargetData->UAV = RHICreateUnorderedAccessView(TargetData->RenderTargetToCopyTo, 0); + } + } + else + { + TargetData->RenderTargetToCopyTo = nullptr; + TargetData->UAV = nullptr; + } + + }); + } + } + return bNeedsReset; +} + +void FNiagaraDataInterfaceProxyRenderTarget2DProxy::PreStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) +{ + +} + +void FNiagaraDataInterfaceProxyRenderTarget2DProxy::PostStage(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceStageArgs& Context) +{ +} + +void FNiagaraDataInterfaceProxyRenderTarget2DProxy::PostSimulate(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) +{ + + FRenderTarget2DRWInstanceData_RenderThread* ProxyData = SystemInstancesToProxyData_RT.Find(Context.SystemInstanceID); + + if (ProxyData->RenderTargetToCopyTo != nullptr) + { + + //FRHICopyTextureInfo CopyInfo; + //RHICmdList.CopyTexture(ProxyData->CurrentData->GridBuffer.Buffer, ProxyData->RenderTargetToCopyTo, CopyInfo); + + //RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ProxyData->RenderTargetToCopyTo); + //RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, EResourceTransitionPipeline::EComputeToGfx, ProxyData->UAV); + } +} + +void FNiagaraDataInterfaceProxyRenderTarget2DProxy::ResetData(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceArgs& Context) +{ +} +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceSkeletalMesh.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceSkeletalMesh.cpp index 16229a42bf2c..1b2286730170 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceSkeletalMesh.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceSkeletalMesh.cpp @@ -61,18 +61,27 @@ void FSkeletalMeshSamplingRegionAreaWeightedSampler::Init(FNDISkeletalMesh_Insta float FSkeletalMeshSamplingRegionAreaWeightedSampler::GetWeights(TArray& OutWeights) { - check(Owner && Owner->Mesh); - check(Owner->Mesh->IsValidLODIndex(Owner->GetLODIndex())); + check(Owner); + + USkeletalMesh* SkelMesh = Owner->SkeletalMesh.Get(); + if (SkelMesh == nullptr) + { + OutWeights.Empty(); + return 0.0f; + } + + check(SkelMesh->IsValidLODIndex(Owner->GetLODIndex())); float Total = 0.0f; int32 NumUsedRegions = Owner->SamplingRegionIndices.Num(); if (NumUsedRegions <= 1) { //Use 0 or 1 Sampling region. Only need additional area weighting between regions if we're sampling from multiple. + OutWeights.Empty(); return 0.0f; } - const FSkeletalMeshSamplingInfo& SamplingInfo = Owner->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = SkelMesh->GetSamplingInfo(); OutWeights.Empty(NumUsedRegions); for (int32 i = 0; i < NumUsedRegions; ++i) { @@ -517,11 +526,11 @@ void FSkeletalMeshGpuSpawnStaticBuffers::Initialise(FNDISkeletalMesh_InstanceDat if (TriangleCount == 0) { - UE_LOG(LogNiagara, Warning, TEXT("FSkeletalMeshGpuSpawnStaticBuffers> TriangleCount(%d) is invalid for SkelMesh(%s) System(%s)"), TriangleCount, *GetFullNameSafe(InstData->Mesh), *GetFullNameSafe(SystemInstance->GetSystem())); + UE_LOG(LogNiagara, Warning, TEXT("FSkeletalMeshGpuSpawnStaticBuffers> TriangleCount(%d) is invalid for SkelMesh(%s) System(%s)"), TriangleCount, *GetFullNameSafe(InstData->SkeletalMesh.Get()), *GetFullNameSafe(SystemInstance->GetSystem())); } if (VertexCount == 0) { - UE_LOG(LogNiagara, Warning, TEXT("FSkeletalMeshGpuSpawnStaticBuffers> VertexCount(%d) is invalid for SkelMesh(%s) System(%s)"), VertexCount, *GetFullNameSafe(InstData->Mesh), *GetFullNameSafe(SystemInstance->GetSystem())); + UE_LOG(LogNiagara, Warning, TEXT("FSkeletalMeshGpuSpawnStaticBuffers> VertexCount(%d) is invalid for SkelMesh(%s) System(%s)"), VertexCount, *GetFullNameSafe(InstData->SkeletalMesh.Get()), *GetFullNameSafe(SystemInstance->GetSystem())); } if (bUseGpuUniformlyDistributedSampling) @@ -529,7 +538,7 @@ void FSkeletalMeshGpuSpawnStaticBuffers::Initialise(FNDISkeletalMesh_InstanceDat const int32 NumAreaSamples = SkeletalMeshSamplingLODBuiltData->AreaWeightedTriangleSampler.GetNumEntries(); if (NumAreaSamples != TriangleCount) { - UE_LOG(LogNiagara, Warning, TEXT("FSkeletalMeshGpuSpawnStaticBuffers> AreaWeighted Triangle Sampling Count (%d) does not match triangle count (%d), disabling uniform sampling for SkelMesh(%s) System(%s)"), NumAreaSamples, TriangleCount, *GetFullNameSafe(InstData->Mesh), *GetFullNameSafe(SystemInstance->GetSystem())); + UE_LOG(LogNiagara, Warning, TEXT("FSkeletalMeshGpuSpawnStaticBuffers> AreaWeighted Triangle Sampling Count (%d) does not match triangle count (%d), disabling uniform sampling for SkelMesh(%s) System(%s)"), NumAreaSamples, TriangleCount, *GetFullNameSafe(InstData->SkeletalMesh.Get()), *GetFullNameSafe(SystemInstance->GetSystem())); bUseGpuUniformlyDistributedSampling = false; } } @@ -552,7 +561,7 @@ void FSkeletalMeshGpuSpawnStaticBuffers::Initialise(FNDISkeletalMesh_InstanceDat // Create triangle / vertex region sampling data if (InstData->SamplingRegionIndices.Num() > 0) { - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->SkeletalMesh->GetSamplingInfo(); // Count required regions bSamplingRegionsAllAreaWeighted = true; @@ -792,14 +801,14 @@ void FSkeletalMeshGpuDynamicBufferProxy::NewFrame(const FNDISkeletalMesh_Instanc USkeletalMesh* SkelMesh = nullptr; if (InstanceData != nullptr) { - SkelComp = Cast(InstanceData->Component.Get()); + SkelComp = Cast(InstanceData->SceneComponent.Get()); if ( SkelComp != nullptr ) { SkelMesh = SkelComp->SkeletalMesh; } if (SkelMesh == nullptr) { - SkelMesh = InstanceData->MeshSafe.Get(); + SkelMesh = InstanceData->SkeletalMesh.Get(); } } @@ -1109,7 +1118,7 @@ public: FRHIComputeShader* ComputeShaderRHI = Context.Shader.GetComputeShader(); FNiagaraDataInterfaceProxySkeletalMesh* InterfaceProxy = static_cast(Context.DataInterface); - FNiagaraDataInterfaceProxySkeletalMeshData* InstanceData = InterfaceProxy->SystemInstancesToData.Find(Context.SystemInstance); + FNiagaraDataInterfaceProxySkeletalMeshData* InstanceData = InterfaceProxy->SystemInstancesToData.Find(Context.SystemInstanceID); if (InstanceData && InstanceData->StaticBuffers) { FSkeletalMeshGpuSpawnStaticBuffers* StaticBuffers = InstanceData->StaticBuffers; @@ -1363,154 +1372,128 @@ void UNiagaraDataInterfaceSkeletalMesh::ProvidePerInstanceDataForRenderThread(vo Data->MeshSkinWeightLookupBuffer = SourceData->MeshSkinWeightLookupBuffer; } -USkeletalMesh* UNiagaraDataInterfaceSkeletalMesh::GetSkeletalMesh(UNiagaraComponent* OwningComponent, TWeakObjectPtr& SceneComponent, USkeletalMeshComponent*& FoundSkelComp, FNDISkeletalMesh_InstanceData* InstData) +USkeletalMesh* UNiagaraDataInterfaceSkeletalMesh::GetSkeletalMesh(FNiagaraSystemInstance* SystemInstance, TWeakObjectPtr& SceneComponent, USkeletalMeshComponent*& FoundSkelComp, FNDISkeletalMesh_InstanceData* InstData) { - FoundSkelComp = nullptr; - USkeletalMesh* Mesh = nullptr; - if (MeshUserParameter.Parameter.IsValid() && InstData) - { - FNiagaraSystemInstance* SystemInstance = OwningComponent->GetSystemInstance(); - if (UObject* UserParamObject = InstData->UserParamBinding.Init(SystemInstance->GetInstanceParameters(), MeshUserParameter.Parameter)) + // Helper to scour an actor (or its parents) for a valid skeletal mesh component + auto FindActorSkelMeshComponent = [](AActor* Actor, bool bRecurseParents = false) -> USkeletalMeshComponent* { + if (ASkeletalMeshActor* SkelMeshActor = Cast(Actor)) { - InstData->CachedUserParam = UserParamObject; + USkeletalMeshComponent* Comp = SkelMeshActor->GetSkeletalMeshComponent(); + if (Comp && !Comp->IsPendingKill()) + { + return Comp; + } + } + + // Fall back on any valid component on the actor + while (Actor) + { + for (UActorComponent* ActorComp : Actor->GetComponents()) + { + USkeletalMeshComponent* Comp = Cast(ActorComp); + if (Comp && !Comp->IsPendingKill() && Comp->SkeletalMesh != nullptr) + { + return Comp; + } + } + + if (bRecurseParents) + { + Actor = Actor->GetParentActor(); + } + else + { + break; + } + } + + return nullptr; + }; + + if (MeshUserParameter.Parameter.IsValid() && InstData && SystemInstance != nullptr) + { + // Initialize the binding and retrieve the object. If a valid object is bound, we'll try and retrieve the SkelMesh component from it. + // If it's not valid yet, we'll reset and do this again when/if a valid object is set on the binding + UObject* UserParamObject = InstData->UserParamBinding.Init(SystemInstance->GetInstanceParameters(), MeshUserParameter.Parameter); + InstData->CachedUserParam = UserParamObject; + if (UserParamObject) + { if (USkeletalMeshComponent* UserSkelMeshComp = Cast(UserParamObject)) { - FoundSkelComp = UserSkelMeshComp; - Mesh = FoundSkelComp->SkeletalMesh; - } - else if (ASkeletalMeshActor* UserSkelMeshActor = Cast(UserParamObject)) - { - FoundSkelComp = UserSkelMeshActor->GetSkeletalMeshComponent(); - Mesh = FoundSkelComp->SkeletalMesh; + if (!UserSkelMeshComp->IsPendingKill()) + { + FoundSkelComp = UserSkelMeshComp; + } } else if (AActor* Actor = Cast(UserParamObject)) { - for (UActorComponent* ActorComp : Actor->GetComponents()) - { - USkeletalMeshComponent* SourceComp = Cast(ActorComp); - if (SourceComp) - { - USkeletalMesh* PossibleMesh = SourceComp->SkeletalMesh; - if (PossibleMesh != nullptr/* && PossibleMesh->bAllowCPUAccess*/) - { - Mesh = PossibleMesh; - FoundSkelComp = SourceComp; - break; - } - } - } + FoundSkelComp = FindActorSkelMeshComponent(Actor); } else { //We have a valid, non-null UObject parameter type but it is not a type we can use to get a skeletal mesh from. UE_LOG(LogNiagara, Warning, TEXT("SkeletalMesh data interface using object parameter with invalid type. Skeletal Mesh Data Interfaces can only get a valid mesh from SkeletalMeshComponents, SkeletalMeshActors or Actors.")); UE_LOG(LogNiagara, Warning, TEXT("Invalid Parameter : %s"), *UserParamObject->GetFullName()); - UE_LOG(LogNiagara, Warning, TEXT("Niagara Component : %s"), *OwningComponent->GetFullName()); - UE_LOG(LogNiagara, Warning, TEXT("System : %s"), *OwningComponent->GetAsset()->GetFullName()); + UE_LOG(LogNiagara, Warning, TEXT("Niagara Component : %s"), *GetFullNameSafe(Cast(SystemInstance->GetAttachComponent()))); + UE_LOG(LogNiagara, Warning, TEXT("System : %s"), *GetFullNameSafe(SystemInstance->GetSystem())); } } else { - //WARNING - We have a valid user param but the object set is null. + // The binding exists, but no object is bound. Not warning here in case the user knows what they're doing. } } - else if (SourceComponent) + else if (SourceComponent && !SourceComponent->IsPendingKill()) { - Mesh = SourceComponent->SkeletalMesh; FoundSkelComp = SourceComponent; } else if (Source) { - ASkeletalMeshActor* MeshActor = Cast(Source); - USkeletalMeshComponent* SourceComp = nullptr; - if (MeshActor != nullptr) - { - SourceComp = MeshActor->GetSkeletalMeshComponent(); - } - else - { - SourceComp = Source->FindComponentByClass(); - } - - if (SourceComp) - { - Mesh = SourceComp->SkeletalMesh; - FoundSkelComp = SourceComp; - } - else - { - SceneComponent = Source->GetRootComponent(); - } + FoundSkelComp = FindActorSkelMeshComponent(Source); } - else + else if (SystemInstance != nullptr) { - if (UNiagaraComponent* SimComp = OwningComponent) + if (USceneComponent* AttachComponent = SystemInstance->GetAttachComponent()) { - if (USkeletalMeshComponent* ParentComp = Cast(SimComp->GetAttachParent())) + // First, try to find the mesh component up the attachment hierarchy + for (USceneComponent* Curr = AttachComponent; Curr; Curr = Curr->GetAttachParent()) { - FoundSkelComp = ParentComp; - Mesh = ParentComp->SkeletalMesh; - } - else if (USkeletalMeshComponent* OuterComp = SimComp->GetTypedOuter()) - { - FoundSkelComp = OuterComp; - Mesh = OuterComp->SkeletalMesh; - } - else - { - AActor* Owner = SimComp->GetAttachmentRootActor(); - while (Owner && !Mesh) + USkeletalMeshComponent* ParentComp = Cast(Curr); + if (ParentComp && !ParentComp->IsPendingKill()) { - for (UActorComponent* ActorComp : Owner->GetComponents()) - { - USkeletalMeshComponent* SourceComp = Cast(ActorComp); - if (SourceComp) - { - USkeletalMesh* PossibleMesh = SourceComp->SkeletalMesh; - if (PossibleMesh != nullptr/* && PossibleMesh->bAllowCPUAccess*/) - { - Mesh = PossibleMesh; - FoundSkelComp = SourceComp; - - break; - } - } - } - - // Iterate on the actor hierarchy. - Owner = Owner->GetParentActor(); + FoundSkelComp = ParentComp; + break; } } - - if (!SceneComponent.IsValid()) + + if (!FoundSkelComp) { - SceneComponent = SimComp; + // Next, try to find one in our outer chain + USkeletalMeshComponent* OuterComp = AttachComponent->GetTypedOuter(); + if (OuterComp && !OuterComp->IsPendingKill()) + { + FoundSkelComp = OuterComp; + } + else if (AActor* Actor = AttachComponent->GetAttachmentRootActor()) + { + // Final fall-back, look for any mesh component on our root actor or any of its parents + FoundSkelComp = FindActorSkelMeshComponent(Actor, true); + } } } } + USkeletalMesh* Mesh = nullptr; if (FoundSkelComp) { + Mesh = FoundSkelComp->SkeletalMesh; SceneComponent = FoundSkelComp; } - #if WITH_EDITORONLY_DATA - // Don't fall back on the preview mesh if we have a valid skeletal mesh component referenced - if (!Mesh && !FoundSkelComp && PreviewMesh) + else if (!SystemInstance || !SystemInstance->GetWorld()->IsGameWorld()) { - bool bUsePreviewMesh = true; - if (OwningComponent != nullptr) - { - if (UWorld* World = OwningComponent->GetWorld()) - { - bUsePreviewMesh = !World->IsGameWorld(); - } - } - - if (bUsePreviewMesh) - { - Mesh = PreviewMesh; - } + // NOTE: We don't fall back on the preview mesh if we have a valid skeletal mesh component referenced + Mesh = PreviewMesh; } #endif @@ -1524,14 +1507,14 @@ bool FNDISkeletalMesh_InstanceData::Init(UNiagaraDataInterfaceSkeletalMesh* Inte check(SystemInstance); // Initialize members - Component = nullptr; + SceneComponent = nullptr; CachedAttachParent = nullptr; - Mesh = nullptr; - MeshSafe = nullptr; + CachedUserParam = nullptr; + SkeletalMesh = nullptr; Transform = FMatrix::Identity; TransformInverseTransposed = FMatrix::Identity; PrevTransform = FMatrix::Identity; - DeltaSeconds = SystemInstance->GetComponent()->GetWorld()->GetDeltaSeconds(); + DeltaSeconds = SystemInstance->GetWorld()->GetDeltaSeconds(); ChangeId = Interface->ChangeId; bIsGpuUniformlyDistributedSampling = false; bUnlimitedBoneInfluences = false; @@ -1543,28 +1526,32 @@ bool FNDISkeletalMesh_InstanceData::Init(UNiagaraDataInterfaceSkeletalMesh* Inte // Get skel mesh and confirm have valid data USkeletalMeshComponent* NewSkelComp = nullptr; - Mesh = Interface->GetSkeletalMesh(SystemInstance->GetComponent(), Component, NewSkelComp, this); - MeshSafe = Mesh; - - if (!Component.IsValid()) - { - UE_LOG(LogNiagara, Log, TEXT("SkeletalMesh data interface has no valid component. Failed InitPerInstanceData - %s"), *Interface->GetFullName()); - return false; - } - - Transform = Component->GetComponentToWorld().ToMatrixWithScale(); - TransformInverseTransposed = Transform.Inverse().GetTransposed(); - PrevTransform = Transform; + USkeletalMesh* Mesh = Interface->GetSkeletalMesh(SystemInstance, SceneComponent, NewSkelComp, this); - CachedAttachParent = Component->GetAttachParent(); + SkeletalMesh = Mesh; + bMeshValid = Mesh != nullptr; + bComponentValid = SceneComponent.IsValid(); + + Transform = (bComponentValid ? SceneComponent->GetComponentToWorld() : SystemInstance->GetWorldTransform()).ToMatrixWithScale(); + TransformInverseTransposed = Transform.Inverse().GetTransposed(); + PrevTransform = Transform; + + if (USceneComponent* AttachComponent = SystemInstance->GetAttachComponent()) + { + CachedAttachParent = AttachComponent->GetAttachParent(); + } #if WITH_EDITOR if (Mesh != nullptr) { - Mesh->GetOnMeshChanged().AddUObject(SystemInstance->GetComponent(), &UNiagaraComponent::ReinitializeSystem); - if (USkeleton* Skeleton = Mesh->Skeleton) + // HACK! This only works on systems created by a Niagara component...should maybe move somewhere else to cover non-component systems + if (UNiagaraComponent* NiagaraComponent = Cast(SystemInstance->GetAttachComponent())) { - Skeleton->RegisterOnSkeletonHierarchyChanged(USkeleton::FOnSkeletonHierarchyChanged::CreateUObject(SystemInstance->GetComponent(), &UNiagaraComponent::ReinitializeSystem)); + Mesh->GetOnMeshChanged().AddUObject(NiagaraComponent, &UNiagaraComponent::ReinitializeSystem); + if (USkeleton* Skeleton = Mesh->Skeleton) + { + Skeleton->RegisterOnSkeletonHierarchyChanged(USkeleton::FOnSkeletonHierarchyChanged::CreateUObject(NiagaraComponent, &UNiagaraComponent::ReinitializeSystem)); + } } } #endif @@ -1682,7 +1669,7 @@ bool FNDISkeletalMesh_InstanceData::Init(UNiagaraDataInterfaceSkeletalMesh* Inte ExcludedBoneIndex = RefSkel.FindBoneIndex(Interface->ExcludeBoneName); if (ExcludedBoneIndex == INDEX_NONE) { - UE_LOG(LogNiagara, Warning, TEXT("Skeletal Mesh Data Interface '%s' is missing bone '%s' this is ok but may not exclude what you want Mesh '%s' Component '%s'"), *Interface->GetFullName(), *Interface->ExcludeBoneName.ToString(), *Mesh->GetFullName(), *Component->GetFullName()); + UE_LOG(LogNiagara, Warning, TEXT("Skeletal Mesh Data Interface '%s' is missing bone '%s' this is ok but may not exclude what you want Mesh '%s' Component '%s'"), *Interface->GetFullName(), *Interface->ExcludeBoneName.ToString(), *Mesh->GetFullName(), *SceneComponent->GetFullName()); } } @@ -1691,7 +1678,7 @@ bool FNDISkeletalMesh_InstanceData::Init(UNiagaraDataInterfaceSkeletalMesh* Inte { if (RefSkel.GetNum() > TNumericLimits::Max()) { - UE_LOG(LogNiagara, Warning, TEXT("Skeletal Mesh Data Interface '%s' requires more bones '%d' than we currently support '%d' Mesh '%s' Component '%s'"), *Interface->GetFullName(), RefSkel.GetNum(), TNumericLimits::Max(), *Mesh->GetFullName(), *Component->GetFullName()); + UE_LOG(LogNiagara, Warning, TEXT("Skeletal Mesh Data Interface '%s' requires more bones '%d' than we currently support '%d' Mesh '%s' Component '%s'"), *Interface->GetFullName(), RefSkel.GetNum(), TNumericLimits::Max(), *Mesh->GetFullName(), *SceneComponent->GetFullName()); return false; } @@ -1846,51 +1833,65 @@ bool FNDISkeletalMesh_InstanceData::Init(UNiagaraDataInterfaceSkeletalMesh* Inte return true; } -bool FNDISkeletalMesh_InstanceData::ResetRequired(UNiagaraDataInterfaceSkeletalMesh* Interface)const +bool FNDISkeletalMesh_InstanceData::ResetRequired(UNiagaraDataInterfaceSkeletalMesh* Interface, FNiagaraSystemInstance* SystemInstance) const { - USceneComponent* Comp = Component.Get(); - if (!Comp) + // Reset if the scene component we've cached has been invalidated + USceneComponent* Comp = SceneComponent.Get(); + if (bComponentValid && !Comp) { - //The component we were bound to is no longer valid so we have to trigger a reset. return true; } - //Detect and reset on any attachment change. - if (CachedAttachParent.IsValid() && Comp->GetAttachParent() != CachedAttachParent.Get()) + // Reset if any mesh was bound on init, but is now invalidated + USkeletalMesh* SkelMesh = SkeletalMesh.Get(); + if (bMeshValid && !SkelMesh) { return true; } - - // Reset if the LOD we relied on was streamed out, of if the lod we need could now be available. - if (Mesh) + + if (Interface->MeshUserParameter.Parameter.IsValid()) { - const int32 PendingFirstLODIndex = Mesh->GetResourceForRendering()->GetPendingFirstLODIdx(MinLODIdx); + // Reset if the user object ptr has been changed to look at a new object + if (UserParamBinding.GetValue() != CachedUserParam) + { + return true; + } + } + else if (Interface->SourceComponent) + { + // Reset if the source component changed (or there wasn't one and now there is) + if (Interface->SourceComponent != Comp) + { + return true; + } + } + else if (USceneComponent* AttachComponent = SystemInstance->GetAttachComponent()) + { + // Reset if we detect any attachment change. + // TODO: This check is not really comprehensive. What we really need to know is if the mesh we cached comes from a skeletal mesh component in our + // attachment hierarchy, and if that hierarchy has changed in the chain between the system instance's attach component and the cached component, + // therefore potentially invalidating the cached component and mesh as our best choice. + if (CachedAttachParent != AttachComponent->GetAttachParent()) + { + // The scene component our system instance was associated with has changed attachment, so we need to reinit + return true; + } + } + + // Reset if the LOD we relied on was streamed out, or if the LOD we need could now be available. + if (SkelMesh != nullptr) + { + const int32 PendingFirstLODIndex = SkelMesh->GetResourceForRendering()->GetPendingFirstLODIdx(MinLODIdx); if (PendingFirstLODIndex > CachedLODIdx || (PendingFirstLODIndex < CachedLODIdx && bResetOnLODStreamedIn)) { return true; } } + // Reset if the skeletal mesh on the cached skeletal mesh component changed. if (USkeletalMeshComponent* SkelComp = Cast(Comp)) { - if (!SkelComp->SkeletalMesh)//TODO: Handle clearing the mesh gracefully. - { - return true; - } - - //If the user ptr has been changed to look at a new mesh component. TODO: Handle more gracefully. - if (Interface->MeshUserParameter.Parameter.IsValid()) - { - UObject* NewUserParam = UserParamBinding.GetValue(); - if (CachedUserParam != NewUserParam) - { - return true; - } - } - - // Handle the case where they've procedurally swapped out the skeletal mesh from - // the one we previously cached data for. - if (SkelComp->SkeletalMesh != Mesh && Mesh != nullptr && SkelComp->SkeletalMesh != nullptr) + if (SkelComp->SkeletalMesh != SkelMesh) { if (SkinningData.SkinningData.IsValid()) { @@ -1900,18 +1901,18 @@ bool FNDISkeletalMesh_InstanceData::ResetRequired(UNiagaraDataInterfaceSkeletalM } } + // Reset if any parameters changed on the data interface if (Interface->ChangeId != ChangeId) { return true; } - return false; } bool FNDISkeletalMesh_InstanceData::Tick(UNiagaraDataInterfaceSkeletalMesh* Interface, FNiagaraSystemInstance* SystemInstance, float InDeltaSeconds) { - if (ResetRequired(Interface)) + if (ResetRequired(Interface, SystemInstance)) { return true; } @@ -1919,18 +1920,9 @@ bool FNDISkeletalMesh_InstanceData::Tick(UNiagaraDataInterfaceSkeletalMesh* Inte { DeltaSeconds = InDeltaSeconds; - if (Component.IsValid()) - { - PrevTransform = Transform; - Transform = Component->GetComponentToWorld().ToMatrixWithScale(); - TransformInverseTransposed = Transform.Inverse().GetTransposed(); - } - else - { - PrevTransform = FMatrix::Identity; - Transform = FMatrix::Identity; - TransformInverseTransposed = FMatrix::Identity; - } + PrevTransform = Transform; + Transform = (SceneComponent.IsValid() ? SceneComponent->GetComponentToWorld() : SystemInstance->GetWorldTransform()).ToMatrixWithScale(); + TransformInverseTransposed = Transform.Inverse().GetTransposed(); // Cache socket transforms to avoid potentially calculating them multiple times during the VM FilteredSocketTransformsIndex = (FilteredSocketTransformsIndex + 1) % FilteredSocketTransforms.Num(); @@ -1938,7 +1930,7 @@ bool FNDISkeletalMesh_InstanceData::Tick(UNiagaraDataInterfaceSkeletalMesh* Inte if (MeshGpuSpawnDynamicBuffers) { - USkeletalMeshComponent* Comp = Cast(Component.Get()); + USkeletalMeshComponent* Comp = Cast(SceneComponent.Get()); const USkinnedMeshComponent* BaseComp = nullptr; if (Comp) { @@ -1954,7 +1946,7 @@ bool FNDISkeletalMesh_InstanceData::Tick(UNiagaraDataInterfaceSkeletalMesh* Inte void FNDISkeletalMesh_InstanceData::UpdateFilteredSocketTransforms() { - USkeletalMeshComponent* SkelComp = Cast(Component.Get()); + USkeletalMeshComponent* SkelComp = Cast(SceneComponent.Get()); TArray& WriteBuffer = GetFilteredSocketsWriteBuffer(); for (int32 i = 0; i < FilteredSocketInfo.Num(); ++i) @@ -2075,7 +2067,6 @@ void UNiagaraDataInterfaceSkeletalMesh::GetFunctions(TArray(InstData->Component.Get()) : nullptr; if (!InstData) { @@ -2176,12 +2167,15 @@ void UNiagaraDataInterfaceSkeletalMesh::DestroyPerInstanceData(void* PerInstance FNDISkeletalMesh_InstanceData* Inst = (FNDISkeletalMesh_InstanceData*)PerInstanceData; #if WITH_EDITOR - if(USkeletalMesh* SkeletalMesh = Inst->MeshSafe.Get()) - { - SkeletalMesh->GetOnMeshChanged().RemoveAll(SystemInstance->GetComponent()); - if (USkeleton* Skeleton = SkeletalMesh->Skeleton) + if (USkeletalMesh* SkeletalMesh = Inst->SkeletalMesh.Get()) + { + if (UNiagaraComponent* NiagaraComponent = Cast(SystemInstance->GetAttachComponent())) { - Skeleton->UnregisterOnSkeletonHierarchyChanged(SystemInstance->GetComponent()); + SkeletalMesh->GetOnMeshChanged().RemoveAll(NiagaraComponent); + if (USkeleton* Skeleton = SkeletalMesh->Skeleton) + { + Skeleton->UnregisterOnSkeletonHierarchyChanged(NiagaraComponent); + } } } #endif @@ -2218,13 +2212,15 @@ void UNiagaraDataInterfaceSkeletalMesh::GetFeedback(UNiagaraSystem* Asset, UNiag } bool bHasCPUAccessWarning = false; - bool bHasNoMeshAssignedError = false; - + bool bHasNoMeshAssignedWarning = false; + // Collect Errors #if WITH_EDITORONLY_DATA + FNiagaraSystemInstance* SystemInstance = Component ? Component->GetSystemInstance() : nullptr; TWeakObjectPtr SceneComponent; USkeletalMeshComponent* SkelMeshComponent = nullptr; - USkeletalMesh* SkelMesh = GetSkeletalMesh(Component, SceneComponent, SkelMeshComponent); + USkeletalMesh* SkelMesh = GetSkeletalMesh(SystemInstance, SceneComponent, SkelMeshComponent); + if (SkelMesh != nullptr) { bool bHasCPUAccess = true; @@ -2280,7 +2276,7 @@ void UNiagaraDataInterfaceSkeletalMesh::GetFeedback(UNiagaraSystem* Asset, UNiag { for (const auto& DIInfo : Script->GetVMExecutableData().DataInterfaceInfo) { - if (DIInfo.GetDefaultDataInterface()->GetClass() == GetClass()) + if (DIInfo.MatchesClass(GetClass())) { for (const auto& Func : DIInfo.RegisteredFunctions) { @@ -2302,7 +2298,7 @@ void UNiagaraDataInterfaceSkeletalMesh::GetFeedback(UNiagaraSystem* Asset, UNiag } else { - bHasNoMeshAssignedError = true; + bHasNoMeshAssignedWarning = true; } // Report Errors/Warnings @@ -2324,13 +2320,13 @@ void UNiagaraDataInterfaceSkeletalMesh::GetFeedback(UNiagaraSystem* Asset, UNiag } #endif - if (Source == nullptr && bHasNoMeshAssignedError) + if (Source == nullptr && bHasNoMeshAssignedWarning) { - FNiagaraDataInterfaceError NoMeshAssignedError(LOCTEXT("NoMeshAssignedError", "This Data Interface must be assigned a skeletal mesh to operate."), - LOCTEXT("NoMeshAssignedErrorSummary", "No mesh assigned error"), + FNiagaraDataInterfaceFeedback NoMeshAssignedError(LOCTEXT("NoMeshAssignedError", "This Data Interface should be assigned a skeletal mesh to operate correctly."), + LOCTEXT("NoMeshAssignedErrorSummary", "No mesh assigned warning"), FNiagaraDataInterfaceFix()); - OutErrors.Add(NoMeshAssignedError); + OutWarnings.Add(NoMeshAssignedError); } } @@ -2914,8 +2910,8 @@ void UNiagaraDataInterfaceSkeletalMesh::SetSamplingRegionsFromBlueprints(const T ETickingGroup UNiagaraDataInterfaceSkeletalMesh::CalculateTickGroup(const void* PerInstanceData) const { const FNDISkeletalMesh_InstanceData* InstData = static_cast(PerInstanceData); - USkeletalMeshComponent* Component = Cast(InstData->Component.Get()); - if ( Component && bRequireCurrentFrameData) + USkeletalMeshComponent* Component = Cast(InstData->SceneComponent.Get()); + if (Component && bRequireCurrentFrameData) { return NDISKelMesh_GetComponentTickGroup(Component); } @@ -3007,17 +3003,17 @@ int32 UNiagaraDataInterfaceSkeletalMesh::CalculateLODIndexAndSamplingRegions(USk template<> void FSkeletalMeshAccessorHelper::Init(FNDISkeletalMesh_InstanceData* InstData) { - Comp = Cast(InstData->Component.Get()); - Mesh = InstData->Mesh; + Comp = Cast(InstData->SceneComponent.Get()); + Mesh = InstData->SkeletalMesh.Get(); LODData = InstData->CachedLODData; SkinWeightBuffer = InstData->GetSkinWeights(); IndexBuffer = LODData ? LODData->MultiSizeIndexContainer.GetIndexBuffer() : nullptr; SkinningData = InstData->SkinningData.SkinningData.Get(); Usage = InstData->SkinningData.Usage; - if (InstData->Mesh) + if (Mesh) { - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = Mesh->GetSamplingInfo(); SamplingRegion = &SamplingInfo.GetRegion(InstData->SamplingRegionIndices[0]); SamplingRegionBuiltData = &SamplingInfo.GetRegionBuiltData(InstData->SamplingRegionIndices[0]); } @@ -3036,17 +3032,17 @@ void FSkeletalMeshAccessorHelper::Init void FSkeletalMeshAccessorHelper::Init(FNDISkeletalMesh_InstanceData* InstData) { - Comp = Cast(InstData->Component.Get()); - Mesh = InstData->Mesh; + Comp = Cast(InstData->SceneComponent.Get()); + Mesh = InstData->SkeletalMesh.Get(); LODData = InstData->CachedLODData; SkinWeightBuffer = InstData->GetSkinWeights(); IndexBuffer = LODData ? LODData->MultiSizeIndexContainer.GetIndexBuffer() : nullptr; SkinningData = InstData->SkinningData.SkinningData.Get(); Usage = InstData->SkinningData.Usage; - if (InstData->Mesh) + if (Mesh) { - const FSkeletalMeshSamplingInfo& SamplingInfo = InstData->Mesh->GetSamplingInfo(); + const FSkeletalMeshSamplingInfo& SamplingInfo = Mesh->GetSamplingInfo(); SamplingRegion = &SamplingInfo.GetRegion(InstData->SamplingRegionIndices[0]); SamplingRegionBuiltData = &SamplingInfo.GetRegionBuiltData(InstData->SamplingRegionIndices[0]); } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceSpline.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceSpline.cpp index fe956c8e7e11..741bb85c3db2 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceSpline.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceSpline.cpp @@ -394,14 +394,11 @@ bool UNiagaraDataInterfaceSpline::PerInstanceTick(void* PerInstanceData, FNiagar { SplineComponent = Source->FindComponentByClass(); } - else + else if (USceneComponent* AttachComp = SystemInstance->GetAttachComponent()) { - if (UNiagaraComponent* SimComp = SystemInstance->GetComponent()) + if (AActor* Owner = AttachComp->GetAttachmentRootActor()) { - if (AActor* Owner = SimComp->GetAttachmentRootActor()) - { - SplineComponent = Owner->FindComponentByClass(); - } + SplineComponent = Owner->FindComponentByClass(); } } InstData->Component = SplineComponent; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceStaticMesh.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceStaticMesh.cpp index 8d5f6f760c86..123da964205d 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceStaticMesh.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceStaticMesh.cpp @@ -66,10 +66,10 @@ void FStaticMeshFilteredAreaWeightedSectionSampler::Init(const FStaticMeshLODRes float FStaticMeshFilteredAreaWeightedSectionSampler::GetWeights(TArray& OutWeights) { float Total = 0.0f; - if (Owner && Owner->Mesh) + if (Owner && Owner->bMeshValid) { OutWeights.Empty(Owner->GetValidSections().Num()); - if (Owner->Mesh->bSupportUniformlyDistributedSampling && Res->AreaWeightedSectionSamplers.Num() > 0) + if (Owner->StaticMesh->bSupportUniformlyDistributedSampling && Res->AreaWeightedSectionSamplers.Num() > 0) { for (int32 i = 0; i < Owner->GetValidSections().Num(); ++i) { @@ -231,11 +231,9 @@ void FNDIStaticMesh_InstanceData::InitVertexColorFiltering() bool FNDIStaticMesh_InstanceData::Init(UNiagaraDataInterfaceStaticMesh* Interface, FNiagaraSystemInstance* SystemInstance) { check(SystemInstance); - UStaticMesh* PrevMesh = Mesh; - SafeComponent_GT = nullptr; - SafeMesh_GT = nullptr; + SceneComponent = nullptr; + StaticMesh = nullptr; - Mesh = nullptr; Transform = FMatrix::Identity; TransformInverseTransposed = FMatrix::Identity; PrevTransform = FMatrix::Identity; @@ -243,21 +241,23 @@ bool FNDIStaticMesh_InstanceData::Init(UNiagaraDataInterfaceStaticMesh* Interfac ChangeId = Interface->ChangeId; bUsePhysicsVelocity = Interface->bUsePhysicsBodyVelocity; PhysicsVelocity = FVector::ZeroVector; + bMeshValid = false; + bComponentValid = false; + bMeshAllowsCpuAccess = false; + bIsCpuUniformlyDistributedSampling = false; + bIsGpuUniformlyDistributedSampling = false; + ValidSections.Empty(); - SafeMesh_GT = Interface->GetStaticMesh(SafeComponent_GT, SystemInstance); - if (!SafeComponent_GT.IsValid()) - { - UE_LOG(LogNiagara, Log, TEXT("StaticMesh data interface has no valid component - %s"), *Interface->GetFullName()); - return false; - } - Mesh = SafeMesh_GT.Get(); + UStaticMesh* Mesh = Interface->GetStaticMesh(SceneComponent, SystemInstance); + bComponentValid = SceneComponent.IsValid(); + Transform = (bComponentValid ? SceneComponent->GetComponentToWorld() : SystemInstance->GetWorldTransform()).ToMatrixWithScale(); PrevTransform = Transform; - Transform = SafeComponent_GT->GetComponentToWorld().ToMatrixWithScale(); TransformInverseTransposed = Transform.Inverse().GetTransposed(); + if (bUsePhysicsVelocity) { - if (UStaticMeshComponent* MeshComponent = Cast(SafeComponent_GT)) + if (UStaticMeshComponent* MeshComponent = Cast(SceneComponent)) { PhysicsVelocity = MeshComponent->GetPhysicsLinearVelocity(); } @@ -270,7 +270,7 @@ bool FNDIStaticMesh_InstanceData::Init(UNiagaraDataInterfaceStaticMesh* Interfac } // Report missing or inaccessible meshes to the log - if (!Mesh) + if (Mesh == nullptr) { UE_LOG(LogNiagara, Log, TEXT("StaticMesh data interface has no valid mesh - %s"), *Interface->GetFullName()); } @@ -281,19 +281,19 @@ bool FNDIStaticMesh_InstanceData::Init(UNiagaraDataInterfaceStaticMesh* Interfac Mesh = nullptr; // Disallow usage of this mesh to prevent issues on cooked builds } -#if WITH_EDITOR - if (Mesh) + StaticMesh = Mesh; + bMeshValid = Mesh != nullptr; + + if (Mesh != nullptr) { - Mesh->GetOnMeshChanged().AddUObject(SystemInstance->GetComponent(), &UNiagaraComponent::ReinitializeSystem); - } +#if WITH_EDITOR + // HACK! This only works on systems created by a Niagara component...should maybe move somewhere else to cover non-component systems + if (UNiagaraComponent* NiagaraComponent = Cast(SystemInstance->GetAttachComponent())) + { + Mesh->GetOnMeshChanged().AddUObject(NiagaraComponent, &UNiagaraComponent::ReinitializeSystem); + } #endif - bMeshAllowsCpuAccess = false; - bIsCpuUniformlyDistributedSampling = false; - bIsGpuUniformlyDistributedSampling = false; - ValidSections.Empty(); - if (Mesh) - { MinLOD = Mesh->MinLOD.GetValue(); CachedLODIdx = Mesh->RenderData->GetCurrentFirstLODIdx(MinLOD); @@ -324,7 +324,7 @@ bool FNDIStaticMesh_InstanceData::Init(UNiagaraDataInterfaceStaticMesh* Interfac { if (NumMeshSockets > TNumericLimits::Max()) { - UE_LOG(LogNiagara, Warning, TEXT("Static Mesh Data Interface '%s' requires more sockets '%d' than we currently support '%d' Mesh '%s' Component '%s'"), *GetFullNameSafe(Interface), NumMeshSockets, TNumericLimits::Max(), *GetFullNameSafe(Mesh), *GetFullNameSafe(SafeComponent_GT.Get())); + UE_LOG(LogNiagara, Warning, TEXT("Static Mesh Data Interface '%s' requires more sockets '%d' than we currently support '%d' Mesh '%s' Component '%s'"), *GetFullNameSafe(Interface), NumMeshSockets, TNumericLimits::Max(), *GetFullNameSafe(Mesh), *GetFullNameSafe(SceneComponent.Get())); return false; } @@ -346,7 +346,7 @@ bool FNDIStaticMesh_InstanceData::Init(UNiagaraDataInterfaceStaticMesh* Interfac const int32 SocketIndex = Mesh->Sockets.IndexOfByPredicate([&](const UStaticMeshSocket* Socket) { return Socket->SocketName == FilteredSocketName; }); if (SocketIndex == INDEX_NONE) { - UE_LOG(LogNiagara, Warning, TEXT("Static Mesh Data Interface '%s' could not find socket '%s' Mesh '%s' Component '%s'"), *GetFullNameSafe(Interface), *FilteredSocketName.ToString(), *GetFullNameSafe(Mesh), *GetFullNameSafe(SafeComponent_GT.Get())); + UE_LOG(LogNiagara, Warning, TEXT("Static Mesh Data Interface '%s' could not find socket '%s' Mesh '%s' Component '%s'"), *GetFullNameSafe(Interface), *FilteredSocketName.ToString(), *GetFullNameSafe(Mesh), *GetFullNameSafe(SceneComponent.Get())); continue; } ++NumFilteredSockets; @@ -361,23 +361,34 @@ bool FNDIStaticMesh_InstanceData::Init(UNiagaraDataInterfaceStaticMesh* Interfac } } - SafeMesh_GT = Mesh; return true; } bool FNDIStaticMesh_InstanceData::ResetRequired(UNiagaraDataInterfaceStaticMesh* Interface) const { - if (!SafeComponent_GT.IsValid()) + USceneComponent* Component = SceneComponent.Get(); + if (bComponentValid && !Component) { - //The component we were bound to is no longer valid so we have to trigger a reset. + // The component we were bound to is no longer valid so we have to trigger a reset. return true; } - UStaticMesh* MeshSafe = SafeMesh_GT.Get(); - if (MeshSafe != Mesh) + UStaticMesh* Mesh = StaticMesh.Get(); + if (bMeshValid) { - //The static mesh we were bound to is no longer valid so we have to trigger a reset. - return true; + if (!Mesh) + { + // The static mesh we were bound to is no longer valid so we have to trigger a reset. + return true; + } + else if (UStaticMeshComponent* StaticMeshComp = Cast(Component)) + { + if (Mesh != StaticMeshComp->GetStaticMesh()) + { + // The mesh changed on the component we're attached to so we have to reset + return true; + } + } } if (Interface != nullptr && ChangeId != Interface->ChangeId) @@ -385,17 +396,17 @@ bool FNDIStaticMesh_InstanceData::ResetRequired(UNiagaraDataInterfaceStaticMesh* return true; } - // Currently we only reset if the cached LOD was streamed out, to avoid performance hits. To revisit. - // We could probably just recache the data derived from the LOD instead of resetting everything. - if (Mesh && Mesh->RenderData->GetCurrentFirstLODIdx(MinLOD) > CachedLODIdx) + if (Mesh != nullptr) { - return true; - } + // Currently we only reset if the cached LOD was streamed out, to avoid performance hits. To revisit. + // We could probably just recache the data derived from the LOD instead of resetting everything. + if (Mesh->RenderData->GetCurrentFirstLODIdx(MinLOD) > CachedLODIdx) + { + return true; + } - // The following conditions look like they could only be triggered in Editor... - //bool bPrevVCSampling = bSupportingVertexColorSampling;//TODO: Vertex color filtering needs more work. - if (Mesh) - { + // The following conditions look like they could only be triggered in Editor... + //bool bPrevVCSampling = bSupportingVertexColorSampling;//TODO: Vertex color filtering needs more work. const bool bNewMeshAllowsCpuAccess = Mesh->bAllowCPUAccess; const bool bNewIsCpuAreaWeightedSampling = Mesh->bSupportUniformlyDistributedSampling; const bool bNewIsGpuAreaWeightedSampling = bIsCpuUniformlyDistributedSampling && Mesh->bSupportGpuUniformlyDistributedSampling; @@ -403,11 +414,6 @@ bool FNDIStaticMesh_InstanceData::ResetRequired(UNiagaraDataInterfaceStaticMesh* //bSupportingVertexColorSampling = bEnableVertexColorRangeSorting && MeshHasColors(); return bNewMeshAllowsCpuAccess != bMeshAllowsCpuAccess || bNewIsCpuAreaWeightedSampling != bIsCpuUniformlyDistributedSampling || bNewIsGpuAreaWeightedSampling != bIsGpuUniformlyDistributedSampling /* || bSupportingVertexColorSampling != bPrevVCSampling*/; } - else if (bMeshAllowsCpuAccess || bIsCpuUniformlyDistributedSampling || bIsGpuUniformlyDistributedSampling) - { - // We previously had a CPU accessible mesh, but now have none - return true; - } return false; } @@ -421,30 +427,23 @@ bool FNDIStaticMesh_InstanceData::Tick(UNiagaraDataInterfaceStaticMesh* Interfac else { DeltaSeconds = InDeltaSeconds; - if (SafeComponent_GT.IsValid() && SafeMesh_GT.IsValid()) + + PrevTransform = Transform; + Transform = (SceneComponent.IsValid() ? SceneComponent->GetComponentToWorld() : SystemInstance->GetWorldTransform()).ToMatrixWithScale(); + TransformInverseTransposed = Transform.Inverse().GetTransposed(); + + if (bUsePhysicsVelocity) { - PrevTransform = Transform; - Transform = SafeComponent_GT->GetComponentToWorld().ToMatrixWithScale(); - TransformInverseTransposed = Transform.Inverse().GetTransposed(); - if (bUsePhysicsVelocity) + if (UStaticMeshComponent* MeshComponent = Cast(SceneComponent)) { - if (UStaticMeshComponent* MeshComponent = Cast(SafeComponent_GT)) - { - PhysicsVelocity = MeshComponent->GetPhysicsLinearVelocity(); - } - else - { - PhysicsVelocity = FVector::ZeroVector; - } + PhysicsVelocity = MeshComponent->GetPhysicsLinearVelocity(); + } + else + { + PhysicsVelocity = FVector::ZeroVector; } } - else - { - PrevTransform = FMatrix::Identity; - Transform = FMatrix::Identity; - TransformInverseTransposed = FMatrix::Identity; - PhysicsVelocity = FVector::ZeroVector; - } + return false; } } @@ -606,8 +605,8 @@ public: { FNiagaraDataInterfaceProxyStaticMesh* InterfaceProxy = static_cast(Context.DataInterface); - FNiagaraStaticMeshData* Data = InterfaceProxy->SystemInstancesToMeshData.Find(Context.SystemInstance); - ensureMsgf(Data, TEXT("Failed to find data for instance %s"), *FNiagaraUtilities::SystemInstanceIDToString(Context.SystemInstance)); + FNiagaraStaticMeshData* Data = InterfaceProxy->SystemInstancesToMeshData.Find(Context.SystemInstanceID); + ensureMsgf(Data, TEXT("Failed to find data for instance %s"), *FNiagaraUtilities::SystemInstanceIDToString(Context.SystemInstanceID)); if (Data) { @@ -1091,7 +1090,7 @@ struct TSampleModeBinder { FNDIStaticMesh_InstanceData* InstData = (FNDIStaticMesh_InstanceData*)InstanceData; UNiagaraDataInterfaceStaticMesh* MeshInterface = CastChecked(Interface); - if (InstData->Mesh == nullptr) + if (!InstData->bMeshValid) { NextBinder::template Bind(Interface, BindingInfo, InstanceData, OutFunc); } @@ -1140,7 +1139,7 @@ struct TTypedMeshAccessorBinder static void Bind(UNiagaraDataInterface* Interface, const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction &OutFunc) { FNDIStaticMesh_InstanceData* InstData = (FNDIStaticMesh_InstanceData*)InstanceData; - if (!InstData->Mesh) + if (!InstData->bMeshValid) { NextBinder::template Bind(Interface, BindingInfo, InstanceData, OutFunc); return; @@ -1191,7 +1190,7 @@ DEFINE_NDI_FUNC_BINDER(UNiagaraDataInterfaceStaticMesh, GetVertexPosition); void UNiagaraDataInterfaceStaticMesh::GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction &OutFunc) { FNDIStaticMesh_InstanceData* InstData = (FNDIStaticMesh_InstanceData*)InstanceData; - check(InstData && InstData->SafeComponent_GT.IsValid()); + check(InstData); if (BindingInfo.Name == StaticMeshHelpers::IsValidName) { @@ -1384,11 +1383,11 @@ bool UNiagaraDataInterfaceStaticMesh::InitPerInstanceData(void* PerInstanceData, if (bSuccess) { FStaticMeshGpuSpawnBuffer* MeshGpuSpawnBuffer = nullptr; - if (Inst->Mesh && SystemInstance->HasGPUEmitters()) + if (Inst->bMeshValid && SystemInstance->HasGPUEmitters()) { // Always allocate when bAllowCPUAccess (index buffer can only have SRV created in this case as of today) // We do not know if this interface is allocated for CPU or GPU so we allocate for both case... TODO: have some cached data created in case a GPU version is needed? - ensure(Inst->Mesh->bAllowCPUAccess); // this should have been verified in Init() + ensure(Inst->StaticMesh->bAllowCPUAccess); // this should have been verified in Init() MeshGpuSpawnBuffer = new FStaticMeshGpuSpawnBuffer; TRefCountPtr Res = Inst->GetCurrentFirstLOD(); @@ -1421,9 +1420,12 @@ void UNiagaraDataInterfaceStaticMesh::DestroyPerInstanceData(void* PerInstanceDa //UE_LOG(LogNiagara, Log, TEXT("GT: StaticMesh DI - DestroyPerInstanceData %s"), *SystemInstance->GetId().ToString()); #if WITH_EDITOR - if (Inst->Mesh) + if (Inst->StaticMesh.IsValid()) { - Inst->Mesh->GetOnMeshChanged().RemoveAll(SystemInstance->GetComponent()); + if (UNiagaraComponent* NiagaraComponent = Cast(SystemInstance->GetAttachComponent())) + { + Inst->StaticMesh->GetOnMeshChanged().RemoveAll(NiagaraComponent); + } } #endif @@ -1448,9 +1450,9 @@ bool UNiagaraDataInterfaceStaticMesh::PerInstanceTick(void* PerInstanceData, FNi } #if WITH_EDITOR -TArray UNiagaraDataInterfaceStaticMesh::GetErrors() +void UNiagaraDataInterfaceStaticMesh::GetFeedback(UNiagaraSystem* Asset, UNiagaraComponent* Component, TArray& OutErrors, + TArray& OutWarnings, TArray& OutInfo) { - TArray Errors; if (Source == nullptr && DefaultMesh != nullptr && !DefaultMesh->bAllowCPUAccess) { FNiagaraDataInterfaceError CPUAccessNotAllowedError(FText::Format(LOCTEXT("CPUAccessNotAllowedError", "This mesh needs CPU access in order to be used properly.({0})"), FText::FromString(DefaultMesh->GetName())), @@ -1462,14 +1464,14 @@ TArray UNiagaraDataInterfaceStaticMesh::GetErrors() return true; })); - Errors.Add(CPUAccessNotAllowedError); + OutErrors.Add(CPUAccessNotAllowedError); } - bool bHasNoMeshAssignedError = (Source == nullptr && DefaultMesh == nullptr); + bool bHasNoMeshAssignedWarning = (Source == nullptr && DefaultMesh == nullptr); #if WITH_EDITORONLY_DATA - if (bHasNoMeshAssignedError && PreviewMesh != nullptr) + if (bHasNoMeshAssignedWarning && PreviewMesh != nullptr) { - bHasNoMeshAssignedError = false; + bHasNoMeshAssignedWarning = false; if (!PreviewMesh->bAllowCPUAccess) { @@ -1482,30 +1484,31 @@ TArray UNiagaraDataInterfaceStaticMesh::GetErrors() return true; })); - Errors.Add(CPUAccessNotAllowedError); + OutErrors.Add(CPUAccessNotAllowedError); } } #endif - if (bHasNoMeshAssignedError) + if (bHasNoMeshAssignedWarning) { - FNiagaraDataInterfaceError NoMeshAssignedError(LOCTEXT("NoMeshAssignedError", "This Data Interface must be assigned a skeletal mesh to operate."), - LOCTEXT("NoMeshAssignedErrorSummary", "No mesh assigned error"), + FNiagaraDataInterfaceFeedback NoMeshAssignedError(LOCTEXT("NoMeshAssignedError", "This Data Interface should be assigned a static mesh to operate correctly."), + LOCTEXT("NoMeshAssignedErrorSummary", "No mesh assigned warning"), FNiagaraDataInterfaceFix()); - Errors.Add(NoMeshAssignedError); + OutWarnings.Add(NoMeshAssignedError); } - - return Errors; } #endif -TWeakObjectPtr UNiagaraDataInterfaceStaticMesh::GetStaticMesh(TWeakObjectPtr& OutComponent, FNiagaraSystemInstance* SystemInstance) +UStaticMesh* UNiagaraDataInterfaceStaticMesh::GetStaticMesh(TWeakObjectPtr& OutComponent, FNiagaraSystemInstance* SystemInstance) { - TWeakObjectPtr OutMesh; + UStaticMesh* OutMesh = nullptr; + UStaticMeshComponent* FoundMeshComponent = nullptr; + OutComponent = nullptr; + if (SourceComponent) { - OutComponent = SourceComponent; + FoundMeshComponent = SourceComponent; OutMesh = SourceComponent->GetStaticMesh(); } else if (Source) @@ -1523,7 +1526,7 @@ TWeakObjectPtr UNiagaraDataInterfaceStaticMesh::GetStaticMesh(TWeak if (SourceComp) { - OutComponent = SourceComp; + FoundMeshComponent = SourceComp; OutMesh = SourceComp->GetStaticMesh(); } else @@ -1533,70 +1536,72 @@ TWeakObjectPtr UNiagaraDataInterfaceStaticMesh::GetStaticMesh(TWeak } else if (SystemInstance != nullptr) { - if (UNiagaraComponent* SimComp = SystemInstance->GetComponent()) + if (USceneComponent* AttachComponent = SystemInstance->GetAttachComponent()) { - if (UStaticMeshComponent* ParentComp = Cast(SimComp->GetAttachParent())) + // First, try to find the mesh component up the attachment hierarchy + for (USceneComponent* Curr = AttachComponent; Curr; Curr = Curr->GetAttachParent()) { - OutComponent = ParentComp; - OutMesh = ParentComp->GetStaticMesh(); - } - else if (UStaticMeshComponent* OuterComp = SimComp->GetTypedOuter()) - { - OutComponent = OuterComp; - OutMesh = OuterComp->GetStaticMesh(); - } - else if (AActor* Owner = SimComp->GetAttachmentRootActor()) - { - for (UActorComponent* ActorComp : Owner->GetComponents()) + if (UStaticMeshComponent* ParentComp = Cast(Curr)) { - UStaticMeshComponent* SourceComp = Cast(ActorComp); - if (SourceComp) - { - UStaticMesh* PossibleMesh = SourceComp->GetStaticMesh(); - if (PossibleMesh != nullptr && PossibleMesh->bAllowCPUAccess) - { - OutComponent = SourceComp; - OutMesh = PossibleMesh; - break; - } - } + FoundMeshComponent = ParentComp; + OutMesh = ParentComp->GetStaticMesh(); + break; } } - if (!OutComponent.IsValid()) + if (!OutMesh) { - OutComponent = SimComp; + // Next, try to find one in our outer chain + if (UStaticMeshComponent* OuterComp = AttachComponent->GetTypedOuter()) + { + FoundMeshComponent = OuterComp; + OutMesh = OuterComp->GetStaticMesh(); + } + else if (AActor* Owner = AttachComponent->GetAttachmentRootActor()) + { + // Final fall-back, look for any mesh component on our root actor or any of its parents + while (Owner && !OutMesh) + { + for (UActorComponent* ActorComp : Owner->GetComponents()) + { + UStaticMeshComponent* SourceComp = Cast(ActorComp); + if (SourceComp) + { + FoundMeshComponent = SourceComp; + UStaticMesh* PossibleMesh = SourceComp->GetStaticMesh(); + if (PossibleMesh != nullptr && PossibleMesh->bAllowCPUAccess) + { + OutMesh = PossibleMesh; + break; + } + } + } + + // Iterate on the actor hierarchy. + Owner = Owner->GetParentActor(); + } + } } } } - if (!OutMesh.IsValid() && DefaultMesh) + if (!OutComponent.IsValid()) + { + OutComponent = FoundMeshComponent; + } + + if (!OutMesh) { OutMesh = DefaultMesh; } #if WITH_EDITORONLY_DATA - if (!OutMesh.IsValid() && PreviewMesh) + if (!OutMesh && !FoundMeshComponent && (!SystemInstance || !SystemInstance->GetWorld()->IsGameWorld())) { - bool bUsePreviewMesh = true; - if (SystemInstance != nullptr) - { - UNiagaraComponent* OwningComponent = SystemInstance->GetComponent(); - if (OwningComponent != nullptr) - { - if (UWorld* World = OwningComponent->GetWorld()) - { - bUsePreviewMesh = !World->IsGameWorld(); - } - } - } - - if (bUsePreviewMesh) - { - OutMesh = PreviewMesh; - } + OutMesh = PreviewMesh; } #endif + return OutMesh; } @@ -1607,7 +1612,7 @@ void UNiagaraDataInterfaceStaticMesh::IsValid(FVectorVMContext& Context) VectorVM::FExternalFuncRegisterHandler OutValid(Context); FNiagaraBool Valid; - Valid.SetValue(InstData->Mesh != nullptr); + Valid.SetValue(InstData->bMeshValid); for (int32 i = 0; i < Context.NumInstances; ++i) { *OutValid.GetDest() = Valid; @@ -1754,7 +1759,7 @@ void UNiagaraDataInterfaceStaticMesh::RandomTriCoord(FVectorVMContext& Context) FNDIOutputParam OutTri(Context); FNDIOutputParam OutBary(Context); - check(InstData->Mesh); + check(InstData->StaticMesh.IsValid()); TRefCountPtr Res = InstData->GetCurrentFirstLOD(); FIndexArrayView Indices = Res->IndexBuffer.GetArrayView(); for (int32 i = 0; i < Context.NumInstances; ++i) @@ -1791,7 +1796,7 @@ void UNiagaraDataInterfaceStaticMesh::RandomTriCoordVertexColorFiltered(FVectorV // Handle no mesh case //TODO: Maybe figure out a good way to stub this in bindings to prevent the branch - if (!InstData->Mesh) + if (!InstData->bMeshValid) { for (int32 i = 0; i < Context.NumInstances; ++i) { @@ -1868,7 +1873,7 @@ void UNiagaraDataInterfaceStaticMesh::RandomTriCoordOnSection(FVectorVMContext& FNDIOutputParam OutTri(Context); FNDIOutputParam OutBary(Context); - check(InstData->Mesh); + check(InstData->bMeshValid); TRefCountPtr Res = InstData->GetCurrentFirstLOD(); FIndexArrayView Indices = Res->IndexBuffer.GetArrayView(); const int32 MaxSection = Res->Sections.Num() - 1; @@ -1909,7 +1914,7 @@ void UNiagaraDataInterfaceStaticMesh::GetTriCoordPosition(FVectorVMContext& Cont // Handle no mesh case //TODO: Maybe figure out a good way to stub this in bindings to prevent the branch - if (!InstData->Mesh) + if (!InstData->bMeshValid) { FVector Pos(0.0f); TransformHandler.TransformPosition(Pos, InstData->Transform); @@ -1954,7 +1959,7 @@ void UNiagaraDataInterfaceStaticMesh::GetTriCoordNormal(FVectorVMContext& Contex // Handle no mesh case //TODO: Maybe figure out a good way to stub this in bindings to prevent the branch - if (!InstData->Mesh) + if (!InstData->bMeshValid) { for (int32 i = 0; i < Context.NumInstances; ++i) { @@ -1998,7 +2003,7 @@ void UNiagaraDataInterfaceStaticMesh::GetTriCoordTangents(FVectorVMContext& Cont // Handle no mesh case //TODO: Maybe figure out a good way to stub this in bindings to prevent the branch - if (!InstData->Mesh) + if (!InstData->bMeshValid) { for (int32 i = 0; i < Context.NumInstances; ++i) { @@ -2039,7 +2044,7 @@ void UNiagaraDataInterfaceStaticMesh::GetTriCoordColor(FVectorVMContext& Context FNDIOutputParam OutColor(Context); TRefCountPtr Res; - if (InstData->Mesh) + if (InstData->bMeshValid) { Res = InstData->GetCurrentFirstLOD(); } @@ -2086,7 +2091,7 @@ void UNiagaraDataInterfaceStaticMesh::GetTriCoordUV(FVectorVMContext& Context) // Handle no mesh case //TODO: Maybe figure out a good way to stub this in bindings to prevent the branch - if (!InstData->Mesh) + if (!InstData->bMeshValid) { for (int32 i = 0; i < Context.NumInstances; ++i) { @@ -2126,7 +2131,7 @@ void UNiagaraDataInterfaceStaticMesh::GetTriCoordPositionAndVelocity(FVectorVMCo // Handle no mesh case //TODO: Maybe figure out a good way to stub this in bindings to prevent the branch - if (!InstData->Mesh) + if (!InstData->bMeshValid) { FVector WSPos = InstData->Transform.TransformPosition(FVector(0.0f)); for (int32 i = 0; i < Context.NumInstances; ++i) @@ -2260,7 +2265,7 @@ void UNiagaraDataInterfaceStaticMesh::GetVertexPosition(FVectorVMContext& Contex // Handle no mesh case //TODO: Maybe figure out a good way to stub this in bindings to prevent the branch - if (!InstData->Mesh) + if (!InstData->bMeshValid) { FVector WSPos = InstData->Transform.TransformPosition(FVector(0.0f)); for (int32 i = 0; i < Context.NumInstances; ++i) @@ -3247,13 +3252,12 @@ bool FDynamicVertexColorFilterData::Init(FNDIStaticMesh_InstanceData* Owner) { TrianglesSortedByVertexColor.Empty(); VertexColorToTriangleStart.AddDefaulted(256); - check(Owner->Mesh); + check(Owner->bMeshValid); TRefCountPtr Res = Owner->GetCurrentFirstLOD(); - if (Res->VertexBuffers.ColorVertexBuffer.GetNumVertices() == 0) { - UE_LOG(LogNiagara, Log, TEXT("Cannot initialize vertex color filter data for a mesh with no color data - %s"), *Owner->Mesh->GetFullName()); + UE_LOG(LogNiagara, Log, TEXT("Cannot initialize vertex color filter data for a mesh with no color data - %s"), *GetFullNameSafe(Owner->StaticMesh.Get())); return false; } @@ -3296,11 +3300,11 @@ TSharedPtr FNDI_StaticMesh_GeneratedData::GetDyna FScopeLock Lock(&CriticalSection); check(Instance); - check(Instance->Mesh); + check(Instance->bMeshValid); TSharedPtr Ret = nullptr; - uint32 FilterDataHash = GetTypeHash(Instance->Mesh); + uint32 FilterDataHash = GetTypeHash(Instance->StaticMesh.Get()); for (int32 ValidSec : Instance->GetValidSections()) { FilterDataHash = HashCombine(GetTypeHash(ValidSec), FilterDataHash); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceTexture.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceTexture.cpp index df6fd1b8d2a8..0ee5a0b41e06 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceTexture.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceTexture.cpp @@ -384,7 +384,8 @@ public: FRHIComputeShader* ComputeShaderRHI = Context.Shader.GetComputeShader(); FNiagaraDataInterfaceProxyTexture* TextureDI = static_cast(Context.DataInterface); - if (TextureDI && TextureDI->TextureRHI) + + if (TextureDI && TextureDI->TextureReferenceRHI.IsValid()) { FRHISamplerState* SamplerStateRHI = TextureDI->SamplerStateRHI; if (!SamplerStateRHI) @@ -393,13 +394,14 @@ public: // are initalized in UNiagaraDataInterfaceTexture::PushToRenderThread(). SamplerStateRHI = TStaticSamplerState::GetRHI(); } + SetTextureParameter( RHICmdList, ComputeShaderRHI, TextureParam, SamplerParam, SamplerStateRHI, - TextureDI->TextureRHI + TextureDI->TextureReferenceRHI->GetReferencedTexture() ); SetShaderValue(RHICmdList, ComputeShaderRHI, Dimensions, TextureDI->TexDims); } @@ -442,10 +444,18 @@ void UNiagaraDataInterfaceTexture::PushToRenderThread() ENQUEUE_RENDER_COMMAND(FPushDITextureToRT) ( - [RT_Proxy, RT_Resource=Texture ? Texture->Resource : nullptr, RT_TexDims](FRHICommandListImmediate& RHICmdList) + [RT_Proxy, RT_Texture=Texture, RT_TexDims](FRHICommandListImmediate& RHICmdList) { - RT_Proxy->TextureRHI = RT_Resource ? RT_Resource->TextureRHI : nullptr; - RT_Proxy->SamplerStateRHI = RT_Resource ? RT_Resource->SamplerStateRHI : nullptr; + if (RT_Texture) + { + RT_Proxy->TextureReferenceRHI = RT_Texture->TextureReference.TextureReferenceRHI; + RT_Proxy->SamplerStateRHI = RT_Texture->Resource ? RT_Texture->Resource->SamplerStateRHI : nullptr; + } + else + { + RT_Proxy->TextureReferenceRHI = nullptr; + RT_Proxy->SamplerStateRHI = nullptr; + } RT_Proxy->TexDims = RT_TexDims; } ); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVector2DCurve.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVector2DCurve.cpp index b45791d9d9de..0e609fcfb431 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVector2DCurve.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVector2DCurve.cpp @@ -67,7 +67,7 @@ void UNiagaraDataInterfaceVector2DCurve::UpdateTimeRanges() LUTMinTime = FMath::Min(XCurve.GetNumKeys() > 0 ? XCurve.GetFirstKey().Time : LUTMinTime, LUTMinTime); LUTMinTime = FMath::Min(YCurve.GetNumKeys() > 0 ? YCurve.GetFirstKey().Time : LUTMinTime, LUTMinTime); - LUTMaxTime = FLT_MIN; + LUTMaxTime = -FLT_MAX; LUTMaxTime = FMath::Max(XCurve.GetNumKeys() > 0 ? XCurve.GetLastKey().Time : LUTMaxTime, LUTMaxTime); LUTMaxTime = FMath::Max(YCurve.GetNumKeys() > 0 ? YCurve.GetLastKey().Time : LUTMaxTime, LUTMaxTime); LUTInvTimeRange = 1.0f / (LUTMaxTime - LUTMinTime); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVector4Curve.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVector4Curve.cpp index bab1540d870e..e71b6b7a9d05 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVector4Curve.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVector4Curve.cpp @@ -76,7 +76,7 @@ void UNiagaraDataInterfaceVector4Curve::UpdateTimeRanges() LUTMinTime = FMath::Min(ZCurve.GetNumKeys() > 0 ? ZCurve.GetFirstKey().Time : LUTMinTime, LUTMinTime); LUTMinTime = FMath::Min(WCurve.GetNumKeys() > 0 ? WCurve.GetFirstKey().Time : LUTMinTime, LUTMinTime); - LUTMaxTime = FLT_MIN; + LUTMaxTime = -FLT_MAX; LUTMaxTime = FMath::Max(XCurve.GetNumKeys() > 0 ? XCurve.GetLastKey().Time : LUTMaxTime, LUTMaxTime); LUTMaxTime = FMath::Max(YCurve.GetNumKeys() > 0 ? YCurve.GetLastKey().Time : LUTMaxTime, LUTMaxTime); LUTMaxTime = FMath::Max(ZCurve.GetNumKeys() > 0 ? ZCurve.GetLastKey().Time : LUTMaxTime, LUTMaxTime); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVectorCurve.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVectorCurve.cpp index c65c900fd7a1..05b0f69c5441 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVectorCurve.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVectorCurve.cpp @@ -72,7 +72,7 @@ void UNiagaraDataInterfaceVectorCurve::UpdateTimeRanges() LUTMinTime = FMath::Min(YCurve.GetNumKeys() > 0 ? YCurve.GetFirstKey().Time : LUTMinTime, LUTMinTime); LUTMinTime = FMath::Min(ZCurve.GetNumKeys() > 0 ? ZCurve.GetFirstKey().Time : LUTMinTime, LUTMinTime); - LUTMaxTime = FLT_MIN; + LUTMaxTime = -FLT_MAX; LUTMaxTime = FMath::Max(XCurve.GetNumKeys() > 0 ? XCurve.GetLastKey().Time : LUTMaxTime, LUTMaxTime); LUTMaxTime = FMath::Max(YCurve.GetNumKeys() > 0 ? YCurve.GetLastKey().Time : LUTMaxTime, LUTMaxTime); LUTMaxTime = FMath::Max(ZCurve.GetNumKeys() > 0 ? ZCurve.GetLastKey().Time : LUTMaxTime, LUTMaxTime); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataSet.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataSet.cpp index 2728134a0208..6af68468504d 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataSet.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataSet.cpp @@ -99,9 +99,26 @@ FNiagaraDataSet::FNiagaraDataSet() FNiagaraDataSet::~FNiagaraDataSet() { + CompiledData.Reset(); + // int32 CurrBytes = RenderDataFloat.NumBytes + RenderDataInt.NumBytes; // DEC_MEMORY_STAT_BY(STAT_NiagaraVBMemory, CurrBytes); - ReleaseBuffers(); + if (Data.Num() > 0) + { + for (FNiagaraDataBuffer* Buffer : Data) + { + Buffer->Destroy(); + } + Data.Empty(); + } + + if (GPUFreeIDs.Buffer) + { + check(IsInRenderingThread()); + GPUFreeIDs.Release(); + } + + GPUNumAllocatedIDs = 0; } void FNiagaraDataSet::Init(const FNiagaraDataSetCompiledData* InDataSetCompiledData) @@ -161,26 +178,6 @@ void FNiagaraDataSet::ResetBuffersInternal() EndSimulate(); } -void FNiagaraDataSet::ReleaseBuffers() -{ - CheckCorrectThread(); - if (Data.Num() > 0) - { - for (FNiagaraDataBuffer* Buffer : Data) - { - Buffer->Destroy(); - } - Data.Empty(); - } - - if (GPUFreeIDs.Buffer) - { - GPUFreeIDs.Release(); - } - - GPUNumAllocatedIDs = 0; -} - FNiagaraDataBuffer& FNiagaraDataSet::BeginSimulate(bool bResetDestinationData) { //CheckCorrectThread(); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEffectType.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEffectType.cpp index 510cc60aa2c6..bad872f4836f 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEffectType.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEffectType.cpp @@ -1,7 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraEffectType.h" -#include "NiagaraModule.h" #include "NiagaraCommon.h" +#include "NiagaraCustomVersion.h" #include "NiagaraSystem.h" //In an effort to cut the impact of runtime perf tracking, I limit the number of fames we actually sample on. diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitter.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitter.cpp index 395cb8e3bf77..bb4f51954b61 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitter.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitter.cpp @@ -350,6 +350,10 @@ void UNiagaraEmitter::PostLoad() { RendererProperties.RemoveAt(RendererIndex); } + else + { + RendererProperties[RendererIndex]->ConditionalPostLoad(); + } } for (int32 SimulationStageIndex = SimulationStages.Num() - 1; SimulationStageIndex >= 0; --SimulationStageIndex) @@ -358,6 +362,10 @@ void UNiagaraEmitter::PostLoad() { SimulationStages.RemoveAt(SimulationStageIndex); } + else + { + SimulationStages[SimulationStageIndex]->ConditionalPostLoad(); + } } const int32 NiagaraVer = GetLinkerCustomVersion(FNiagaraCustomVersion::GUID); @@ -576,7 +584,13 @@ void UNiagaraEmitter::PostLoad() ResolveScalabilitySettings(); #if !UE_BUILD_SHIPPING - DebugSimName = GetFullName(); + DebugSimName.Empty(); + if (const UNiagaraSystem* SystemOwner = Cast(GetOuter())) + { + DebugSimName = SystemOwner->GetName(); + DebugSimName.AppendChar(':'); + } + DebugSimName.Append(GetName()); #endif } @@ -1015,14 +1029,19 @@ void UNiagaraEmitter::CacheFromCompiledData(const FNiagaraDataSetCompiledData* C void UNiagaraEmitter::CacheFromShaderCompiled() { + bRequiresViewUniformBuffer = false; if (GPUComputeScript && (SimTarget == ENiagaraSimTarget::GPUComputeSim)) { if (const FNiagaraShaderScript* NiagaraShaderScript = GPUComputeScript->GetRenderThreadScript()) { - FNiagaraShaderRef NiagaraShaderRef = NiagaraShaderScript->GetShaderGameThread(); - if (NiagaraShaderRef.IsValid()) + for (int i=0; i < NiagaraShaderScript->GetNumPermutations(); ++i) { - bRequiresViewUniformBuffer = NiagaraShaderRef->ViewUniformBufferParam.IsBound(); + FNiagaraShaderRef NiagaraShaderRef = NiagaraShaderScript->GetShaderGameThread(i); + if (NiagaraShaderRef.IsValid() && NiagaraShaderRef->ViewUniformBufferParam.IsBound()) + { + bRequiresViewUniformBuffer = true; + break; + } } } } @@ -1285,6 +1304,41 @@ bool UNiagaraEmitter::UsesCollection(const class UNiagaraParameterCollection* Co return false; } + +bool UNiagaraEmitter::CanObtainParticleAttribute(const FNiagaraVariableBase& InVar) const +{ + if (SpawnScriptProps.Script) + return SpawnScriptProps.Script->GetVMExecutableData().Attributes.Contains(InVar); + return false; +} +bool UNiagaraEmitter::CanObtainEmitterAttribute(const FNiagaraVariableBase& InVarWithUniqueNameNamespace) const +{ + const UNiagaraSystem* Sys = GetTypedOuter(); + if (Sys) + { + return Sys->CanObtainEmitterAttribute(InVarWithUniqueNameNamespace); + } + return false; +} +bool UNiagaraEmitter::CanObtainSystemAttribute(const FNiagaraVariableBase& InVar) const +{ + const UNiagaraSystem* Sys = GetTypedOuter(); + if (Sys) + { + return Sys->CanObtainSystemAttribute(InVar); + } + return false; +} +bool UNiagaraEmitter::CanObtainUserVariable(const FNiagaraVariableBase& InVar) const +{ + const UNiagaraSystem* Sys = GetTypedOuter(); + if (Sys) + { + return Sys->CanObtainUserVariable(InVar); + } + return false; +} + FString UNiagaraEmitter::GetUniqueEmitterName()const { return UniqueEmitterName; @@ -1394,8 +1448,9 @@ void UNiagaraEmitter::UpdateFromMergedCopy(const INiagaraMergeManager& MergeMana MergedRenderer->OnChanged().AddUObject(this, &UNiagaraEmitter::RendererChanged); } - // Copy parent scratch pad scripts. + // Copy scratch pad scripts. ParentScratchPadScripts.Empty(); + ScratchPadScripts.Empty(); for (UNiagaraScript* MergedParentScratchPadScript : MergedEmitter->ParentScratchPadScripts) { @@ -1403,6 +1458,12 @@ void UNiagaraEmitter::UpdateFromMergedCopy(const INiagaraMergeManager& MergeMana ParentScratchPadScripts.Add(MergedParentScratchPadScript); } + for (UNiagaraScript* MergedScratchPadScript : MergedEmitter->ScratchPadScripts) + { + ReouterMergedObject(this, MergedScratchPadScript); + ScratchPadScripts.Add(MergedScratchPadScript); + } + SetEditorData(MergedEmitter->GetEditorData()); // Update the change id since we don't know what's changed. @@ -1424,6 +1485,14 @@ void UNiagaraEmitter::SyncEmitterAlias(const FString& InOldName, const FString& Script->Modify(false); Script->SyncAliases(RenameMap); } + + for (UNiagaraRendererProperties* Renderer : RendererProperties) + { + if (Renderer) + { + Renderer->RenameEmitter(*InOldName, this); + } + } } #endif bool UNiagaraEmitter::SetUniqueEmitterName(const FString& InName) diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterHandle.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterHandle.cpp index f46a2721c8e8..c8fa863fc208 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterHandle.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterHandle.cpp @@ -5,10 +5,7 @@ #include "NiagaraEmitter.h" #include "NiagaraScriptSourceBase.h" #include "NiagaraCommon.h" -#include "NiagaraDataInterface.h" -#include "NiagaraModule.h" - -#include "Modules/ModuleManager.h" +#include "NiagaraCustomVersion.h" const FNiagaraEmitterHandle FNiagaraEmitterHandle::InvalidHandle; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstance.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstance.cpp index b57dd54f26f8..80fc2bbab041 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstance.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstance.cpp @@ -15,6 +15,7 @@ #include "NiagaraScriptExecutionContext.h" #include "NiagaraWorldManager.h" #include "NiagaraSimulationStageBase.h" +#include "NiagaraComponentSettings.h" DECLARE_DWORD_COUNTER_STAT(TEXT("Num Custom Events"), STAT_NiagaraNumCustomEvents, STATGROUP_Niagara); @@ -87,6 +88,14 @@ static FAutoConsoleVariableRef CVarMaxNiagaraGPUParticlesSpawnPerFrame( ECVF_Default ); +static int32 GNiagaraUseSuppressEmitterList = 0; +static FAutoConsoleVariableRef CVarNiagaraUseEmitterSupressList( + TEXT("fx.Niagara.UseEmitterSuppressList"), + GNiagaraUseSuppressEmitterList, + TEXT("When an emitter is activated we will check the surpession list."), + ECVF_Default +); + ////////////////////////////////////////////////////////////////////////// template @@ -217,11 +226,32 @@ void FNiagaraEmitterInstance::Dump()const bool FNiagaraEmitterInstance::IsAllowedToExecute() const { - const FNiagaraEmitterHandle& EmitterHandle = GetEmitterHandle(); - return EmitterHandle.GetIsEnabled() && - CachedEmitter->IsAllowedByScalability() && - // TODO: fall back to CPU sim instead once we have scalability functionality to do so - (CachedEmitter->SimTarget != ENiagaraSimTarget::GPUComputeSim || (Batcher && FNiagaraUtilities::AllowGPUParticles(Batcher->GetShaderPlatform()))); + if (!GetEmitterHandle().GetIsEnabled() + || !CachedEmitter->IsAllowedByScalability()) + { + return false; + } + + if (GNiagaraUseSuppressEmitterList != 0) + { + if (const UNiagaraComponentSettings* ComponentSettings = GetDefault()) + { + FNiagaraEmitterNameSettingsRef Ref; + if (const UNiagaraSystem* ParentSystem = ParentSystemInstance->GetSystem()) + { + Ref.SystemName = ParentSystem->GetFName(); + } + Ref.EmitterName = CachedEmitter->GetUniqueEmitterName(); + if (ComponentSettings->SuppressEmitterList.Contains(Ref)) + { + return false; + } + } + } + + // TODO: fall back to CPU sim instead once we have scalability functionality to do so + return (CachedEmitter->SimTarget != ENiagaraSimTarget::GPUComputeSim + || (Batcher && FNiagaraUtilities::AllowGPUParticles(Batcher->GetShaderPlatform()))); } void FNiagaraEmitterInstance::Init(int32 InEmitterIdx, FNiagaraSystemInstanceID InSystemInstanceID) @@ -427,6 +457,13 @@ void FNiagaraEmitterInstance::Init(int32 InEmitterIdx, FNiagaraSystemInstanceID ScriptDefinedDataInterfaceParameters.Bind(&UpdateExecContext.Parameters); UpdateExecContext.Parameters.UnbindFromSourceStores(); + if (GPUExecContext) + { + SystemScriptDefinedDataInterfaceParameters.Bind(&GPUExecContext->CombinedParamStore); + ScriptDefinedDataInterfaceParameters.Bind(&GPUExecContext->CombinedParamStore); + GPUExecContext->CombinedParamStore.UnbindFromSourceStores(); + } + if (EventInstanceData.IsValid()) { for (FNiagaraScriptExecutionContext& EventContext : EventInstanceData->EventExecContexts) @@ -450,8 +487,39 @@ void FNiagaraEmitterInstance::Init(int32 InEmitterIdx, FNiagaraSystemInstanceID Info.EventData = nullptr; } } + + + // We may need to populate bindings that will be used in rendering + bool bAnyRendererBindingsAdded = false; + for (UNiagaraRendererProperties* Props : CachedEmitter->GetRenderers()) + { + if (Props && Props->bIsEnabled) + { + bAnyRendererBindingsAdded |= Props->PopulateRequiredBindings(RendererBindings); + } + } + + if (bAnyRendererBindingsAdded) + { + if (ParentSystemInstance) + ParentSystemInstance->GetInstanceParameters().Bind(&RendererBindings); + + SystemScriptDefinedDataInterfaceParameters.Bind(&RendererBindings); + ScriptDefinedDataInterfaceParameters.Bind(&RendererBindings); + + if (GPUExecContext && CachedEmitter->SimTarget == ENiagaraSimTarget::GPUComputeSim) + { + GPUExecContext->CombinedParamStore.Bind(&RendererBindings); + } + } } + if (GPUExecContext) + { + GPUExecContext->BakeVariableNamesForIterationLookup(); + } + + MaxInstanceCount = CachedEmitter->GetMaxInstanceCount(); ParticleDataSet->SetMaxInstanceCount(MaxInstanceCount); @@ -726,15 +794,30 @@ void FNiagaraEmitterInstance::BindParameters(bool bExternalOnly) if (CachedEmitter->SimTarget == ENiagaraSimTarget::GPUComputeSim) { - SpawnExecContext.Parameters.Bind(&GPUExecContext->CombinedParamStore); - UpdateExecContext.Parameters.Bind(&GPUExecContext->CombinedParamStore); + // I don't think we need to bind the spawn parameters any more as this is purely bound to the GPU store + //SpawnExecContext.Parameters.Bind(&GPUExecContext->CombinedParamStore); + //UpdateExecContext.Parameters.Bind(&GPUExecContext->CombinedParamStore); + InstanceParams.Bind(&GPUExecContext->CombinedParamStore); +#if WITH_EDITORONLY_DATA + CachedEmitter->SpawnScriptProps.Script->RapidIterationParameters.Bind(&SpawnExecContext.Parameters); + CachedEmitter->UpdateScriptProps.Script->RapidIterationParameters.Bind(&UpdateExecContext.Parameters); for (int32 i = 0; i < CachedEmitter->GetSimulationStages().Num(); i++) { CachedEmitter->GetSimulationStages()[i]->Script->RapidIterationParameters.Bind(&GPUExecContext->CombinedParamStore); } +#endif } } + + //if (bAnyRendererBindingsAdded) + { + if (ParentSystemInstance) + ParentSystemInstance->GetInstanceParameters().Bind(&RendererBindings); + + //SystemScriptDefinedDataInterfaceParameters.Bind(&RendererBindings); + //ScriptDefinedDataInterfaceParameters.Bind(&RendererBindings); + } } const FNiagaraEmitterHandle& FNiagaraEmitterInstance::GetEmitterHandle() const @@ -1859,6 +1942,43 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) INC_DWORD_STAT_BY(STAT_NiagaraNumParticles, Data.GetCurrentDataChecked().GetNumInstances()); } +bool FNiagaraEmitterInstance::GetBoundRendererValue_GT(const FNiagaraVariableBase& InBaseVar, const FNiagaraVariableBase& InSubVar, void* OutValueData) const +{ + + if (InBaseVar.IsDataInterface()) + { + UNiagaraDataInterface* UObj = RendererBindings.GetDataInterface(InBaseVar); + if (UObj && InSubVar.GetName() == NAME_None) + { + UNiagaraDataInterface** Var = (UNiagaraDataInterface**)OutValueData; + *Var = UObj; + return true; + } + else if (UObj && UObj->CanExposeVariables()) + { + void* PerInstanceData = ParentSystemInstance->FindDataInterfaceInstanceData(UObj); + return UObj->GetExposedVariableValue(InSubVar, PerInstanceData, ParentSystemInstance, OutValueData); + } + } + else if (InBaseVar.IsUObject()) + { + UObject* UObj = RendererBindings.GetUObject(InBaseVar); + UObject** Var = (UObject**)OutValueData; + *Var = UObj; + return true; + } + else + { + const uint8* Data = RendererBindings.GetParameterData(InBaseVar); + if (Data && InBaseVar.GetSizeInBytes() != 0) + { + memcpy(OutValueData, Data, InBaseVar.GetSizeInBytes()); + return true; + } + } + return false; +} + /** Calculate total number of spawned particles from events; these all come from event handler script with the SpawnedParticles execution mode * We get the counts ahead of event processing time so we only have to allocate new particles once * TODO: augment for multiple spawning event scripts @@ -1907,10 +2027,11 @@ void FNiagaraEmitterInstance::SetExecutionState(ENiagaraExecutionState InState) UE_LOG(LogNiagara, Log, TEXT("Emitter \"%s\" change state N O O O O O "), *GetEmitterHandle().GetName().ToString()); }*/ if (ensureMsgf(InState >= ENiagaraExecutionState::Active && InState < ENiagaraExecutionState::Num, - TEXT("Setting invalid emitter execution state! %d\nEmitter=%s\nComponent=%s"), + TEXT("Setting invalid emitter execution state! %d\nEmitter=%s\nSystem=%s\nComponent=%s"), (int32)InState, - *CachedEmitter->GetFullName(), - ParentSystemInstance && ParentSystemInstance->GetComponent() ? *ParentSystemInstance->GetComponent()->GetFullName() : TEXT("nullptr")) + *GetFullNameSafe(CachedEmitter), + *GetFullNameSafe(ParentSystemInstance ? ParentSystemInstance->GetSystem() : nullptr), + *GetFullNameSafe(ParentSystemInstance ? ParentSystemInstance->GetAttachComponent() : nullptr)) ) { //We can't move out of disabled without a proper reinit. @@ -1934,13 +2055,11 @@ UObject* FNiagaraEmitterInstance::FindBinding(const FNiagaraVariable& InVariable return nullptr; } - FNiagaraSystemInstance* SystemInstance = GetParentSystemInstance(); - if (SystemInstance) - { - UNiagaraComponent* Component = SystemInstance->GetComponent(); - if (Component) + if (FNiagaraSystemInstance* SystemInstance = GetParentSystemInstance()) + { + if (FNiagaraUserRedirectionParameterStore* OverrideParameters = SystemInstance->GetOverrideParameters()) { - return Component->GetOverrideParameters().GetUObject(InVariable); + return OverrideParameters->GetUObject(InVariable); } } return nullptr; @@ -1953,13 +2072,11 @@ UNiagaraDataInterface* FNiagaraEmitterInstance::FindDataInterface(const FNiagara return nullptr; } - FNiagaraSystemInstance* SystemInstance = GetParentSystemInstance(); - if (SystemInstance) - { - UNiagaraComponent* Component = SystemInstance->GetComponent(); - if (Component) + if (FNiagaraSystemInstance* SystemInstance = GetParentSystemInstance()) + { + if (FNiagaraUserRedirectionParameterStore* OverrideParameters = SystemInstance->GetOverrideParameters()) { - return Component->GetOverrideParameters().GetDataInterface(InVariable); + return OverrideParameters->GetDataInterface(InVariable); } } return nullptr; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstanceBatcher.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstanceBatcher.cpp index 28eadec96eb8..098d452cb68d 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstanceBatcher.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstanceBatcher.cpp @@ -69,18 +69,39 @@ static FAutoConsoleVariableRef CVarNiagaraGpuSubmitCommandHint( ECVF_Default ); -//-TODO: FIX THIS POST MERGE -int32 GNiagaraGpuLowLatencyTranslucencyEnabled = 0; +int32 GNiagaraGpuLowLatencyTranslucencyEnabled = 1; static FAutoConsoleVariableRef CVarNiagaraGpuLowLatencyTranslucencyEnabled( TEXT("fx.NiagaraGpuLowLatencyTranslucencyEnabled"), GNiagaraGpuLowLatencyTranslucencyEnabled, - TEXT("When enabled we will create the final data buffer for translucent objects ahead of time\n") + TEXT("When enabled translucent materials can use the current frames simulation data no matter which tick pass Niagara uses.\n") TEXT("This can result in an additional data buffer being required but will reduce any latency when using view uniform buffer / depth buffer / distance fields / etc"), ECVF_Default ); const FName NiagaraEmitterInstanceBatcher::Name(TEXT("NiagaraEmitterInstanceBatcher")); +namespace NiagaraEmitterInstanceBatcherLocal +{ + static ETickStage CalculateTickStage(const FNiagaraGPUSystemTick& Tick) + { + if (!GNiagaraAllowTickBeforeRender || Tick.bRequiresDistanceFieldData || Tick.bRequiresDepthBuffer) + { + return ETickStage::PostOpaqueRender; + } + + if (Tick.bRequiresEarlyViewData) + { + return ETickStage::PostInitViews; + } + + if (Tick.bRequiresViewUniformBuffer) + { + return ETickStage::PostOpaqueRender; + } + return ETickStage::PreInitViews; + } +} + FFXSystemInterface* NiagaraEmitterInstanceBatcher::GetInterface(const FName& InName) { return InName == Name ? this : nullptr; @@ -155,6 +176,14 @@ void NiagaraEmitterInstanceBatcher::InstanceDeallocated_RenderThread(const FNiag FNiagaraGPUSystemTick& Tick = Ticks_RT[iTick]; if (Tick.SystemInstanceID == InstanceID) { + ensure(NumTicksThatRequireDistanceFieldData >= (Tick.bRequiresDistanceFieldData ? 1u : 0u)); + ensure(NumTicksThatRequireDepthBuffer >= (Tick.bRequiresDepthBuffer ? 1u : 0u)); + ensure(NumTicksThatRequireEarlyViewData >= (Tick.bRequiresEarlyViewData ? 1u : 0u)); + + NumTicksThatRequireDistanceFieldData -= Tick.bRequiresDistanceFieldData ? 1 : 0; + NumTicksThatRequireDepthBuffer -= Tick.bRequiresDepthBuffer ? 1 : 0; + NumTicksThatRequireEarlyViewData -= Tick.bRequiresEarlyViewData ? 1 : 0; + //-OPT: Since we can't RemoveAtSwap (due to ordering issues) if may be better to not remove and flag as dead Tick.Destroy(); Ticks_RT.RemoveAt(iTick); @@ -185,28 +214,19 @@ void NiagaraEmitterInstanceBatcher::BuildConstantBuffers(FNiagaraGPUSystemTick& } int32 BoundParameterCounts[FNiagaraGPUSystemTick::UBT_NumTypes][2]; - for (int32 i = 0; i < FNiagaraGPUSystemTick::UBT_NumTypes; ++i) - { - for (int32 j = 0; j < 2; ++j) - { - BoundParameterCounts[i][j] = 0; - } - } + FMemory::Memzero(BoundParameterCounts); for (uint32 CountIt = 0; CountIt < Tick.Count; ++CountIt) { FNiagaraComputeInstanceData& EmitterData = EmittersData[CountIt]; - const FNiagaraShaderRef& Shader = EmitterData.Context->GPUScript_RT->GetShader(); - const int32 HasExternalConstants = EmitterData.Context->ExternalCBufferLayout->UBLayout.ConstantBufferSize > 0 ? 1 : 0; - for (int32 InterpIt = 0; InterpIt < (HasInterpolationParameters ? 2 : 1); ++InterpIt) { - BoundParameterCounts[FNiagaraGPUSystemTick::UBT_Global][InterpIt] += Shader->GlobalConstantBufferParam[InterpIt].IsBound() ? 1 : 0; - BoundParameterCounts[FNiagaraGPUSystemTick::UBT_System][InterpIt] += Shader->SystemConstantBufferParam[InterpIt].IsBound() ? 1 : 0; - BoundParameterCounts[FNiagaraGPUSystemTick::UBT_Owner][InterpIt] += Shader->OwnerConstantBufferParam[InterpIt].IsBound() ? 1 : 0; - BoundParameterCounts[FNiagaraGPUSystemTick::UBT_Emitter][InterpIt] += Shader->EmitterConstantBufferParam[InterpIt].IsBound() ? 1 : 0; - BoundParameterCounts[FNiagaraGPUSystemTick::UBT_External][InterpIt] += Shader->ExternalConstantBufferParam[InterpIt].IsBound() ? 1 : 0; + BoundParameterCounts[FNiagaraGPUSystemTick::UBT_Global][InterpIt] += EmitterData.Context->GPUScript_RT->IsGlobalConstantBufferUsed_RenderThread(InterpIt) ? 1 : 0; + BoundParameterCounts[FNiagaraGPUSystemTick::UBT_System][InterpIt] += EmitterData.Context->GPUScript_RT->IsSystemConstantBufferUsed_RenderThread(InterpIt) ? 1 : 0; + BoundParameterCounts[FNiagaraGPUSystemTick::UBT_Owner][InterpIt] += EmitterData.Context->GPUScript_RT->IsOwnerConstantBufferUsed_RenderThread(InterpIt) ? 1 : 0; + BoundParameterCounts[FNiagaraGPUSystemTick::UBT_Emitter][InterpIt] += EmitterData.Context->GPUScript_RT->IsEmitterConstantBufferUsed_RenderThread(InterpIt) ? 1 : 0; + BoundParameterCounts[FNiagaraGPUSystemTick::UBT_External][InterpIt] += EmitterData.Context->GPUScript_RT->IsExternalConstantBufferUsed_RenderThread(InterpIt) ? 1 : 0; } } @@ -280,10 +300,10 @@ void NiagaraEmitterInstanceBatcher::GiveSystemTick_RenderThread(FNiagaraGPUSyste // @todo REMOVE THIS HACK if (GFrameNumberRenderThread > LastFrameThatDrainedData + GNiagaraGpuMaxQueuedRenderFrames) - { - Tick.Destroy(); - return; - } + { + Tick.Destroy(); + return; + } // Now we consume DataInterface instance data. if (Tick.DIInstanceData) @@ -305,6 +325,10 @@ void NiagaraEmitterInstanceBatcher::GiveSystemTick_RenderThread(FNiagaraGPUSyste // This is making a copy of Tick. That structure is small now and we take a copy to avoid // making a bunch of small allocations on the game thread. We may need to revisit this. Ticks_RT.Add(Tick); + + NumTicksThatRequireDistanceFieldData += Tick.bRequiresDistanceFieldData ? 1 : 0; + NumTicksThatRequireDepthBuffer += Tick.bRequiresDepthBuffer ? 1 : 0; + NumTicksThatRequireEarlyViewData += Tick.bRequiresEarlyViewData ? 1 : 0; } void NiagaraEmitterInstanceBatcher::ReleaseInstanceCounts_RenderThread(FNiagaraComputeExecutionContext* ExecContext, FNiagaraDataSet* DataSet) @@ -324,6 +348,16 @@ void NiagaraEmitterInstanceBatcher::ReleaseInstanceCounts_RenderThread(FNiagaraC void NiagaraEmitterInstanceBatcher::FinishDispatches() { ReleaseTicks(); + + NumTicksThatRequireDistanceFieldData = 0; + NumTicksThatRequireDepthBuffer = 0; + NumTicksThatRequireEarlyViewData = 0; + + for (int32 iTickStage=0; iTickStage < (int)ETickStage::Max; ++iTickStage) + { + ContextsPerStage[iTickStage].Reset(); + TicksPerStage[iTickStage].Reset(); + } } void NiagaraEmitterInstanceBatcher::ReleaseTicks() @@ -343,7 +377,7 @@ bool NiagaraEmitterInstanceBatcher::UseOverlapCompute() return !IsMobilePlatform(ShaderPlatform) && GNiagaraOverlapCompute; } -bool NiagaraEmitterInstanceBatcher::ResetDataInterfaces(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData *Instance, FRHICommandList &RHICmdList, const FNiagaraShaderRef& ComputeShader ) const +bool NiagaraEmitterInstanceBatcher::ResetDataInterfaces(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData *Instance, FRHICommandList &RHICmdList, const FNiagaraShaderScript* ShaderScript) const { bool ValidSpawnStage = true; FNiagaraComputeExecutionContext* Context = Instance->Context; @@ -351,17 +385,16 @@ bool NiagaraEmitterInstanceBatcher::ResetDataInterfaces(const FNiagaraGPUSystemT // Reset all rw data interface data if (Tick.bNeedsReset) { + // Note: All stages will contain the same bindings so if they are valid for one they are valid for all, this could change in the future + const FNiagaraShaderRef ComputeShader = ShaderScript->GetShader(0); + uint32 InterfaceIndex = 0; for (FNiagaraDataInterfaceProxy* Interface : Instance->DataInterfaceProxies) { const FNiagaraDataInterfaceParamRef& DIParam = ComputeShader->GetDIParameters()[InterfaceIndex]; if (DIParam.Parameters.IsValid()) { - FNiagaraDataInterfaceSetArgs TmpContext; - TmpContext.Shader = ComputeShader; - TmpContext.DataInterface = Interface; - TmpContext.SystemInstance = Tick.SystemInstanceID; - TmpContext.Batcher = this; + const FNiagaraDataInterfaceArgs TmpContext(Interface, Tick.SystemInstanceID, this); Interface->ResetData(RHICmdList, TmpContext); } InterfaceIndex++; @@ -384,14 +417,7 @@ void NiagaraEmitterInstanceBatcher::PreStageInterface(const FNiagaraGPUSystemTic const FNiagaraDataInterfaceParamRef& DIParam = ComputeShader->GetDIParameters()[InterfaceIndex]; if (DIParam.Parameters.IsValid()) { - FNiagaraDataInterfaceSetArgs TmpContext; - TmpContext.Shader = ComputeShader; - TmpContext.DataInterface = Interface; - TmpContext.SystemInstance = Tick.SystemInstanceID; - TmpContext.Batcher = this; - TmpContext.SimulationStageIndex = SimulationStageIndex; - TmpContext.IsOutputStage = Instance->IsOutputStage(Interface, SimulationStageIndex); - TmpContext.IsIterationStage = Instance->IsIterationStage(Interface, SimulationStageIndex); + const FNiagaraDataInterfaceStageArgs TmpContext(Interface, Tick.SystemInstanceID, this, SimulationStageIndex, Instance->IsOutputStage(Interface, SimulationStageIndex), Instance->IsIterationStage(Interface, SimulationStageIndex)); Interface->PreStage(RHICmdList, TmpContext); } InterfaceIndex++; @@ -406,34 +432,25 @@ void NiagaraEmitterInstanceBatcher::PostStageInterface(const FNiagaraGPUSystemTi const FNiagaraDataInterfaceParamRef& DIParam = ComputeShader->GetDIParameters()[InterfaceIndex]; if (DIParam.Parameters.IsValid()) { - FNiagaraDataInterfaceSetArgs TmpContext; - TmpContext.ComputeInstanceData = Instance; - TmpContext.Shader = ComputeShader; - TmpContext.DataInterface = Interface; - TmpContext.SystemInstance = Tick.SystemInstanceID; - TmpContext.Batcher = this; - TmpContext.SimulationStageIndex = SimulationStageIndex; - TmpContext.IsOutputStage = Instance->IsOutputStage(Interface, SimulationStageIndex); - TmpContext.IsIterationStage = Instance->IsIterationStage(Interface, SimulationStageIndex); + const FNiagaraDataInterfaceStageArgs TmpContext(Interface, Tick.SystemInstanceID, this, SimulationStageIndex, Instance->IsOutputStage(Interface, SimulationStageIndex), Instance->IsIterationStage(Interface, SimulationStageIndex)); Interface->PostStage(RHICmdList, TmpContext); } InterfaceIndex++; } } -void NiagaraEmitterInstanceBatcher::PostSimulateInterface(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData* Instance, FRHICommandList& RHICmdList, const FNiagaraShaderRef& ComputeShader) const +void NiagaraEmitterInstanceBatcher::PostSimulateInterface(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData* Instance, FRHICommandList& RHICmdList, const FNiagaraShaderScript* ShaderScript) const { + // Note: All stages will contain the same bindings so if they are valid for one they are valid for all, this could change in the future + const FNiagaraShaderRef ComputeShader = ShaderScript->GetShader(0); + uint32 InterfaceIndex = 0; for (FNiagaraDataInterfaceProxy* Interface : Instance->DataInterfaceProxies) { const FNiagaraDataInterfaceParamRef& DIParam = ComputeShader->GetDIParameters()[InterfaceIndex]; if (DIParam.Parameters.IsValid()) { - FNiagaraDataInterfaceSetArgs TmpContext; - TmpContext.Shader = ComputeShader; - TmpContext.DataInterface = Interface; - TmpContext.SystemInstance = Tick.SystemInstanceID; - TmpContext.Batcher = this; + const FNiagaraDataInterfaceArgs TmpContext(Interface, Tick.SystemInstanceID, this); Interface->PostSimulate(RHICmdList, TmpContext); } InterfaceIndex++; @@ -497,9 +514,9 @@ void NiagaraEmitterInstanceBatcher::TransitionBuffers(FRHICommandList& RHICmdLis RHICmdList.TransitionResources(EResourceTransitionAccess::ERWBarrier, EResourceTransitionPipeline::EComputeToCompute, WriteBuffers.GetData(), WriteBuffers.Num()); } -void NiagaraEmitterInstanceBatcher::DispatchMultipleStages(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData* Instance, FRHICommandList& RHICmdList, FRHIUniformBuffer* ViewUniformBuffer, const FNiagaraShaderRef& ComputeShader) +void NiagaraEmitterInstanceBatcher::DispatchMultipleStages(const FNiagaraGPUSystemTick& Tick, FNiagaraComputeInstanceData* Instance, FRHICommandList& RHICmdList, FRHIUniformBuffer* ViewUniformBuffer, const FNiagaraShaderScript* ShaderScript) { - if (!ResetDataInterfaces(Tick, Instance, RHICmdList, ComputeShader)) + if (!ResetDataInterfaces(Tick, Instance, RHICmdList, ShaderScript)) { return; } @@ -530,13 +547,16 @@ void NiagaraEmitterInstanceBatcher::DispatchMultipleStages(const FNiagaraGPUSyst continue; } + const int32 PermutationId = ShaderScript->ShaderStageIndexToPermutationId_RenderThread(SimulationStageIndex); + const FNiagaraShaderRef ComputeShader = ShaderScript->GetShader(PermutationId); + PreStageInterface(Tick, Instance, RHICmdList, ComputeShader, SimulationStageIndex); TransitionBuffers(RHICmdList, ComputeShader, Instance, SimulationStageIndex, LastSource, bFreeIDTableTransitioned); if (!IterationInterface) { - Run(Tick, Instance, 0, Instance->SimStageData[SimulationStageIndex].Destination->GetNumInstances(), ComputeShader, RHICmdList, ViewUniformBuffer, Instance->SpawnInfo, false, DefaultSimulationStageIndex, SimulationStageIndex, nullptr, HasRunParticleStage); + Run(Tick, Instance, 0, Instance->SimStageData[SimulationStageIndex].DestinationNumInstances, ComputeShader, RHICmdList, ViewUniformBuffer, Instance->SpawnInfo, false, DefaultSimulationStageIndex, SimulationStageIndex, nullptr, HasRunParticleStage); HasRunParticleStage = true; } else @@ -554,26 +574,27 @@ void NiagaraEmitterInstanceBatcher::DispatchMultipleStages(const FNiagaraGPUSyst } } - PostSimulateInterface(Tick, Instance, RHICmdList, ComputeShader); + PostSimulateInterface(Tick, Instance, RHICmdList, ShaderScript); } else { + const FNiagaraShaderRef ComputeShader = ShaderScript->GetShader(0); + // run shader, sim and spawn in a single dispatch check(Instance->SimStageData.Num() > 0); TransitionBuffers(RHICmdList, ComputeShader, Instance, 0, LastSource, bFreeIDTableTransitioned); - Run(Tick, Instance, 0, Instance->SimStageData[0].Destination->GetNumInstances(), ComputeShader, RHICmdList, ViewUniformBuffer, Instance->SpawnInfo); + Run(Tick, Instance, 0, Instance->SimStageData[0].DestinationNumInstances, ComputeShader, RHICmdList, ViewUniformBuffer, Instance->SpawnInfo); } } -void NiagaraEmitterInstanceBatcher::ResizeBuffersAndGatherResources(FOverlappableTicks& OverlappableTick, FRHICommandList& RHICmdList, FNiagaraBufferArray& OutputGraphicsBuffers, FEmitterInstanceList& InstancesWithPersistentIDs) +void NiagaraEmitterInstanceBatcher::GatherResources(FOverlappableTicks& OverlappableTick, FRHICommandList& RHICmdList, FNiagaraBufferArray& OutputGraphicsBuffers, FEmitterInstanceList& InstancesWithPersistentIDs) { SCOPE_CYCLE_COUNTER(STAT_NiagaraGPUDispatchSetup_RT); - //UE_LOG(LogNiagara, Warning, TEXT("NiagaraEmitterInstanceBatcher::ResizeBuffersAndGatherResources: %0xP"), this); + const bool bReadbackPending = GPUInstanceCounterManager.HasPendingGPUReadback(); + for (FNiagaraGPUSystemTick* Tick : OverlappableTick) { - //UE_LOG(LogNiagara, Warning, TEXT("NiagaraEmitterInstanceBatcher::ResizeBuffersAndGatherResources Tick: %p Count: %d"), Tick, Tick->Count); - const uint32 DispatchCount = Tick->Count; const bool bIsFinalTick = Tick->bIsFinalTick; const bool bNeedsReset = Tick->bNeedsReset; @@ -583,158 +604,45 @@ void NiagaraEmitterInstanceBatcher::ResizeBuffersAndGatherResources(FOverlappabl { FNiagaraComputeInstanceData& Instance = Instances[Index]; FNiagaraComputeExecutionContext* Context = Instance.Context; - if ( Context == nullptr ) + if ( (Context == nullptr) || !Context->GPUScript_RT->IsShaderMapComplete_RenderThread() ) { continue; } - FNiagaraShaderRef Shader = Context->GPUScript_RT->GetShader(); - if ( Shader.IsNull() ) { - continue; - } - - const bool bRequiresPersistentIDs = Context->MainDataSet->RequiresPersistentIDs(); - - check(Instance.SimStageData.Num() == Context->MaxUpdateIterations); - - //The buffer containing current simulation state. - Instance.SimStageData[0].Source = Context->MainDataSet->GetCurrentData(); - - //The buffer we're going to write simulation results to. - //-TODO: FIX ME - //const bool bWritePreallocatedFinalData = bIsFinalTick && (Context->GetTranslucentDataToRender() != nullptr); - //Instance.DestinationData = bWritePreallocatedFinalData ? Context->GetTranslucentDataToRender() : &Context->MainDataSet->BeginSimulate(); - Instance.SimStageData[0].Destination = &Context->MainDataSet->BeginSimulate(); - - check(Instance.SimStageData[0].Source && Instance.SimStageData[0].Destination); - FNiagaraDataBuffer* CurrentData = Instance.SimStageData[0].Source; - FNiagaraDataBuffer* DestinationData = Instance.SimStageData[0].Destination; - - const uint32 MaxInstanceCount = Context->MainDataSet->GetMaxInstanceCount(); - const uint32 PrevNumInstances = bNeedsReset ? 0 : CurrentData->GetNumInstances(); - check(PrevNumInstances <= MaxInstanceCount && Context->ScratchMaxInstances <= MaxInstanceCount); - - // At this point we can finally clamp the number of instances we're trying to allocate to the max instance count. We don't need to adjust the - // information inside Instance.SpawnInfo, because that just stores the start indices for each spawner; they're still correct if we spawn - // fewer particles than expected, it just means that some spawners will not be used. - const uint32 NewNumInstances = FMath::Min(Instance.SpawnInfo.SpawnRateInstances + Instance.SpawnInfo.EventSpawnTotal + PrevNumInstances, MaxInstanceCount); - const uint32 AdjustedSpawnCount = (uint32)FMath::Max((int32)NewNumInstances - PrevNumInstances, 0); - - // We must assume all particles survive when allocating here. If this is not true, the read back in ResolveDatasetWrites will shrink the buffers. - const uint32 RequiredInstances = FMath::Max(PrevNumInstances, NewNumInstances); - - // We need an extra scratch instance. The code which computes the instance count cap knows about this, so it ensures that we stay within - // the buffer limit including this instance. - const uint32 AllocatedInstances = FMath::Max(RequiredInstances, Context->ScratchMaxInstances) + 1; - - if (bRequiresPersistentIDs) - { - Context->MainDataSet->AllocateGPUFreeIDs(AllocatedInstances, RHICmdList, FeatureLevel, Context->GetDebugSimName()); InstancesWithPersistentIDs.Add(&Instance); } - //-TODO: FIX ME - //if (bWritePreallocatedFinalData) - //{ - // // Ensure the allocated size is big enough to fit what we expect in - // ensureMsgf(DestinationData.GetNumInstancesAllocated() >= AllocatedInstances, TEXT("MaxInstance %d is less than calculate %d bNeedsReset %d Iterations %d Tick(%p) Instance(%p)"), DestinationData.GetNumInstancesAllocated(), AllocatedInstances, bNeedsReset, Context->MaxUpdateIterations, Tick, &Instance); - //} - //else - //{ - // ensureMsgf(Context->ScratchMaxInstances >= AllocatedInstances, TEXT("MaxInstance %d is less than calculate %d bNeedsReset %d Iterations %d Tick(%p) Instance(%p)"), Context->ScratchMaxInstances, AllocatedInstances, bNeedsReset, Context->MaxUpdateIterations, Tick, &Instance); - DestinationData->AllocateGPU(AllocatedInstances, GPUInstanceCounterManager, RHICmdList, FeatureLevel, Context->GetDebugSimName()); - //} - DestinationData->SetNumInstances(RequiredInstances); - DestinationData->SetNumSpawnedInstances(AdjustedSpawnCount); - - Instance.SimStageData[0].SourceCountOffset = Instance.SimStageData[0].Source->GetGPUInstanceCountBufferOffset(); - if (Instance.SimStageData[0].SourceCountOffset == INDEX_NONE) // It is possible that this has been queued for readback, taking ownership of the data. Use that instead. - { - Instance.SimStageData[0].SourceCountOffset = Context->EmitterInstanceReadback.GPUCountOffset; - } - Instance.SimStageData[0].DestinationCountOffset = Instance.SimStageData[0].Destination->GetGPUInstanceCountBufferOffset(); - - //UE_LOG(LogScript, Warning, TEXT("ResizeBuffersAndGatherResources [%d][%d] Run ReqInst: %d Cur: %p Dest: %p "), Index, 0, RequiredInstances, Instance.SimStageData[0].Source, Instance.SimStageData[0].Destination); - - Context->MainDataSet->EndSimulate(); - - // Go ahead and reserve the readback data... - //uint32 ComputeCountOffsetOverride = INDEX_NONE; - if (!GPUInstanceCounterManager.HasPendingGPUReadback() && Tick->bIsFinalTick) - { - // Now that the current data is not required anymore, stage it for readback. - if (CurrentData->GetNumInstances() && Context->EmitterInstanceReadback.GPUCountOffset == INDEX_NONE && CurrentData->GetGPUInstanceCountBufferOffset() != INDEX_NONE) - { - // Transfer the GPU instance counter ownership to the context. Note that a readback request will be performed later in the tick update, unless there's already a pending readback. - Context->EmitterInstanceReadback.GPUCountOffset = CurrentData->GetGPUInstanceCountBufferOffset(); - Context->EmitterInstanceReadback.CPUCount = CurrentData->GetNumInstances(); - CurrentData->ClearGPUInstanceCountBufferOffset(); - - //UE_LOG(LogNiagara, Log, TEXT("EmitterInstanceReadback.CPUCount dispatch %d Offset: %d"), Context->EmitterInstanceReadback.CPUCount, Context->EmitterInstanceReadback.GPUCountOffset); - } - } - - uint32 NumBufferIterations = 1; - if (Tick->NumInstancesWithSimStages > 0) - { - const uint32 NumStages = Instance.Context->MaxUpdateIterations; - if (NumStages > 1) - { - for (uint32 SimulationStageIndex = 0; SimulationStageIndex < NumStages; SimulationStageIndex++) - { - if (SimulationStageIndex != 0) - { - Instance.SimStageData[SimulationStageIndex].Source = Instance.SimStageData[SimulationStageIndex - 1].Source; - Instance.SimStageData[SimulationStageIndex].Destination = Instance.SimStageData[SimulationStageIndex - 1].Destination; - - Instance.SimStageData[SimulationStageIndex].SourceCountOffset = Instance.SimStageData[SimulationStageIndex - 1].SourceCountOffset; - Instance.SimStageData[SimulationStageIndex].DestinationCountOffset = Instance.SimStageData[SimulationStageIndex - 1].DestinationCountOffset; - } - - // Determine if the iteration is outputting to a custom data size - FNiagaraDataInterfaceProxy* IterationInterface = FindIterationInterface(&Instance, SimulationStageIndex); - - Instance.SimStageData[SimulationStageIndex].AlternateIterationSource = IterationInterface; - - if (IterationInterface && Context->SpawnStages.Num() > 0 && - ((Tick->bNeedsReset && !Context->SpawnStages.Contains(SimulationStageIndex)) || - (!Tick->bNeedsReset && Context->SpawnStages.Contains(SimulationStageIndex)))) - { - continue; - } - - if (!IterationInterface && SimulationStageIndex != 0) - { - // Go ahead and grab the write buffer, which may be too small, so make sure to resize it. - Instance.SimStageData[SimulationStageIndex].Source = Context->MainDataSet->GetCurrentData(); - DestinationData = &Context->MainDataSet->BeginSimulate(false); - Instance.SimStageData[SimulationStageIndex].Destination = DestinationData; - DestinationData->AllocateGPU(AllocatedInstances, GPUInstanceCounterManager, RHICmdList, FeatureLevel, Context->GetDebugSimName()); - DestinationData->SetNumInstances(RequiredInstances); - Instance.SimStageData[SimulationStageIndex].SourceCountOffset = Instance.SimStageData[SimulationStageIndex].Source->GetGPUInstanceCountBufferOffset(); - Instance.SimStageData[SimulationStageIndex].DestinationCountOffset = Instance.SimStageData[SimulationStageIndex].Destination->GetGPUInstanceCountBufferOffset(); - - //UE_LOG(LogScript, Warning, TEXT("ResizeBuffersAndGatherResources [%d][%d] Run ReqInst: %d Cur: %p Dest: %p "), Index, SimulationStageIndex, RequiredInstances, Instance.SimStageData[SimulationStageIndex].Source, Instance.SimStageData[SimulationStageIndex].Destination); - - // We don't actually write we just map out the buffers here. This toggles src and dest... - Context->MainDataSet->EndSimulate(); - } - - - } - } - } - - CurrentData = Context->MainDataSet->GetCurrentData(); if (bIsFinalTick) { - //UE_LOG(LogScript, Warning, TEXT("ResizeBuffersAndGatherResources [%d] DataSetToRender %p "),Index, CurrentData); + FNiagaraDataBuffer* FinalBuffer = Context->MainDataSet->GetCurrentData(); + Context->SetDataToRender(FinalBuffer); - Context->SetDataToRender(CurrentData); - OutputGraphicsBuffers.Add(CurrentData->GetGPUBufferFloat().UAV); - OutputGraphicsBuffers.Add(CurrentData->GetGPUBufferHalf().UAV); - OutputGraphicsBuffers.Add(CurrentData->GetGPUBufferInt().UAV); + // For the final tick we can stage the readback of the original source data + if (!bReadbackPending) + { + FNiagaraDataBuffer* ReadbackBuffer = Instance.SimStageData[0].Source; + if (ReadbackBuffer->GetNumInstances() && Context->EmitterInstanceReadback.GPUCountOffset == INDEX_NONE && ReadbackBuffer->GetGPUInstanceCountBufferOffset() != INDEX_NONE) + { + // Transfer the GPU instance counter ownership to the context. Note that a readback request will be performed later in the tick update, unless there's already a pending readback. + Context->EmitterInstanceReadback.GPUCountOffset = ReadbackBuffer->GetGPUInstanceCountBufferOffset(); + Context->EmitterInstanceReadback.CPUCount = ReadbackBuffer->GetNumInstances(); + ReadbackBuffer->ClearGPUInstanceCountBufferOffset(); + } + } + + if (FinalBuffer->GetGPUBufferFloat().UAV) + { + OutputGraphicsBuffers.Add(FinalBuffer->GetGPUBufferFloat().UAV); + } + if (FinalBuffer->GetGPUBufferHalf().UAV) + { + OutputGraphicsBuffers.Add(FinalBuffer->GetGPUBufferHalf().UAV); + } + if (FinalBuffer->GetGPUBufferInt().UAV) + { + OutputGraphicsBuffers.Add(FinalBuffer->GetGPUBufferInt().UAV); + } } } } @@ -782,7 +690,8 @@ void NiagaraEmitterInstanceBatcher::DispatchAllOnCompute(FOverlappableTicks& Ove { FNiagaraComputeInstanceData& Instance = Instances[Index]; FNiagaraComputeExecutionContext* Context = Instance.Context; - if (Context && Context->GPUScript_RT->GetShader().IsValid()) + + if (Context && Context->GPUScript_RT->IsShaderMapComplete_RenderThread()) { if (Context->DebugInfo.IsValid()) { @@ -802,65 +711,18 @@ void NiagaraEmitterInstanceBatcher::DispatchAllOnCompute(FOverlappableTicks& Ove { FNiagaraComputeInstanceData& Instance = Instances[Index]; FNiagaraComputeExecutionContext* Context = Instance.Context; - if (Context && Context->GPUScript_RT->GetShader().IsValid()) + + if (Context && Context->GPUScript_RT->IsShaderMapComplete_RenderThread()) { FNiagaraComputeExecutionContext::TickCounter++; // run shader, sim and spawn in a single dispatch - DispatchMultipleStages(*Tick, &Instance, RHICmdList, ViewUniformBuffer, Context->GPUScript_RT->GetShader()); + DispatchMultipleStages(*Tick, &Instance, RHICmdList, ViewUniformBuffer, Context->GPUScript_RT); } } } } -void NiagaraEmitterInstanceBatcher::PostRenderOpaque(FRHICommandListImmediate& RHICmdList, FRHIUniformBuffer* ViewUniformBuffer, const class FShaderParametersMetadata* SceneTexturesUniformBufferStruct, FRHIUniformBuffer* SceneTexturesUniformBuffer, bool bAllowGPUParticleUpdate) -{ - if (!FNiagaraUtilities::AllowGPUParticles(GetShaderPlatform())) - { - return; - } - - LLM_SCOPE(ELLMTag::Niagara); - - if (bAllowGPUParticleUpdate) - { - // Setup new readback since if there is no pending request, there is no risk of having invalid data read (offset being allocated after the readback was sent). - ExecuteAll(RHICmdList, ViewUniformBuffer, ETickStage::PostOpaqueRender); - - RHICmdList.BeginUAVOverlap(); - UpdateFreeIDBuffers(RHICmdList, DeferredIDBufferUpdates); - RHICmdList.EndUAVOverlap(); - - DeferredIDBufferUpdates.SetNum(0, false); - - FinishDispatches(); - } - - if (!GPUInstanceCounterManager.HasPendingGPUReadback()) - { - GPUInstanceCounterManager.EnqueueGPUReadback(RHICmdList); - } -} - -bool NiagaraEmitterInstanceBatcher::ShouldTickForStage(const FNiagaraGPUSystemTick& Tick, ETickStage TickStage) const -{ - if (!GNiagaraAllowTickBeforeRender || Tick.bRequiresDistanceFieldData || Tick.bRequiresDepthBuffer) - { - return TickStage == ETickStage::PostOpaqueRender; - } - - if (Tick.bRequiresEarlyViewData) - { - return TickStage == ETickStage::PostInitViews; - } - - if (Tick.bRequiresViewUniformBuffer) - { - return TickStage == ETickStage::PostOpaqueRender; - } - return TickStage == ETickStage::PreInitViews; -} - void NiagaraEmitterInstanceBatcher::ResizeFreeIDsListSizesBuffer(uint32 NumInstances) { if (NumInstances <= NumAllocatedFreeIDListSizes) @@ -928,12 +790,269 @@ void NiagaraEmitterInstanceBatcher::UpdateFreeIDBuffers(FRHICommandList& RHICmdL bFreeIDListSizesBufferCleared = false; } +void NiagaraEmitterInstanceBatcher::UpdateInstanceCountManager(FRHICommandListImmediate& RHICmdList) +{ + // Resize dispatch buffer count + { + int32 TotalDispatchCount = 0; + for (FNiagaraGPUSystemTick& Tick : Ticks_RT) + { + TotalDispatchCount += (int32)Tick.TotalDispatches; + + // Cancel any pending readback if the emitter is resetting. + if (Tick.bNeedsReset) + { + FNiagaraComputeInstanceData* Instances = Tick.GetInstanceData(); + for (uint32 InstanceIndex = 0; InstanceIndex < Tick.Count; ++InstanceIndex) + { + FNiagaraComputeExecutionContext* Context = Instances[InstanceIndex].Context; + if (Context) + { + GPUInstanceCounterManager.FreeEntry(Context->EmitterInstanceReadback.GPUCountOffset); + } + } + } + } + GPUInstanceCounterManager.ResizeBuffers(RHICmdList, FeatureLevel, TotalDispatchCount); + } + + // Update the instance counts from the GPU readback. + { + SCOPE_CYCLE_COUNTER(STAT_NiagaraGPUReadback_RT); + const uint32* Counts = GPUInstanceCounterManager.GetGPUReadback(); + if (Counts) + { + for (FNiagaraGPUSystemTick& Tick : Ticks_RT) + { + FNiagaraComputeInstanceData* Instances = Tick.GetInstanceData(); + for (uint32 InstanceIndex = 0; InstanceIndex < Tick.Count; ++InstanceIndex) + { + FNiagaraComputeExecutionContext* Context = Instances[InstanceIndex].Context; + if (Context && Context->EmitterInstanceReadback.GPUCountOffset != INDEX_NONE) + { + check(Context->MainDataSet); + FNiagaraDataBuffer* CurrentData = Context->MainDataSet->GetCurrentData(); + if (CurrentData) + { + const uint32 DeadInstanceCount = Context->EmitterInstanceReadback.CPUCount - Counts[Context->EmitterInstanceReadback.GPUCountOffset]; + + // This will communicate the particle counts to the game thread. If DeadInstanceCount equals CurrentData->GetNumInstances() the game thread will know that the emitter has completed. + if (DeadInstanceCount <= CurrentData->GetNumInstances()) + { + CurrentData->SetNumInstances(CurrentData->GetNumInstances() - DeadInstanceCount); + //UE_LOG(LogNiagara, Log, TEXT("GPU Readback Offset: %d %p = %d"), Context->EmitterInstanceReadback.GPUCountOffset, CurrentData, CurrentData->GetNumInstances()); + } + } + + // Now release the readback since another one will be enqueued in the tick. + // Also prevents processing the same data again. + GPUInstanceCounterManager.FreeEntry(Context->EmitterInstanceReadback.GPUCountOffset); + } + } + } + // Readback is only valid for one frame, so that any newly allocated instance count + // are guarantied to be in the next valid readback data. + GPUInstanceCounterManager.ReleaseGPUReadback(); + } + } +} + +void NiagaraEmitterInstanceBatcher::BuildTickStagePasses(FRHICommandListImmediate& RHICmdList) +{ + for (int32 iTickStage = 0; iTickStage < (int)ETickStage::Max; ++iTickStage) + { + ContextsPerStage[iTickStage].Reset(Ticks_RT.Num()); + TicksPerStage[iTickStage].Reset(Ticks_RT.Num()); + } + + for (FNiagaraGPUSystemTick& Tick : Ticks_RT) + { + FNiagaraComputeInstanceData* Data = Tick.GetInstanceData(); + FNiagaraComputeExecutionContext* SharedContext = Data->Context; + + if (!SharedContext->GPUScript_RT->IsShaderMapComplete_RenderThread()) + { + continue; + } + + const ETickStage TickStage = NiagaraEmitterInstanceBatcherLocal::CalculateTickStage(Tick); + + BuildConstantBuffers(Tick); + + Tick.bIsFinalTick = false; + + const bool bResetCounts = SharedContext->ScratchIndex == INDEX_NONE; + if (bResetCounts) + { + check(!ContextsPerStage[(int)ETickStage::PreInitViews].Contains(SharedContext)); + check(!ContextsPerStage[(int)ETickStage::PostInitViews].Contains(SharedContext)); + check(!ContextsPerStage[(int)ETickStage::PostOpaqueRender].Contains(SharedContext)); + ContextsPerStage[(int)TickStage].Add(SharedContext); + } + + // Here scratch index represent the index of the last tick + SharedContext->ScratchIndex = TicksPerStage[(int)TickStage].Add(&Tick); + + // Allows us to count total required instances across all ticks + for (uint32 i = 0; i < Tick.Count; ++i) + { + FNiagaraComputeInstanceData& InstanceData = Tick.GetInstanceData()[i]; + FNiagaraComputeExecutionContext* ExecContext = InstanceData.Context; + if ((ExecContext == nullptr) || !ExecContext->GPUScript_RT->IsShaderMapComplete_RenderThread()) + { + continue; + } + + uint32 PrevNumInstances = 0; + if (bResetCounts) + { + ExecContext->ScratchMaxInstances = InstanceData.SpawnInfo.MaxParticleCount; + PrevNumInstances = Tick.bNeedsReset ? 0 : ExecContext->MainDataSet->GetCurrentData()->GetNumInstances(); + } + else + { + PrevNumInstances = Tick.bNeedsReset ? 0 : ExecContext->ScratchNumInstances; + } + + ExecContext->ScratchNumInstances = InstanceData.SpawnInfo.SpawnRateInstances + InstanceData.SpawnInfo.EventSpawnTotal + PrevNumInstances; + + const uint32 MaxInstanceCount = ExecContext->MainDataSet->GetMaxInstanceCount(); + ExecContext->ScratchNumInstances = FMath::Min(ExecContext->ScratchNumInstances, MaxInstanceCount); + ExecContext->ScratchMaxInstances = FMath::Max(ExecContext->ScratchMaxInstances, ExecContext->ScratchNumInstances); + + InstanceData.SimStageData[0].SourceNumInstances = PrevNumInstances; + InstanceData.SimStageData[0].DestinationNumInstances = ExecContext->ScratchNumInstances; + } + } + + for (int32 iTickStage = 0; iTickStage < (int)ETickStage::Max; ++iTickStage) + { + // Set bIsFinalTick for the last tick of each context and reset the scratch index. + const int32 ScrachIndexReset = UseOverlapCompute() ? 0 : INDEX_NONE; + for (FNiagaraComputeExecutionContext* Context : ContextsPerStage[iTickStage]) + { + TicksPerStage[iTickStage][Context->ScratchIndex]->bIsFinalTick = true; + Context->ScratchIndex = ScrachIndexReset; + } + + for (FNiagaraGPUSystemTick* Tick : TicksPerStage[iTickStage]) + { + for (uint32 i = 0; i < Tick->Count; ++i) + { + FNiagaraComputeInstanceData& InstanceData = Tick->GetInstanceData()[i]; + FNiagaraComputeExecutionContext* ExecContext = InstanceData.Context; + if ((ExecContext == nullptr) || !ExecContext->GPUScript_RT->IsShaderMapComplete_RenderThread()) + { + continue; + } + + // First stage is presumed to be a particle stage + FNiagaraDataBuffer* CurrentData = ExecContext->MainDataSet->GetCurrentData(); + FNiagaraDataBuffer* DestinationData = &ExecContext->MainDataSet->BeginSimulate(); + + if (ExecContext->MainDataSet->RequiresPersistentIDs()) + { + ExecContext->MainDataSet->AllocateGPUFreeIDs(ExecContext->ScratchMaxInstances + 1, RHICmdList, FeatureLevel, ExecContext->GetDebugSimName()); + } + + DestinationData->AllocateGPU(ExecContext->ScratchMaxInstances + 1, GPUInstanceCounterManager, RHICmdList, FeatureLevel, ExecContext->GetDebugSimName()); + + InstanceData.SimStageData[0].Source = CurrentData; + InstanceData.SimStageData[0].SourceCountOffset = CurrentData->GetGPUInstanceCountBufferOffset(); + InstanceData.SimStageData[0].Destination = DestinationData; + InstanceData.SimStageData[0].DestinationCountOffset = DestinationData->GetGPUInstanceCountBufferOffset(); + + ExecContext->MainDataSet->EndSimulate(); + + if (Tick->NumInstancesWithSimStages > 0) + { + // Setup iteration source for stage 0 + InstanceData.SimStageData[0].AlternateIterationSource = FindIterationInterface(&InstanceData, 0); + + const uint32 NumStages = InstanceData.Context->MaxUpdateIterations; + if (NumStages > 1) + { + // Flip current / destination buffers so we read from the buffer we just wrote into on the next stage + Swap(CurrentData, DestinationData); + + uint32 CurrentNumInstances = InstanceData.SimStageData[0].DestinationNumInstances; + uint32 DestinationNumInstances = InstanceData.SimStageData[0].SourceNumInstances; + + for (uint32 SimulationStageIndex = 1; SimulationStageIndex < NumStages; ++SimulationStageIndex) + { + InstanceData.SimStageData[SimulationStageIndex].Source = CurrentData; + InstanceData.SimStageData[SimulationStageIndex].SourceCountOffset = CurrentData->GetGPUInstanceCountBufferOffset(); + InstanceData.SimStageData[SimulationStageIndex].SourceNumInstances = CurrentNumInstances; + InstanceData.SimStageData[SimulationStageIndex].Destination = DestinationData; + InstanceData.SimStageData[SimulationStageIndex].DestinationCountOffset = InstanceData.bUsesOldShaderStages ? DestinationData->GetGPUInstanceCountBufferOffset() : INDEX_NONE; + InstanceData.SimStageData[SimulationStageIndex].DestinationNumInstances = DestinationNumInstances; + + // Determine if the iteration is outputting to a custom data size + FNiagaraDataInterfaceProxy* IterationInterface = FindIterationInterface(&InstanceData, SimulationStageIndex); + InstanceData.SimStageData[SimulationStageIndex].AlternateIterationSource = IterationInterface; + + if (IterationInterface && ExecContext->SpawnStages.Num() > 0 && + ((Tick->bNeedsReset && !ExecContext->SpawnStages.Contains(SimulationStageIndex)) || + (!Tick->bNeedsReset && ExecContext->SpawnStages.Contains(SimulationStageIndex)))) + { + continue; + } + + if (!InstanceData.bUsesOldShaderStages) + { + // This should never be nullptr with simulation stages + const FSimulationStageMetaData* StageMetaData = ExecContext->GetSimStageMetaData(SimulationStageIndex); + check(StageMetaData); + InstanceData.SimStageData[SimulationStageIndex].StageMetaData = StageMetaData; + + // No particle data will be written we read only, i.e. scattering particles into a grid + if (!StageMetaData->bWritesParticles) + { + continue; + } + + // Particle counts are not changing and we can safely read / write to the same particle buffer + if (StageMetaData->bPartialParticleUpdate) + { + InstanceData.SimStageData[SimulationStageIndex].Destination = CurrentData; + InstanceData.SimStageData[SimulationStageIndex].DestinationCountOffset = INDEX_NONE; + InstanceData.SimStageData[SimulationStageIndex].DestinationNumInstances = CurrentNumInstances; + continue; + } + } + + // We need to allocate a new buffer as particle counts could be changing + ensure(CurrentData == ExecContext->MainDataSet->GetCurrentData()); + DestinationData = &ExecContext->MainDataSet->BeginSimulate(false); + DestinationData->AllocateGPU(ExecContext->ScratchMaxInstances + 1, GPUInstanceCounterManager, RHICmdList, FeatureLevel, ExecContext->GetDebugSimName()); + ExecContext->MainDataSet->EndSimulate(); + + DestinationNumInstances = CurrentNumInstances; + + InstanceData.SimStageData[SimulationStageIndex].Destination = DestinationData; + InstanceData.SimStageData[SimulationStageIndex].DestinationCountOffset = DestinationData->GetGPUInstanceCountBufferOffset(); + InstanceData.SimStageData[SimulationStageIndex].DestinationNumInstances = DestinationNumInstances; + + Swap(CurrentData, DestinationData); + } + } + } + + if (Tick->bIsFinalTick && GNiagaraGpuLowLatencyTranslucencyEnabled) + { + ExecContext->SetTranslucentDataToRender(ExecContext->MainDataSet->GetCurrentData()); + } + } + } + } +} + void NiagaraEmitterInstanceBatcher::ExecuteAll(FRHICommandList& RHICmdList, FRHIUniformBuffer* ViewUniformBuffer, ETickStage TickStage) { SCOPE_CYCLE_COUNTER(STAT_NiagaraGPUSimTick_RT); - // This is always called by the renderer so early out if we have no work. - if (Ticks_RT.Num() == 0) + // Anything to execute for this tick stage? + if (TicksPerStage[(int)TickStage].Num() == 0) { return; } @@ -950,75 +1069,12 @@ void NiagaraEmitterInstanceBatcher::ExecuteAll(FRHICommandList& RHICmdList, FRHI FMemMark Mark(FMemStack::Get()); TArray > SimPasses; { - TArray< FNiagaraComputeExecutionContext* , TMemStackAllocator<> > RelevantContexts; - TArray< FNiagaraGPUSystemTick* , TMemStackAllocator<> > RelevantTicks; - for (FNiagaraGPUSystemTick& Tick : Ticks_RT) - { - FNiagaraComputeInstanceData* Data = Tick.GetInstanceData(); - FNiagaraComputeExecutionContext* SharedContext = Data->Context; - // This assumes all emitters fallback to the same FNiagaraShaderScript*. - FNiagaraShaderRef ComputeShader = SharedContext->GPUScript_RT->GetShader(); - if (ComputeShader.IsNull() || !ShouldTickForStage(Tick, TickStage)) - { - continue; - } - - BuildConstantBuffers(Tick); - - Tick.bIsFinalTick = false; // @todo : this is true sometimes, needs investigation - - const bool bResetCounts = SharedContext->ScratchIndex == INDEX_NONE; - if (bResetCounts) - { - RelevantContexts.Add(SharedContext); - } - - // Here scratch index represent the index of the last tick - SharedContext->ScratchIndex = RelevantTicks.Add(&Tick); - - // Allows us to count total required instances across all frames - for (uint32 i = 0; i < Tick.Count; ++i) - { - FNiagaraComputeInstanceData& InstanceData = Tick.GetInstanceData()[i]; - FNiagaraComputeExecutionContext* ExecContext = InstanceData.Context; - if (ExecContext == nullptr) - { - continue; - } - - uint32 PrevNumInstances = 0; - if (bResetCounts) - { - ExecContext->ScratchMaxInstances = InstanceData.SpawnInfo.MaxParticleCount; - PrevNumInstances = Tick.bNeedsReset ? 0 : ExecContext->MainDataSet->GetCurrentData()->GetNumInstances(); - } - else - { - PrevNumInstances = Tick.bNeedsReset ? 0 : ExecContext->ScratchNumInstances; - } - ExecContext->ScratchNumInstances = InstanceData.SpawnInfo.SpawnRateInstances + InstanceData.SpawnInfo.EventSpawnTotal + PrevNumInstances; - - const uint32 MaxInstanceCount = ExecContext->MainDataSet->GetMaxInstanceCount(); - ExecContext->ScratchNumInstances = FMath::Min(ExecContext->ScratchNumInstances, MaxInstanceCount); - - ExecContext->ScratchMaxInstances = FMath::Max(ExecContext->ScratchMaxInstances, ExecContext->ScratchNumInstances); - } - } - - // Set bIsFinalTick for the last tick of each context and reset the scratch index. - const int32 ScrachIndexReset = UseOverlapCompute() ? 0 : INDEX_NONE; - for (FNiagaraComputeExecutionContext* Context : RelevantContexts) - { - RelevantTicks[Context->ScratchIndex]->bIsFinalTick = true; - Context->ScratchIndex = ScrachIndexReset; - } - if (UseOverlapCompute()) { // Transpose now only once the data to get all independent tick per pass SimPasses.Reserve(2); // Safe bet! - for (FNiagaraGPUSystemTick* Tick : RelevantTicks) + for (FNiagaraGPUSystemTick* Tick : TicksPerStage[(int)TickStage]) { FNiagaraComputeExecutionContext* Context = Tick->GetInstanceData()->Context; const int32 ScratchIndex = Context->ScratchIndex; @@ -1029,7 +1085,7 @@ void NiagaraEmitterInstanceBatcher::ExecuteAll(FRHICommandList& RHICmdList, FRHI SimPasses.AddDefaulted(SimPasses.Num() - ScratchIndex + 1); if (ScratchIndex == 0) { - SimPasses[0].Reserve(RelevantContexts.Num()); // Guarantied! + SimPasses[0].Reserve(ContextsPerStage[(int)TickStage].Num()); // Guarantied! } } SimPasses[ScratchIndex].Add(Tick); @@ -1048,8 +1104,8 @@ void NiagaraEmitterInstanceBatcher::ExecuteAll(FRHICommandList& RHICmdList, FRHI else { // Force dispatches to run individually, this should only be used for debugging as it is highly inefficient on the GPU - SimPasses.Reserve(RelevantTicks.Num()); // Guarantied! - for (FNiagaraGPUSystemTick* Tick : RelevantTicks) + SimPasses.Reserve(ContextsPerStage[(int)TickStage].Num()); // Guarantied! + for (FNiagaraGPUSystemTick* Tick : TicksPerStage[(int)TickStage]) { SimPasses.AddDefaulted_GetRef().Add(Tick); } @@ -1057,7 +1113,7 @@ void NiagaraEmitterInstanceBatcher::ExecuteAll(FRHICommandList& RHICmdList, FRHI } // Clear any RT bindings that we may be using - // Note: We can not encapsulate the whole Niagara pass as some DI's may not be compatable (i.e. use CopyTexture function), we need to fix this with future RDG conversion + // Note: We can not encapsulate the whole Niagara pass as some DI's may not be compatible (i.e. use CopyTexture function), we need to fix this with future RDG conversion if (SimPasses.Num() > 0) { //PRAGMA_DISABLE_DEPRECATION_WARNINGS @@ -1073,9 +1129,8 @@ void NiagaraEmitterInstanceBatcher::ExecuteAll(FRHICommandList& RHICmdList, FRHI FOverlappableTicks& SimPass = SimPasses[SimPassIdx]; InstancesWithPersistentIDs.SetNum(0, false); - // This initial pass gathers all the buffers that are read from and written to so we can do batch resource transitions. - // It also ensures the GPU buffers are large enough to hold everything. - ResizeBuffersAndGatherResources(SimPass, RHICmdList, OutputGraphicsBuffers, InstancesWithPersistentIDs); + // Gather all the buffers that are read from / written to so we can transition all resources in a batch + GatherResources(SimPass, RHICmdList, OutputGraphicsBuffers, InstancesWithPersistentIDs); { SCOPED_DRAW_EVENT(RHICmdList, NiagaraGPUSimulation); @@ -1138,133 +1193,9 @@ void NiagaraEmitterInstanceBatcher::PreInitViews(FRHICommandListImmediate& RHICm // Update draw indirect buffer to max possible size. if (bAllowGPUParticleUpdate) { - int32 TotalDispatchCount = 0; - for (FNiagaraGPUSystemTick& Tick : Ticks_RT) - { - TotalDispatchCount += (int32)Tick.TotalDispatches; + UpdateInstanceCountManager(RHICmdList); - // Cancel any pending readback if the emitter is resetting. - if (Tick.bNeedsReset) - { - FNiagaraComputeInstanceData* Instances = Tick.GetInstanceData(); - for (uint32 InstanceIndex = 0; InstanceIndex < Tick.Count; ++InstanceIndex) - { - FNiagaraComputeExecutionContext* Context = Instances[InstanceIndex].Context; - if (Context) - { - GPUInstanceCounterManager.FreeEntry(Context->EmitterInstanceReadback.GPUCountOffset); - } - } - } - } - GPUInstanceCounterManager.ResizeBuffers(RHICmdList, FeatureLevel, TotalDispatchCount); - - // Update the instance counts from the GPU readback. - { - SCOPE_CYCLE_COUNTER(STAT_NiagaraGPUReadback_RT); - const uint32* Counts = GPUInstanceCounterManager.GetGPUReadback(); - if (Counts) - { - for (FNiagaraGPUSystemTick& Tick : Ticks_RT) - { - FNiagaraComputeInstanceData* Instances = Tick.GetInstanceData(); - for (uint32 InstanceIndex = 0; InstanceIndex < Tick.Count; ++InstanceIndex) - { - FNiagaraComputeExecutionContext* Context = Instances[InstanceIndex].Context; - if (Context && Context->EmitterInstanceReadback.GPUCountOffset != INDEX_NONE) - { - check(Context->MainDataSet); - FNiagaraDataBuffer* CurrentData = Context->MainDataSet->GetCurrentData(); - if (CurrentData) - { - const uint32 DeadInstanceCount = Context->EmitterInstanceReadback.CPUCount - Counts[Context->EmitterInstanceReadback.GPUCountOffset]; - - // This will communicate the particle counts to the game thread. If DeadInstanceCount equals CurrentData->GetNumInstances() the game thread will know that the emitter has completed. - if (DeadInstanceCount <= CurrentData->GetNumInstances()) - { - CurrentData->SetNumInstances(CurrentData->GetNumInstances() - DeadInstanceCount); - //UE_LOG(LogNiagara, Log, TEXT("GPU Readback Offset: %d %p = %d"), Context->EmitterInstanceReadback.GPUCountOffset, CurrentData, CurrentData->GetNumInstances()); - } - } - - // Now release the readback since another one will be enqueued in the tick. - // Also prevents processing the same data again. - GPUInstanceCounterManager.FreeEntry(Context->EmitterInstanceReadback.GPUCountOffset); - } - } - } - // Readback is only valid for one frame, so that any newly allocated instance count - // are guarantied to be in the next valid readback data. - GPUInstanceCounterManager.ReleaseGPUReadback(); - } - } - - // Determine low latency contexts - if ( GNiagaraGpuLowLatencyTranslucencyEnabled ) - { - //FMemMark Mark(FMemStack::Get()); - //-TODO: We shouldn't have a lot of these, could convert to a TArray perhaps? - TSet LowLatencyTranslucentContexts; - for (FNiagaraGPUSystemTick& Tick : Ticks_RT) - { - const bool bTickInPostOpaqueRender = ShouldTickForStage(Tick, ETickStage::PostOpaqueRender); - if (!bTickInPostOpaqueRender) - { - continue; - } - - FNiagaraComputeInstanceData* Instances = Tick.GetInstanceData(); - for (uint32 InstanceIndex = 0; InstanceIndex < Tick.Count; ++InstanceIndex) - { - FNiagaraComputeExecutionContext* Context = Instances[InstanceIndex].Context; - if (Context == nullptr) - { - continue; - } - FNiagaraShaderRef ComputeShader = Context->GPUScript_RT->GetShader(); - if (ComputeShader.IsNull()) - { - continue; - } - - // Grab low latency contexts & determine instance counts for them - //-TODO: Support shader stages - if (Context->MaxUpdateIterations == 1) - { - const FNiagaraGpuSpawnInfo& SpawnInfo = Instances[InstanceIndex].SpawnInfo; - - uint32 PrevNumInstances = 0; - if (!LowLatencyTranslucentContexts.Contains(Context)) - { - LowLatencyTranslucentContexts.Add(Context); - Context->ScratchMaxInstances = SpawnInfo.MaxParticleCount; - PrevNumInstances = Tick.bNeedsReset ? 0 : Context->MainDataSet->GetCurrentData()->GetNumInstances(); - } - else - { - PrevNumInstances = Tick.bNeedsReset ? 0 : Context->ScratchNumInstances; - } - Context->ScratchNumInstances = SpawnInfo.SpawnRateInstances + SpawnInfo.EventSpawnTotal + PrevNumInstances; - Context->ScratchMaxInstances = FMath::Max(Context->ScratchMaxInstances, Context->ScratchNumInstances); - } - } - } - - // For each low latency translucent context we need to allocate the final buffer ahead of time - for (FNiagaraComputeExecutionContext* ExecContext : LowLatencyTranslucentContexts) - { - if (ExecContext->MainDataSet->RequiresPersistentIDs()) - { - ExecContext->MainDataSet->AllocateGPUFreeIDs(ExecContext->ScratchMaxInstances + 1, RHICmdList, FeatureLevel, ExecContext->GetDebugSimName()); - } - - FNiagaraDataBuffer& FinalDataBuffer = ExecContext->MainDataSet->BeginSimulate(); - FinalDataBuffer.AllocateGPU(ExecContext->ScratchMaxInstances + 1, GPUInstanceCounterManager, RHICmdList, FeatureLevel, ExecContext->GetDebugSimName()); - FinalDataBuffer.SetNumInstances(0); - ExecContext->MainDataSet->EndSimulate(false); - ExecContext->SetTranslucentDataToRender(&FinalDataBuffer); - } - } + BuildTickStagePasses(RHICmdList); // @todo REMOVE THIS HACK LastFrameThatDrainedData = GFrameNumberRenderThread; @@ -1295,43 +1226,54 @@ void NiagaraEmitterInstanceBatcher::PostInitViews(FRHICommandListImmediate& RHIC } } -bool NiagaraEmitterInstanceBatcher::UsesGlobalDistanceField() const +void NiagaraEmitterInstanceBatcher::PostRenderOpaque(FRHICommandListImmediate& RHICmdList, FRHIUniformBuffer* ViewUniformBuffer, const class FShaderParametersMetadata* SceneTexturesUniformBufferStruct, FRHIUniformBuffer* SceneTexturesUniformBuffer, bool bAllowGPUParticleUpdate) { - for (const FNiagaraGPUSystemTick& Tick : Ticks_RT) + if (!FNiagaraUtilities::AllowGPUParticles(GetShaderPlatform())) { - if (Tick.bRequiresDistanceFieldData) - { - return true; - } + return; } - return false; + LLM_SCOPE(ELLMTag::Niagara); + + if (bAllowGPUParticleUpdate) + { + // Setup new readback since if there is no pending request, there is no risk of having invalid data read (offset being allocated after the readback was sent). + ExecuteAll(RHICmdList, ViewUniformBuffer, ETickStage::PostOpaqueRender); + + RHICmdList.BeginUAVOverlap(); + UpdateFreeIDBuffers(RHICmdList, DeferredIDBufferUpdates); + RHICmdList.EndUAVOverlap(); + + DeferredIDBufferUpdates.SetNum(0, false); + + FinishDispatches(); + } + + if (!GPUInstanceCounterManager.HasPendingGPUReadback()) + { + GPUInstanceCounterManager.EnqueueGPUReadback(RHICmdList); + } +} + +bool NiagaraEmitterInstanceBatcher::UsesGlobalDistanceField() const +{ + checkSlow(Ticks_RT.ContainsByPredicate([](const FNiagaraGPUSystemTick& Tick) { return Tick.bRequiresDistanceFieldData; }) == NumTicksThatRequireDistanceFieldData > 0); + + return NumTicksThatRequireDistanceFieldData > 0; } bool NiagaraEmitterInstanceBatcher::UsesDepthBuffer() const { - for (const FNiagaraGPUSystemTick& Tick : Ticks_RT) - { - if (Tick.bRequiresDepthBuffer) - { - return true; - } - } + checkSlow(Ticks_RT.ContainsByPredicate([](const FNiagaraGPUSystemTick& Tick) { return Tick.bRequiresDepthBuffer; }) == NumTicksThatRequireDepthBuffer > 0); - return false; + return NumTicksThatRequireDepthBuffer > 0; } bool NiagaraEmitterInstanceBatcher::RequiresEarlyViewUniformBuffer() const { - for (const FNiagaraGPUSystemTick& Tick : Ticks_RT) - { - if (Tick.bRequiresEarlyViewData) - { - return true; - } - } + checkSlow(Ticks_RT.ContainsByPredicate([](const FNiagaraGPUSystemTick& Tick) { return Tick.bRequiresEarlyViewData; }) == NumTicksThatRequireEarlyViewData > 0); - return false; + return NumTicksThatRequireEarlyViewData > 0; } void NiagaraEmitterInstanceBatcher::PreRender(FRHICommandListImmediate& RHICmdList, const class FGlobalDistanceFieldParameterData* GlobalDistanceFieldParameterData, bool bAllowGPUParticleUpdate) @@ -1538,7 +1480,7 @@ void NiagaraEmitterInstanceBatcher::SetDataInterfaceParameters(const TArrayGetDIParameters()[InterfaceIndex]; if (DIParam.Parameters.IsValid()) { - FNiagaraDataInterfaceSetArgs Context; - Context.Shader = Shader; - Context.DataInterface = Interface; - Context.SystemInstance = SystemInstance; - Context.Batcher = this; - Context.ComputeInstanceData = Instance; - Context.SimulationStageIndex = SimulationStageIndex; - Context.IsOutputStage = Instance->IsOutputStage(Interface, SimulationStageIndex); - Context.IsIterationStage = Instance->IsIterationStage(Interface, SimulationStageIndex); + FNiagaraDataInterfaceSetArgs Context(Interface, SystemInstanceID, this, Shader, Instance, SimulationStageIndex, Instance->IsOutputStage(Interface, SimulationStageIndex), Instance->IsIterationStage(Interface, SimulationStageIndex)); DIParam.DIType.Get(PointerTable.DITypes)->SetParameters(DIParam.Parameters.Get(), RHICmdList, Context); } @@ -1563,7 +1497,7 @@ void NiagaraEmitterInstanceBatcher::SetDataInterfaceParameters(const TArray &DataInterfaceProxies, const FNiagaraShaderRef& Shader, FRHICommandList &RHICmdList, const FNiagaraComputeInstanceData* Instance, const FNiagaraGPUSystemTick& Tick) const +void NiagaraEmitterInstanceBatcher::UnsetDataInterfaceParameters(const TArray &DataInterfaceProxies, const FNiagaraShaderRef& Shader, FRHICommandList &RHICmdList, const FNiagaraComputeInstanceData* Instance, const FNiagaraGPUSystemTick& Tick, uint32 SimulationStageIndex) const { // set up data interface buffers, as defined by the DIs during compilation // @@ -1588,11 +1522,7 @@ void NiagaraEmitterInstanceBatcher::UnsetDataInterfaceParameters(const TArrayPerInstanceDataForRT; } } - FNiagaraDataInterfaceSetArgs Context; - Context.Shader = Shader; - Context.DataInterface = Interface; - Context.SystemInstance = SystemInstance; - Context.Batcher = this; + FNiagaraDataInterfaceSetArgs Context(Interface, SystemInstance, this, Shader, Instance, SimulationStageIndex, Instance->IsOutputStage(Interface, SimulationStageIndex), Instance->IsIterationStage(Interface, SimulationStageIndex)); DIParam.DIType.Get(PointerTable.DITypes)->UnsetParameters(DIParam.Parameters.Get(), RHICmdList, Context); } @@ -1642,35 +1572,34 @@ void NiagaraEmitterInstanceBatcher::Run(const FNiagaraGPUSystemTick& Tick, const return; } - /*UE_LOG(LogNiagara, Log, TEXT("Niagara Gpu Sim - % s - NumInstances: % u - StageNumber : % u"), Context->GetDebugSimName(), - TotalNumInstances, - SimulationStageIndex); - */ - - SCOPED_DRAW_EVENTF(RHICmdList, NiagaraGPUSimulationCS, TEXT("Niagara Gpu Sim - %s - NumInstances: %u - StageNumber: %u - NumInstructions %u"), + SCOPED_DRAW_EVENTF(RHICmdList, NiagaraGPUSimulationCS, TEXT("NiagaraGpuSim(%s) NumInstances(%u) Stage(%s %u) NumInstructions(%u)"), Context->GetDebugSimName(), TotalNumInstances, + Context->GetSimStageMetaData(SimulationStageIndex) ? *Context->GetSimStageMetaData(SimulationStageIndex)->SimulationStageName.ToString() : TEXT("Particles"), SimulationStageIndex, Shader->GetNumInstructions() ); - //UE_LOG(LogNiagara, Warning, TEXT("Run")); - const TArray& DataInterfaceProxies = Instance->DataInterfaceProxies; check(Instance->SimStageData[SimulationStageIndex].Source && Instance->SimStageData[SimulationStageIndex].Destination); FNiagaraDataBuffer& DestinationData = *Instance->SimStageData[SimulationStageIndex].Destination; FNiagaraDataBuffer& CurrentData = *Instance->SimStageData[SimulationStageIndex].Source; - //UE_LOG(LogScript, Warning, TEXT("Run [%d] TotalInstances %d src:%p dest:%p"), SimulationStageIndex, TotalNumInstances, Instance->SimStageData[SimulationStageIndex].Source, Instance->SimStageData[SimulationStageIndex].Destination); - - int32 InstancesToSpawnThisFrame = DestinationData.GetNumSpawnedInstances(); DestinationData.SetIDAcquireTag(FNiagaraComputeExecutionContext::TickCounter); + CurrentData.SetNumInstances(Instance->SimStageData[SimulationStageIndex].SourceNumInstances); + DestinationData.SetNumInstances(Instance->SimStageData[SimulationStageIndex].DestinationNumInstances); + // Only spawn particles on the first stage - if (HasRunParticleStage) + int32 InstancesToSpawnThisFrame = 0; + if (!HasRunParticleStage) { - InstancesToSpawnThisFrame = 0; + if (Instance->SimStageData[SimulationStageIndex].DestinationNumInstances > Instance->SimStageData[SimulationStageIndex].SourceNumInstances) + { + InstancesToSpawnThisFrame = Instance->SimStageData[SimulationStageIndex].DestinationNumInstances - Instance->SimStageData[SimulationStageIndex].SourceNumInstances; + } } + DestinationData.SetNumSpawnedInstances(InstancesToSpawnThisFrame); FRHIComputeShader* ComputeShader = Shader.GetComputeShader(); RHICmdList.SetComputeShader(ComputeShader); @@ -1735,16 +1664,28 @@ void NiagaraEmitterInstanceBatcher::Run(const FNiagaraGPUSystemTick& Tick, const SetShaderValue(RHICmdList, ComputeShader, Shader->NumSpawnedInstancesParam, InstancesToSpawnThisFrame); // number of instances in the spawn run SetShaderValue(RHICmdList, ComputeShader, Shader->DefaultSimulationStageIndexParam, DefaultSimulationStageIndex); // 0, except if several stages are defined SetShaderValue(RHICmdList, ComputeShader, Shader->SimulationStageIndexParam, SimulationStageIndex); // 0, except if several stages are defined - const int32 DefaultIterationCount = -1; - SetShaderValue(RHICmdList, ComputeShader, Shader->IterationInterfaceCount, DefaultIterationCount); // 0, except if several stages are defined const uint32 ShaderThreadGroupSize = FNiagaraShader::GetGroupSize(ShaderPlatform); - if (IterationInterface) { - if (TotalNumInstances > ShaderThreadGroupSize) + // Packed data where X = Instance Count, Y = Iteration Index, Z = Num Iterations + int32 SimulationStageIterationInfo[3] = { -1, 0, 0 }; + float SimulationStageNormalizedIterationIndex = 0.0f; + + if (IterationInterface) { - SetShaderValue(RHICmdList, ComputeShader, Shader->IterationInterfaceCount, TotalNumInstances); // 0, except if several stages are defined + SimulationStageIterationInfo[0] = TotalNumInstances; + if (const FSimulationStageMetaData* StageMetaData = Instance->SimStageData[SimulationStageIndex].StageMetaData) + { + const int32 NumStages = StageMetaData->MaxStage - StageMetaData->MinStage; + ensure((int32(SimulationStageIndex) >= StageMetaData->MinStage) && (int32(SimulationStageIndex) < StageMetaData->MaxStage)); + SimulationStageIterationInfo[1] = SimulationStageIndex - StageMetaData->MinStage; + SimulationStageIterationInfo[2] = NumStages; + SimulationStageNormalizedIterationIndex = NumStages > 1 ? float(SimulationStageIterationInfo[1]) / float(SimulationStageIterationInfo[2] - 1) : 1.0f; + } } + SetShaderValue(RHICmdList, ComputeShader, Shader->SimulationStageIterationInfoParam, SimulationStageIterationInfo); + SetShaderValue(RHICmdList, ComputeShader, Shader->SimulationStageNormalizedIterationIndexParam, SimulationStageNormalizedIterationIndex); + } uint32 NumThreadGroups = 1; @@ -1755,21 +1696,12 @@ void NiagaraEmitterInstanceBatcher::Run(const FNiagaraGPUSystemTick& Tick, const SetConstantBuffers(RHICmdList, Shader, Tick, Instance); - //UE_LOG(LogNiagara, Log, TEXT("Num Instance : %d | Num Group : %d | Spawned Istance : %d | Start Instance : %d | Num Indices : %d | Stage Index : %d"), - //TotalNumInstances, NumThreadGroups, InstancesToSpawnThisFrame, UpdateStartInstance, Context->NumIndicesPerInstance, SimulationStageIndex); - // Dispatch, if anything needs to be done if (TotalNumInstances) { DispatchComputeShader(RHICmdList, Shader.GetShader(), NumThreadGroups, 1, 1); } - // reset iteration count - if (IterationInterface) - { - SetShaderValue(RHICmdList, ComputeShader, Shader->IterationInterfaceCount, DefaultIterationCount); // 0, except if several stages are defined - } - #if WITH_EDITORONLY_DATA // Check to see if we need to queue up a debug dump.. if (Context->DebugInfo.IsValid()) @@ -1820,7 +1752,7 @@ void NiagaraEmitterInstanceBatcher::Run(const FNiagaraGPUSystemTick& Tick, const // Unset UAV parameters and transition resources (TODO: resource transition should be moved to the renderer) // - UnsetDataInterfaceParameters(DataInterfaceProxies, Shader, RHICmdList, Instance, Tick); + UnsetDataInterfaceParameters(DataInterfaceProxies, Shader, RHICmdList, Instance, Tick, SimulationStageIndex); CurrentData.UnsetShaderParams(Shader.GetShader(), RHICmdList); DestinationData.UnsetShaderParams(Shader.GetShader(), RHICmdList); Shader->InstanceCountsParam.UnsetUAV(RHICmdList, ComputeShader); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraFunctionLibrary.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraFunctionLibrary.cpp index b7b9b880ac09..57119b79c872 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraFunctionLibrary.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraFunctionLibrary.cpp @@ -106,7 +106,7 @@ UNiagaraComponent* UNiagaraFunctionLibrary::SpawnSystemAtLocation(const UObject* if(PSC) { #if WITH_EDITORONLY_DATA - PSC->bWaitForCompilationOnActivate = true; + PSC->bWaitForCompilationOnActivate = GIsAutomationTesting; #endif if (!PSC->IsRegistered()) @@ -408,6 +408,12 @@ UNiagaraDataInterfaceSkeletalMesh* UNiagaraFunctionLibrary::GetSkeletalMeshDataI void UNiagaraFunctionLibrary::OverrideSystemUserVariableSkeletalMeshComponent(UNiagaraComponent* NiagaraSystem, const FString& OverrideName, USkeletalMeshComponent* SkeletalMeshComponent) { + if (!NiagaraSystem) + { + UE_LOG(LogNiagara, Warning, TEXT("NiagaraSystem in \"Set Niagara Skeletal Mesh Component\" is NULL, OverrideName \"%s\" and SkeletalMeshComponent \"%s\", skipping."), *OverrideName, SkeletalMeshComponent ? *SkeletalMeshComponent->GetName() : TEXT("NULL")); + return; + } + if (!SkeletalMeshComponent) { UE_LOG(LogNiagara, Warning, TEXT("SkeletalMeshComponent in \"Set Niagara Skeletal Mesh Component\" is NULL, OverrideName \"%s\" and NiagaraSystem \"%s\", skipping."), *OverrideName, *NiagaraSystem->GetOwner()->GetName()); @@ -426,6 +432,12 @@ void UNiagaraFunctionLibrary::OverrideSystemUserVariableSkeletalMeshComponent(UN void UNiagaraFunctionLibrary::SetSkeletalMeshDataInterfaceSamplingRegions(UNiagaraComponent* NiagaraSystem, const FString& OverrideName, const TArray& SamplingRegions) { + if (!NiagaraSystem) + { + UE_LOG(LogNiagara, Warning, TEXT("NiagaraSystem in \"Set Skeletal Mesh Data Interface Sampling Regions\" is NULL, OverrideName \"%s\", skipping."), *OverrideName); + return; + } + UNiagaraDataInterfaceSkeletalMesh* SkeletalMeshInterface = GetSkeletalMeshDataInterface(NiagaraSystem, OverrideName); if (!SkeletalMeshInterface) { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraGPUInstanceCountManager.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraGPUInstanceCountManager.cpp index 70de85af9372..fd7bbea40bb7 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraGPUInstanceCountManager.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraGPUInstanceCountManager.cpp @@ -321,7 +321,7 @@ void FNiagaraGPUInstanceCountManager::UpdateDrawIndirectBuffer(FRHICommandList& } // Transition draw indirect to readable for gfx draw indirect. - RHICmdList.TransitionResource(EResourceTransitionAccess::ERWBarrier, EResourceTransitionPipeline::EComputeToGfx, DrawIndirectBuffer.UAV); + RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, EResourceTransitionPipeline::EComputeToGfx, DrawIndirectBuffer.UAV); } // Once cleared to 0, the count are reusable. FreeEntries.Append(InstanceCountClearTasks); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraLightRendererProperties.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraLightRendererProperties.cpp index 5035e4106ba5..612d82b771b4 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraLightRendererProperties.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraLightRendererProperties.cpp @@ -31,6 +31,14 @@ UNiagaraLightRendererProperties::UNiagaraLightRendererProperties() AttributeBindings.Add(&VolumetricScatteringBinding); } + +void UNiagaraLightRendererProperties::PostLoad() +{ + Super::PostLoad(); + PostLoadBindings(ENiagaraRendererSourceDataMode::Particles); +} + + void UNiagaraLightRendererProperties::PostInitProperties() { Super::PostInitProperties(); @@ -43,7 +51,7 @@ void UNiagaraLightRendererProperties::PostInitProperties() LightRendererPropertiesToDeferredInit.Add(this); return; } - else if (PositionBinding.BoundVariable.GetName() == NAME_None) + else if (!PositionBinding.IsValid()) { PositionBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_POSITION); ColorBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_COLOR); @@ -70,7 +78,7 @@ void UNiagaraLightRendererProperties::InitCDOPropertiesAfterModuleStartup() { if (WeakLightRendererProperties.Get()) { - if (WeakLightRendererProperties->PositionBinding.BoundVariable.GetName() == NAME_None) + if (!WeakLightRendererProperties->PositionBinding.IsValid()) { WeakLightRendererProperties->PositionBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_POSITION); WeakLightRendererProperties->ColorBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_COLOR); @@ -83,10 +91,10 @@ void UNiagaraLightRendererProperties::InitCDOPropertiesAfterModuleStartup() } } -FNiagaraRenderer* UNiagaraLightRendererProperties::CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter) +FNiagaraRenderer* UNiagaraLightRendererProperties::CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) { FNiagaraRenderer* NewRenderer = new FNiagaraRendererLights(FeatureLevel, this, Emitter); - NewRenderer->Initialize(this, Emitter); + NewRenderer->Initialize(this, Emitter, InComponent); return NewRenderer; } @@ -98,12 +106,12 @@ void UNiagaraLightRendererProperties::GetUsedMaterials(const FNiagaraEmitterInst void UNiagaraLightRendererProperties::CacheFromCompiledData(const FNiagaraDataSetCompiledData* CompiledData) { - PositionDataSetAccessor.Init(CompiledData, PositionBinding.DataSetVariable.GetName()); - ColorDataSetAccessor.Init(CompiledData, ColorBinding.DataSetVariable.GetName()); - RadiusDataSetAccessor.Init(CompiledData, RadiusBinding.DataSetVariable.GetName()); - ExponentDataSetAccessor.Init(CompiledData, LightExponentBinding.DataSetVariable.GetName()); - ScatteringDataSetAccessor.Init(CompiledData, VolumetricScatteringBinding.DataSetVariable.GetName()); - EnabledDataSetAccessor.Init(CompiledData, LightRenderingEnabledBinding.DataSetVariable.GetName()); + PositionDataSetAccessor.Init(CompiledData, PositionBinding.GetDataSetBindableVariable().GetName()); + ColorDataSetAccessor.Init(CompiledData, ColorBinding.GetDataSetBindableVariable().GetName()); + RadiusDataSetAccessor.Init(CompiledData, RadiusBinding.GetDataSetBindableVariable().GetName()); + ExponentDataSetAccessor.Init(CompiledData, LightExponentBinding.GetDataSetBindableVariable().GetName()); + ScatteringDataSetAccessor.Init(CompiledData, VolumetricScatteringBinding.GetDataSetBindableVariable().GetName()); + EnabledDataSetAccessor.Init(CompiledData, LightRenderingEnabledBinding.GetDataSetBindableVariable().GetName()); } #if WITH_EDITORONLY_DATA diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraMeshRendererProperties.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraMeshRendererProperties.cpp index f14971244854..7d5b7cfd228f 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraMeshRendererProperties.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraMeshRendererProperties.cpp @@ -5,12 +5,12 @@ #include "Engine/StaticMesh.h" #include "NiagaraConstants.h" #include "NiagaraBoundsCalculatorHelper.h" +#include "NiagaraCustomVersion.h" #include "Modules/ModuleManager.h" #if WITH_EDITOR #include "Widgets/Images/SImage.h" #include "Styling/SlateIconFinder.h" #include "Widgets/SWidget.h" -#include "Styling/SlateBrush.h" #include "AssetThumbnail.h" #include "Widgets/Text/STextBlock.h" #endif @@ -71,12 +71,12 @@ UNiagaraMeshRendererProperties::UNiagaraMeshRendererProperties() AttributeBindings.Add(&RendererVisibilityTagBinding); } -FNiagaraRenderer* UNiagaraMeshRendererProperties::CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter) +FNiagaraRenderer* UNiagaraMeshRendererProperties::CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) { if (ParticleMesh) { FNiagaraRenderer* NewRenderer = new FNiagaraRendererMeshes(FeatureLevel, this, Emitter); - NewRenderer->Initialize(this, Emitter); + NewRenderer->Initialize(this, Emitter, InComponent); return NewRenderer; } @@ -141,7 +141,7 @@ void UNiagaraMeshRendererProperties::InitCDOPropertiesAfterModuleStartup() void UNiagaraMeshRendererProperties::InitBindings() { - if (PositionBinding.BoundVariable.GetName() == NAME_None) + if (!PositionBinding.IsValid()) { PositionBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_POSITION); ColorBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_COLOR); @@ -167,36 +167,36 @@ void UNiagaraMeshRendererProperties::CacheFromCompiledData(const FNiagaraDataSet { // Initialize layout RendererLayoutWithCustomSorting.Initialize(ENiagaraMeshVFLayout::Num); - RendererLayoutWithCustomSorting.SetVariable(CompiledData, PositionBinding.DataSetVariable, ENiagaraMeshVFLayout::Position); - RendererLayoutWithCustomSorting.SetVariable(CompiledData, VelocityBinding.DataSetVariable, ENiagaraMeshVFLayout::Velocity); - RendererLayoutWithCustomSorting.SetVariable(CompiledData, ColorBinding.DataSetVariable, ENiagaraMeshVFLayout::Color); - RendererLayoutWithCustomSorting.SetVariable(CompiledData, ScaleBinding.DataSetVariable, ENiagaraMeshVFLayout::Scale); - RendererLayoutWithCustomSorting.SetVariable(CompiledData, MeshOrientationBinding.DataSetVariable, ENiagaraMeshVFLayout::Transform); - RendererLayoutWithCustomSorting.SetVariable(CompiledData, MaterialRandomBinding.DataSetVariable, ENiagaraMeshVFLayout::MaterialRandom); - RendererLayoutWithCustomSorting.SetVariable(CompiledData, NormalizedAgeBinding.DataSetVariable, ENiagaraMeshVFLayout::NormalizedAge); - RendererLayoutWithCustomSorting.SetVariable(CompiledData, CustomSortingBinding.DataSetVariable, ENiagaraMeshVFLayout::CustomSorting); - RendererLayoutWithCustomSorting.SetVariable(CompiledData, SubImageIndexBinding.DataSetVariable, ENiagaraMeshVFLayout::SubImage); - RendererLayoutWithCustomSorting.SetVariable(CompiledData, CameraOffsetBinding.DataSetVariable, ENiagaraMeshVFLayout::CameraOffset); - MaterialParamValidMask = RendererLayoutWithCustomSorting.SetVariable(CompiledData, DynamicMaterialBinding.DataSetVariable, ENiagaraMeshVFLayout::DynamicParam0) ? 0x1 : 0; - MaterialParamValidMask |= RendererLayoutWithCustomSorting.SetVariable(CompiledData, DynamicMaterial1Binding.DataSetVariable, ENiagaraMeshVFLayout::DynamicParam1) ? 0x2 : 0; - MaterialParamValidMask |= RendererLayoutWithCustomSorting.SetVariable(CompiledData, DynamicMaterial2Binding.DataSetVariable, ENiagaraMeshVFLayout::DynamicParam2) ? 0x4 : 0; - MaterialParamValidMask |= RendererLayoutWithCustomSorting.SetVariable(CompiledData, DynamicMaterial3Binding.DataSetVariable, ENiagaraMeshVFLayout::DynamicParam3) ? 0x8 : 0; + RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, PositionBinding, ENiagaraMeshVFLayout::Position); + RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, VelocityBinding, ENiagaraMeshVFLayout::Velocity); + RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, ColorBinding, ENiagaraMeshVFLayout::Color); + RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, ScaleBinding, ENiagaraMeshVFLayout::Scale); + RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, MeshOrientationBinding, ENiagaraMeshVFLayout::Transform); + RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, MaterialRandomBinding, ENiagaraMeshVFLayout::MaterialRandom); + RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, NormalizedAgeBinding, ENiagaraMeshVFLayout::NormalizedAge); + RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, CustomSortingBinding, ENiagaraMeshVFLayout::CustomSorting); + RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, SubImageIndexBinding, ENiagaraMeshVFLayout::SubImage); + RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, CameraOffsetBinding, ENiagaraMeshVFLayout::CameraOffset); + MaterialParamValidMask = RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, DynamicMaterialBinding, ENiagaraMeshVFLayout::DynamicParam0) ? 0x1 : 0; + MaterialParamValidMask |= RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, DynamicMaterial1Binding, ENiagaraMeshVFLayout::DynamicParam1) ? 0x2 : 0; + MaterialParamValidMask |= RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, DynamicMaterial2Binding, ENiagaraMeshVFLayout::DynamicParam2) ? 0x4 : 0; + MaterialParamValidMask |= RendererLayoutWithCustomSorting.SetVariableFromBinding(CompiledData, DynamicMaterial3Binding, ENiagaraMeshVFLayout::DynamicParam3) ? 0x8 : 0; RendererLayoutWithCustomSorting.Finalize(); RendererLayoutWithoutCustomSorting.Initialize(ENiagaraMeshVFLayout::Num); - RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, PositionBinding.DataSetVariable, ENiagaraMeshVFLayout::Position); - RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, VelocityBinding.DataSetVariable, ENiagaraMeshVFLayout::Velocity); - RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, ColorBinding.DataSetVariable, ENiagaraMeshVFLayout::Color); - RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, ScaleBinding.DataSetVariable, ENiagaraMeshVFLayout::Scale); - RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, MeshOrientationBinding.DataSetVariable, ENiagaraMeshVFLayout::Transform); - RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, MaterialRandomBinding.DataSetVariable, ENiagaraMeshVFLayout::MaterialRandom); - RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, NormalizedAgeBinding.DataSetVariable, ENiagaraMeshVFLayout::NormalizedAge); - RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, SubImageIndexBinding.DataSetVariable, ENiagaraMeshVFLayout::SubImage); - RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, CameraOffsetBinding.DataSetVariable, ENiagaraMeshVFLayout::CameraOffset); - MaterialParamValidMask = RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, DynamicMaterialBinding.DataSetVariable, ENiagaraMeshVFLayout::DynamicParam0) ? 0x1 : 0; - MaterialParamValidMask |= RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, DynamicMaterial1Binding.DataSetVariable, ENiagaraMeshVFLayout::DynamicParam1) ? 0x2 : 0; - MaterialParamValidMask |= RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, DynamicMaterial2Binding.DataSetVariable, ENiagaraMeshVFLayout::DynamicParam2) ? 0x4 : 0; - MaterialParamValidMask |= RendererLayoutWithoutCustomSorting.SetVariable(CompiledData, DynamicMaterial3Binding.DataSetVariable, ENiagaraMeshVFLayout::DynamicParam3) ? 0x8 : 0; + RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, PositionBinding, ENiagaraMeshVFLayout::Position); + RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, VelocityBinding, ENiagaraMeshVFLayout::Velocity); + RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, ColorBinding, ENiagaraMeshVFLayout::Color); + RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, ScaleBinding, ENiagaraMeshVFLayout::Scale); + RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, MeshOrientationBinding, ENiagaraMeshVFLayout::Transform); + RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, MaterialRandomBinding, ENiagaraMeshVFLayout::MaterialRandom); + RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, NormalizedAgeBinding, ENiagaraMeshVFLayout::NormalizedAge); + RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, SubImageIndexBinding, ENiagaraMeshVFLayout::SubImage); + RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, CameraOffsetBinding, ENiagaraMeshVFLayout::CameraOffset); + MaterialParamValidMask = RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, DynamicMaterialBinding, ENiagaraMeshVFLayout::DynamicParam0) ? 0x1 : 0; + MaterialParamValidMask |= RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, DynamicMaterial1Binding, ENiagaraMeshVFLayout::DynamicParam1) ? 0x2 : 0; + MaterialParamValidMask |= RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, DynamicMaterial2Binding, ENiagaraMeshVFLayout::DynamicParam2) ? 0x4 : 0; + MaterialParamValidMask |= RendererLayoutWithoutCustomSorting.SetVariableFromBinding(CompiledData, DynamicMaterial3Binding, ENiagaraMeshVFLayout::DynamicParam3) ? 0x8 : 0; RendererLayoutWithoutCustomSorting.Finalize(); } @@ -282,8 +282,10 @@ void UNiagaraMeshRendererProperties::PostLoad() { ParticleMesh->ConditionalPostLoad(); ParticleMesh->GetOnMeshChanged().AddUObject(this, &UNiagaraMeshRendererProperties::OnMeshChanged); + ParticleMesh->OnPostMeshBuild().AddUObject(this, &UNiagaraMeshRendererProperties::OnMeshPostBuild); } #endif + PostLoadBindings(ENiagaraRendererSourceDataMode::Particles); } #if WITH_EDITORONLY_DATA @@ -379,6 +381,7 @@ void UNiagaraMeshRendererProperties::BeginDestroy() if (GIsEditor && (ParticleMesh != nullptr)) { ParticleMesh->GetOnMeshChanged().RemoveAll(this); + ParticleMesh->OnPostMeshBuild().RemoveAll(this); } #endif } @@ -393,6 +396,7 @@ void UNiagaraMeshRendererProperties::PreEditChange(class FProperty* PropertyThat if (ParticleMesh != nullptr) { ParticleMesh->GetOnMeshChanged().RemoveAll(this); + ParticleMesh->OnPostMeshBuild().RemoveAll(this); } } } @@ -403,11 +407,23 @@ void UNiagaraMeshRendererProperties::PostEditChangeProperty(FPropertyChangedEven SubImageSize.Y = FMath::Max(SubImageSize.Y, 1.f); static FName ParticleMeshName(TEXT("ParticleMesh")); - if (ParticleMesh && PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == ParticleMeshName) + + if (ParticleMesh) { - // We only need to check material usage as we will invalidate any renderers later on - CheckMaterialUsage(); - ParticleMesh->GetOnMeshChanged().AddUObject(this, &UNiagaraMeshRendererProperties::OnMeshChanged); + const bool IsRedirect = PropertyChangedEvent.ChangeType == EPropertyChangeType::Redirected; + if (IsRedirect) + { + // Do this in case the redirected property is not ParticleMesh (we have no way of knowing b/c the property is nullptr) + ParticleMesh->GetOnMeshChanged().RemoveAll(this); + ParticleMesh->OnPostMeshBuild().RemoveAll(this); + } + if (IsRedirect || (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == ParticleMeshName)) + { + // We only need to check material usage as we will invalidate any renderers later on + CheckMaterialUsage(); + ParticleMesh->GetOnMeshChanged().AddUObject(this, &UNiagaraMeshRendererProperties::OnMeshChanged); + ParticleMesh->OnPostMeshBuild().AddUObject(this, &UNiagaraMeshRendererProperties::OnMeshPostBuild); + } } Super::PostEditChangeProperty(PropertyChangedEvent); @@ -426,6 +442,11 @@ void UNiagaraMeshRendererProperties::OnMeshChanged() CheckMaterialUsage(); } +void UNiagaraMeshRendererProperties::OnMeshPostBuild(UStaticMesh*) +{ + OnMeshChanged(); +} + void UNiagaraMeshRendererProperties::CheckMaterialUsage() { if (ParticleMesh && ParticleMesh->RenderData) diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraModule.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraModule.cpp index 77e810ed9efe..11bf5149fd01 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraModule.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraModule.cpp @@ -16,15 +16,11 @@ #include "NiagaraMeshRendererProperties.h" #include "NiagaraRibbonRendererProperties.h" #include "NiagaraComponentRendererProperties.h" +#include "NiagaraCustomVersion.h" #include "NiagaraRenderer.h" -#include "Misc/CoreDelegates.h" #include "NiagaraShaderModule.h" #include "UObject/CoreRedirects.h" #include "NiagaraEmitterInstanceBatcher.h" -#include "DeviceProfiles/DeviceProfileManager.h" -#include "Interfaces/ITargetPlatform.h" -#include "DeviceProfiles/DeviceProfile.h" -#include "Scalability.h" IMPLEMENT_MODULE(INiagaraModule, Niagara); @@ -138,9 +134,14 @@ FNiagaraVariable INiagaraModule::Particles_RibbonWidth; FNiagaraVariable INiagaraModule::Particles_RibbonTwist; FNiagaraVariable INiagaraModule::Particles_RibbonFacing; FNiagaraVariable INiagaraModule::Particles_RibbonLinkOrder; +FNiagaraVariable INiagaraModule::Particles_RibbonU0Override; +FNiagaraVariable INiagaraModule::Particles_RibbonV0RangeOverride; +FNiagaraVariable INiagaraModule::Particles_RibbonU1Override; +FNiagaraVariable INiagaraModule::Particles_RibbonV1RangeOverride; FNiagaraVariable INiagaraModule::Particles_VisibilityTag; FNiagaraVariable INiagaraModule::Particles_ComponentsEnabled; FNiagaraVariable INiagaraModule::ScriptUsage; +FNiagaraVariable INiagaraModule::ScriptContext; FNiagaraVariable INiagaraModule::DataInstance_Alive; FNiagaraVariable INiagaraModule::Translator_BeginDefaults; @@ -242,10 +243,15 @@ void INiagaraModule::StartupModule() Particles_RibbonTwist = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Particles.RibbonTwist")); Particles_RibbonFacing = FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Particles.RibbonFacing")); Particles_RibbonLinkOrder = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Particles.RibbonLinkOrder")); + Particles_RibbonU0Override = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Particles.RibbonU0Override")); + Particles_RibbonV0RangeOverride = FNiagaraVariable(FNiagaraTypeDefinition::GetVec2Def(), TEXT("Particles.RibbonV0RangeOverride")); + Particles_RibbonU1Override = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Particles.RibbonU1Override")); + Particles_RibbonV1RangeOverride = FNiagaraVariable(FNiagaraTypeDefinition::GetVec2Def(), TEXT("Particles.RibbonV1RangeOverride")); Particles_VisibilityTag = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Particles.VisibilityTag")); Particles_ComponentsEnabled = FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Particles.ComponentsEnabled")); ScriptUsage = FNiagaraVariable(FNiagaraTypeDefinition::GetScriptUsageEnum(), TEXT("Script.Usage")); + ScriptContext = FNiagaraVariable(FNiagaraTypeDefinition::GetScriptContextEnum(), TEXT("Script.Context")); DataInstance_Alive = FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("DataInstance.Alive")); Translator_BeginDefaults = FNiagaraVariable(FNiagaraTypeDefinition::GetParameterMapDef(), TEXT("Begin Defaults")); @@ -417,11 +423,13 @@ UScriptStruct* FNiagaraTypeDefinition::HalfVec4Struct; UClass* FNiagaraTypeDefinition::UObjectClass; UClass* FNiagaraTypeDefinition::UMaterialClass; +UClass* FNiagaraTypeDefinition::UTextureClass; UEnum* FNiagaraTypeDefinition::ExecutionStateEnum; UEnum* FNiagaraTypeDefinition::SimulationTargetEnum; UEnum* FNiagaraTypeDefinition::ExecutionStateSourceEnum; UEnum* FNiagaraTypeDefinition::ScriptUsageEnum; +UEnum* FNiagaraTypeDefinition::ScriptContextEnum; UEnum* FNiagaraTypeDefinition::ParameterScopeEnum; UEnum* FNiagaraTypeDefinition::ParameterPanelCategoryEnum; @@ -446,6 +454,7 @@ FNiagaraTypeDefinition FNiagaraTypeDefinition::HalfVec4Def; FNiagaraTypeDefinition FNiagaraTypeDefinition::UObjectDef; FNiagaraTypeDefinition FNiagaraTypeDefinition::UMaterialDef; +FNiagaraTypeDefinition FNiagaraTypeDefinition::UTextureDef; TSet FNiagaraTypeDefinition::NumericStructs; TArray FNiagaraTypeDefinition::OrderedNumericTypes; @@ -459,16 +468,19 @@ TSet FNiagaraTypeDefinition::BoolStructs; FNiagaraTypeDefinition FNiagaraTypeDefinition::CollisionEventDef; -TArray FNiagaraTypeRegistry::RegisteredTypes; +FNiagaraTypeRegistry::RegisteredTypesArray FNiagaraTypeRegistry::RegisteredTypes; TArray FNiagaraTypeRegistry::RegisteredParamTypes; TArray FNiagaraTypeRegistry::RegisteredPayloadTypes; TArray FNiagaraTypeRegistry::RegisteredUserDefinedTypes; TArray FNiagaraTypeRegistry::RegisteredNumericTypes; +TMap FNiagaraTypeRegistry::RegisteredTypeIndexMap; +FRWLock FNiagaraTypeRegistry::RegisteredTypesLock; bool FNiagaraTypeDefinition::IsDataInterface()const { - return GetStruct()->IsChildOf(UNiagaraDataInterface::StaticClass()); + UStruct* ClassStruct = GetStruct(); + return ClassStruct ? ClassStruct->IsChildOf(UNiagaraDataInterface::StaticClass()) : false; } void FNiagaraTypeDefinition::Init() @@ -496,6 +508,7 @@ void FNiagaraTypeDefinition::Init() FNiagaraTypeDefinition::UObjectClass = UObject::StaticClass(); FNiagaraTypeDefinition::UMaterialClass = UMaterialInterface::StaticClass(); + FNiagaraTypeDefinition::UTextureClass = UTexture::StaticClass(); ParameterMapDef = FNiagaraTypeDefinition(ParameterMapStruct); IDDef = FNiagaraTypeDefinition(IDStruct); @@ -517,6 +530,7 @@ void FNiagaraTypeDefinition::Init() UObjectDef = FNiagaraTypeDefinition(UObjectClass); UMaterialDef = FNiagaraTypeDefinition(UMaterialClass); + UTextureDef = FNiagaraTypeDefinition(UTextureClass); CollisionEventDef = FNiagaraTypeDefinition(FNiagaraCollisionEventPayload::StaticStruct()); NumericStructs.Add(NumericStruct); @@ -558,6 +572,7 @@ void FNiagaraTypeDefinition::Init() ExecutionStateSourceEnum = StaticEnum(); SimulationTargetEnum = StaticEnum(); ScriptUsageEnum = StaticEnum(); + ScriptContextEnum = StaticEnum(); ParameterScopeEnum = StaticEnum(); ParameterPanelCategoryEnum = StaticEnum(); @@ -691,6 +706,9 @@ void FNiagaraTypeDefinition::RecreateUserDefinedTypeRegistry() FNiagaraTypeRegistry::Register(UObjectDef, true, false, false); FNiagaraTypeRegistry::Register(UMaterialDef, true, false, false); + FNiagaraTypeRegistry::Register(UTextureDef, true, false, false); + FNiagaraTypeRegistry::Register(FNiagaraRandInfo::StaticStruct(), true, true, false); + FNiagaraTypeRegistry::Register(StaticEnum(), true, true, false); const UNiagaraSettings* Settings = GetDefault(); check(Settings); @@ -724,7 +742,10 @@ void FNiagaraTypeDefinition::RecreateUserDefinedTypeRegistry() { FNiagaraTypeRegistry::Register(ScriptStruct, ParamRefFound != nullptr, PayloadRefFound != nullptr, true); } - if (Obj->GetPathName() != AssetRefPathNamePreResolve.ToString()) + + UObjectRedirector* Redirector = FindObject(nullptr, *AssetRefPathNamePreResolve.ToString()); + bool bRedirectorFollowed = Redirector && (Redirector->DestinationObject == Obj); + if (!bRedirectorFollowed && Obj->GetPathName() != AssetRefPathNamePreResolve.ToString()) { UE_LOG(LogNiagara, Warning, TEXT("Additional parameter/payload enum has moved from where it was in settings (this may cause errors at runtime): Was: \"%s\" Now: \"%s\""), *AssetRefPathNamePreResolve.ToString(), *Obj->GetPathName()); } @@ -736,7 +757,6 @@ void FNiagaraTypeDefinition::RecreateUserDefinedTypeRegistry() } } - for (FSoftObjectPath AssetRef : Settings->AdditionalParameterEnums) { FName AssetRefPathNamePreResolve = AssetRef.GetAssetPathName(); @@ -756,7 +776,9 @@ void FNiagaraTypeDefinition::RecreateUserDefinedTypeRegistry() FNiagaraTypeRegistry::Register(Enum, ParamRefFound != nullptr, PayloadRefFound != nullptr, true); } - if (Obj->GetPathName() != AssetRefPathNamePreResolve.ToString()) + UObjectRedirector* Redirector = FindObject(nullptr, *AssetRefPathNamePreResolve.ToString()); + bool bRedirectorFollowed = Redirector && (Redirector->DestinationObject == Obj); + if (!bRedirectorFollowed && Obj->GetPathName() != AssetRefPathNamePreResolve.ToString()) { UE_LOG(LogNiagara, Warning, TEXT("Additional parameter/payload enum has moved from where it was in settings (this may cause errors at runtime): Was: \"%s\" Now: \"%s\""), *AssetRefPathNamePreResolve.ToString(), *Obj->GetPathName()); } @@ -766,10 +788,6 @@ void FNiagaraTypeDefinition::RecreateUserDefinedTypeRegistry() UE_LOG(LogNiagara, Warning, TEXT("Could not find additional parameter/payload enum: %s"), *AssetRef.ToString()); } } - - FNiagaraTypeRegistry::Register(FNiagaraRandInfo::StaticStruct(), true, true, true); - - FNiagaraTypeRegistry::Register(StaticEnum(), true, true, false); } #endif @@ -976,6 +994,97 @@ void INiagaraModule::ProcessShaderCompilationQueue() return OnProcessQueue.Execute(); } +const FNiagaraTypeDefinition& FNiagaraTypeDefinitionHandle::Resolve() const +{ + const auto& RegisteredTypes = FNiagaraTypeRegistry::GetRegisteredTypes(); + + if (RegisteredTypes.IsValidIndex(RegisteredTypeIndex)) + { + return RegisteredTypes[RegisteredTypeIndex]; + } + + static FNiagaraTypeDefinition Dummy; + return Dummy; +} + +int32 FNiagaraTypeDefinitionHandle::Register(const FNiagaraTypeDefinition& TypeDef) const +{ + if (!TypeDef.IsValid()) + { + return INDEX_NONE; + } + + return FNiagaraTypeRegistry::RegisterIndexed(TypeDef); +} + +FArchive& operator<<(FArchive& Ar, FNiagaraTypeDefinitionHandle& Handle) +{ + UScriptStruct* TypeDefStruct = FNiagaraTypeDefinition::StaticStruct(); + + if (Ar.IsSaving()) + { + FNiagaraTypeDefinition TypeDef = *Handle; + TypeDefStruct->SerializeItem(Ar, &TypeDef, nullptr); + } + else if (Ar.IsLoading()) + { + FNiagaraTypeDefinition TypeDef; + TypeDefStruct->SerializeItem(Ar, &TypeDef, nullptr); + Handle = FNiagaraTypeDefinitionHandle(TypeDef); + } + + return Ar; +} + +bool FNiagaraVariableBase::Serialize(FArchive& Ar) +{ + Ar.UsingCustomVersion(FNiagaraCustomVersion::GUID); + const int32 NiagaraVersion = Ar.CustomVer(FNiagaraCustomVersion::GUID); + + if (!Ar.IsLoading() || NiagaraVersion >= FNiagaraCustomVersion::VariablesUseTypeDefRegistry) + { + Ar << Name; + Ar << TypeDefHandle; + return true; + } + + return false; +} + +#if WITH_EDITORONLY_DATA +void FNiagaraVariableBase::PostSerialize(const FArchive& Ar) +{ + if (Ar.IsLoading() && Ar.CustomVer(FNiagaraCustomVersion::GUID) < FNiagaraCustomVersion::VariablesUseTypeDefRegistry) + { + TypeDefHandle = FNiagaraTypeDefinitionHandle(TypeDef_DEPRECATED); + } +} +#endif + +bool FNiagaraVariable::Serialize(FArchive& Ar) +{ + FNiagaraVariableBase::Serialize(Ar); + + Ar.UsingCustomVersion(FNiagaraCustomVersion::GUID); + const int32 NiagaraVersion = Ar.CustomVer(FNiagaraCustomVersion::GUID); + + if (!Ar.IsLoading() || NiagaraVersion >= FNiagaraCustomVersion::VariablesUseTypeDefRegistry) + { + Ar << VarData; + return true; + } + + // loading legacy data + return false; +} + +#if WITH_EDITORONLY_DATA +void FNiagaraVariable::PostSerialize(const FArchive& Ar) +{ + FNiagaraVariableBase::PostSerialize(Ar); +} +#endif + #if WITH_EDITOR const TArray& FNiagaraGlobalParameters::GetVariables() { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraParameterStore.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraParameterStore.cpp index fc69f6f30b30..e6a32634d3f2 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraParameterStore.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraParameterStore.cpp @@ -1,11 +1,10 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraParameterStore.h" -#include "NiagaraCommon.h" #include "NiagaraDataSet.h" #include "NiagaraComponent.h" +#include "NiagaraCustomVersion.h" #include "NiagaraSystemInstance.h" -#include "NiagaraParameterCollection.h" #include "NiagaraStats.h" DECLARE_CYCLE_STAT(TEXT("Parameter store bind"), STAT_NiagaraParameterStoreBind, STATGROUP_Niagara); @@ -105,6 +104,29 @@ struct FNiagaraVariableSearch } }; +bool FNiagaraVariableWithOffset::Serialize(FArchive& Ar) +{ + FNiagaraVariableBase::Serialize(Ar); + + Ar.UsingCustomVersion(FNiagaraCustomVersion::GUID); + const int32 NiagaraVersion = Ar.CustomVer(FNiagaraCustomVersion::GUID); + + if (!Ar.IsLoading() || NiagaraVersion >= FNiagaraCustomVersion::VariablesUseTypeDefRegistry) + { + Ar << Offset; + return true; + } + + return false; +} + +#if WITH_EDITORONLY_DATA +void FNiagaraVariableWithOffset::PostSerialize(const FArchive& Ar) +{ + FNiagaraVariableBase::PostSerialize(Ar); +} +#endif + ////////////////////////////////////////////////////////////////////////// void FNiagaraParameterStore::CopySortedParameterOffsets(TArrayView Src) @@ -698,7 +720,6 @@ void FNiagaraParameterStore::SanityCheckData(bool bInitInterfaces) { if (DataInterfaces.Num() <= SrcIndex) { - int32 OriginalNum = DataInterfaces.Num(); int32 NewNum = SrcIndex - DataInterfaces.Num() + 1; DataInterfaces.AddZeroed(NewNum); UE_LOG(LogNiagara, Verbose, TEXT("Missing data interfaces! Had to add %d data interface entries to ParameterStore on %s"), NewNum , Owner != nullptr ? *Owner->GetPathName() : TEXT("Unknown owner")); @@ -717,7 +738,6 @@ void FNiagaraParameterStore::SanityCheckData(bool bInitInterfaces) { if (UObjects.Num() <= SrcIndex) { - int32 OriginalNum = UObjects.Num(); int32 NewNum = SrcIndex - UObjects.Num() + 1; UObjects.AddZeroed(NewNum); UE_LOG(LogNiagara, Verbose, TEXT("Missing UObject interfaces! Had to add %d UObject entries for %s on %s"), NewNum , *Parameter.GetName().ToString(), Owner != nullptr ? *Owner->GetPathName() : TEXT("Unknown owner")); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRenderer.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRenderer.cpp index c883609896eb..949bbd47298e 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRenderer.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRenderer.cpp @@ -10,6 +10,7 @@ #include "DynamicBufferAllocator.h" #include "NiagaraEmitterInstanceBatcher.h" #include "NiagaraGPUSortInfo.h" +#include "Materials/MaterialInstanceDynamic.h" DECLARE_CYCLE_STAT(TEXT("Sort Particles"), STAT_NiagaraSortParticles, STATGROUP_Niagara); DECLARE_CYCLE_STAT(TEXT("Global Float Alloc - All"), STAT_NiagaraAllocateGlobalFloatAll, STATGROUP_Niagara); @@ -248,7 +249,7 @@ FNiagaraDynamicDataBase::~FNiagaraDynamicDataBase() } } -FNiagaraDataBuffer* FNiagaraDynamicDataBase::GetParticleDataToRender(bool bUseTranslucent) const +FNiagaraDataBuffer* FNiagaraDynamicDataBase::GetParticleDataToRender(bool bIsLowLatencyTranslucent)const { FNiagaraDataBuffer* Ret = nullptr; @@ -258,8 +259,7 @@ FNiagaraDataBuffer* FNiagaraDynamicDataBase::GetParticleDataToRender(bool bUseTr } else { - bUseTranslucent &= Data.GPUExecContext->GetTranslucentDataToRender() != nullptr; - Ret = bUseTranslucent ? Data.GPUExecContext->GetTranslucentDataToRender() : Data.GPUExecContext->GetDataToRender(); + Ret = Data.GPUExecContext->GetDataToRender(bIsLowLatencyTranslucent); } checkSlow(Ret == nullptr || Ret->IsBeingRead()); @@ -287,17 +287,38 @@ FNiagaraRenderer::FNiagaraRenderer(ERHIFeatureLevel::Type InFeatureLevel, const #endif } -void FNiagaraRenderer::Initialize(const UNiagaraRendererProperties *InProps, const FNiagaraEmitterInstance* Emitter) +void FNiagaraRenderer::Initialize(const UNiagaraRendererProperties *InProps, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) { //Get our list of valid base materials. Fall back to default material if they're not valid. InProps->GetUsedMaterials(Emitter, BaseMaterials_GT); + bool bCreateMidsForUsedMaterials = InProps->NeedsMIDsForMaterials(); + + uint32 Index = 0; for (UMaterialInterface*& Mat : BaseMaterials_GT) { if (!IsMaterialValid(Mat)) { Mat = UMaterial::GetDefaultMaterial(MD_Surface); } - BaseMaterialRelevance_GT |= Mat->GetRelevance_Concurrent(FeatureLevel); + else if (Mat && bCreateMidsForUsedMaterials && !Mat->IsA()) + { + const UNiagaraComponent* Comp = InComponent; + for (const FNiagaraMaterialOverride& Override : Comp->EmitterMaterials) + { + if (Override.EmitterRendererProperty == InProps) + { + if (Index == Override.MaterialSubIndex) + { + Mat = Override.Material; + continue; + } + } + } + } + + Index ++; + if (Mat) + BaseMaterialRelevance_GT |= Mat->GetRelevance_Concurrent(FeatureLevel); } } @@ -387,6 +408,85 @@ struct FParticleOrderAsUint FORCEINLINE operator uint32() const { return OrderAsUint; } }; +void FNiagaraRenderer::ProcessMaterialParameterBindings(TConstArrayView< FNiagaraMaterialAttributeBinding > InMaterialParameterBindings, const FNiagaraEmitterInstance* InEmitter, TConstArrayView InMaterials) const +{ + if (InMaterialParameterBindings.Num() == 0 || !InEmitter) + return; + + FNiagaraSystemInstance* SystemInstance = InEmitter->GetParentSystemInstance(); + if (SystemInstance) + { + auto SystemSim = SystemInstance->GetSystemSimulation(); + + if (SystemSim.IsValid()) + { + for (UMaterialInterface* Mat : InMaterials) + { + UMaterialInstanceDynamic* MatDyn = Cast< UMaterialInstanceDynamic>(Mat); + if (MatDyn) + { + for (const FNiagaraMaterialAttributeBinding& Binding : InMaterialParameterBindings) + { + + if (Binding.GetParamMapBindableVariable().GetType() == FNiagaraTypeDefinition::GetVec4Def() || + (Binding.GetParamMapBindableVariable().GetType().IsDataInterface() && Binding.NiagaraChildVariable.GetType() == FNiagaraTypeDefinition::GetVec4Def())) + { + FLinearColor Var(1.0f, 1.0f, 1.0f, 1.0f); + InEmitter->GetBoundRendererValue_GT(Binding.GetParamMapBindableVariable(), Binding.NiagaraChildVariable, &Var); + MatDyn->SetVectorParameterValue(Binding.MaterialParameterName, Var); + } + else if (Binding.GetParamMapBindableVariable().GetType() == FNiagaraTypeDefinition::GetColorDef() || + (Binding.GetParamMapBindableVariable().GetType().IsDataInterface() && Binding.NiagaraChildVariable.GetType() == FNiagaraTypeDefinition::GetColorDef())) + { + FLinearColor Var(1.0f, 1.0f, 1.0f, 1.0f); + InEmitter->GetBoundRendererValue_GT(Binding.GetParamMapBindableVariable(), Binding.NiagaraChildVariable, &Var); + MatDyn->SetVectorParameterValue(Binding.MaterialParameterName, Var); + } + else if (Binding.GetParamMapBindableVariable().GetType() == FNiagaraTypeDefinition::GetVec3Def() || + (Binding.GetParamMapBindableVariable().GetType().IsDataInterface() && Binding.NiagaraChildVariable.GetType() == FNiagaraTypeDefinition::GetVec3Def())) + { + FLinearColor Var(1.0f, 1.0f, 1.0f, 1.0f); + InEmitter->GetBoundRendererValue_GT(Binding.GetParamMapBindableVariable(), Binding.NiagaraChildVariable, &Var); + MatDyn->SetVectorParameterValue(Binding.MaterialParameterName, Var); + } + else if (Binding.GetParamMapBindableVariable().GetType() == FNiagaraTypeDefinition::GetVec2Def() || + (Binding.GetParamMapBindableVariable().GetType().IsDataInterface() && Binding.NiagaraChildVariable.GetType() == FNiagaraTypeDefinition::GetVec2Def())) + { + FLinearColor Var(1.0f, 1.0f, 1.0f, 1.0f); + InEmitter->GetBoundRendererValue_GT(Binding.GetParamMapBindableVariable(), Binding.NiagaraChildVariable, &Var); + MatDyn->SetVectorParameterValue(Binding.MaterialParameterName, Var); + } + else if (Binding.GetParamMapBindableVariable().GetType() == FNiagaraTypeDefinition::GetFloatDef() || + (Binding.GetParamMapBindableVariable().GetType().IsDataInterface() && Binding.NiagaraChildVariable.GetType() == FNiagaraTypeDefinition::GetFloatDef())) + { + float Var = 1.0f; + InEmitter->GetBoundRendererValue_GT(Binding.GetParamMapBindableVariable(), Binding.NiagaraChildVariable, &Var); + MatDyn->SetScalarParameterValue(Binding.MaterialParameterName, Var); + } + else if (Binding.GetParamMapBindableVariable().GetType() == FNiagaraTypeDefinition::GetUObjectDef() || + Binding.GetParamMapBindableVariable().GetType() == FNiagaraTypeDefinition::GetUTextureDef() || + (Binding.GetParamMapBindableVariable().GetType().IsDataInterface() && Binding.NiagaraChildVariable.GetType() == FNiagaraTypeDefinition::GetUTextureDef())) + { + UObject* Var = nullptr; + InEmitter->GetBoundRendererValue_GT(Binding.GetParamMapBindableVariable(), Binding.NiagaraChildVariable, &Var); + if (Var) + { + UTexture* Tex = Cast(Var); + if (Tex && Tex->Resource != nullptr) + { + MatDyn->SetTextureParameterValue(Binding.MaterialParameterName, Tex); + continue; + } + + } + } + } + } + } + } + } +} + void FNiagaraRenderer::SortIndices(const FNiagaraGPUSortInfo& SortInfo, const FNiagaraRendererVariableInfo& SortVariable, const FNiagaraDataBuffer& Buffer, FGlobalDynamicReadBuffer::FAllocation& OutIndices) { SCOPE_CYCLE_COUNTER(STAT_NiagaraSortParticles); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererComponents.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererComponents.cpp index 0e5711b33caa..aa63dbdc0c3f 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererComponents.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererComponents.cpp @@ -48,7 +48,7 @@ void SetVariableByType(FNiagaraVariable& DataVariable, FNiagaraDataSet& Data, in else if (VarType == FNiagaraTypeDefinition::GetColorDef()) { SetValueWithAccessor(DataVariable, Data, ParticleIndex); } } -void ConvertVariableToType(FNiagaraVariable& SourceVariable, FNiagaraVariable& TargetVariable) +void ConvertVariableToType(const FNiagaraVariable& SourceVariable, FNiagaraVariable& TargetVariable) { FNiagaraTypeDefinition SourceType = SourceVariable.GetType(); FNiagaraTypeDefinition TargetType = TargetVariable.GetType(); @@ -130,7 +130,7 @@ void InvokeSetterFunction(UObject* InRuntimeObject, UFunction* Setter, const uin InRuntimeObject->ProcessEvent(Setter, Params); } -void FNiagaraRendererComponents::Initialize(const UNiagaraRendererProperties* InProperties, const FNiagaraEmitterInstance* Emitter) +void FNiagaraRendererComponents::Initialize(const UNiagaraRendererProperties* InProperties, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) { const UNiagaraComponentRendererProperties* Properties = CastChecked(InProperties); if (!Properties) @@ -183,7 +183,7 @@ void FNiagaraRendererComponents::Initialize(const UNiagaraRendererProperties* In if (Property->IsInContainer(SetterFunction->ParmsSize) && Property->HasAnyPropertyFlags(CPF_Parm) && !Property->HasAnyPropertyFlags(CPF_ReturnParm)) { FNiagaraTypeDefinition FieldType = UNiagaraComponentRendererProperties::ToNiagaraType(Property); - if (FieldType != PropertyBinding.PropertyType && FieldType == PropertyBinding.AttributeBinding.BoundVariable.GetType()) + if (FieldType != PropertyBinding.PropertyType && FieldType == PropertyBinding.AttributeBinding.GetType()) { // we can use the original Niagara value with the setter instead of converting it Setter.bIgnoreConversion = true; @@ -216,7 +216,7 @@ FNiagaraDynamicDataBase* FNiagaraRendererComponents::GenerateDynamicData(const F } FNiagaraDataSet& Data = Emitter->GetData(); FNiagaraDataBuffer& ParticleData = Data.GetCurrentDataChecked(); - FNiagaraDataSetReaderInt32 EnabledAccessor = FNiagaraDataSetAccessor::CreateReader(Data, Properties->EnabledBinding.DataSetVariable.GetName()); + FNiagaraDataSetReaderInt32 EnabledAccessor = FNiagaraDataSetAccessor::CreateReader(Data, Properties->EnabledBinding.GetDataSetBindableVariable().GetName()); FNiagaraDataSetReaderInt32 IDAccessor = FNiagaraDataSetAccessor::CreateReader(Data, FName("UniqueID")); int32 TaskLimitLeft = Properties->ComponentCountLimit; @@ -239,11 +239,15 @@ FNiagaraDynamicDataBase* FNiagaraRendererComponents::GenerateDynamicData(const F } TArray BindingsCopy = Properties->PropertyBindings; + for (FNiagaraComponentPropertyBinding& PropertyBinding : BindingsCopy) { PropertyBinding.SetterFunction = SetterFunctionMapping[PropertyBinding.PropertyName].Function; - FNiagaraVariable& DataVariable = PropertyBinding.AttributeBinding.DataSetVariable; + FNiagaraVariable& DataVariable = PropertyBinding.WritableValue; + const FNiagaraVariableBase& FoundVar = PropertyBinding.AttributeBinding.GetDataSetBindableVariable(); + DataVariable.SetType(FoundVar.GetType()); + DataVariable.SetName(FoundVar.GetName()); DataVariable.ClearData(); if (!DataVariable.IsValid() || !Data.HasVariable(DataVariable)) { @@ -271,7 +275,7 @@ FNiagaraDynamicDataBase* FNiagaraRendererComponents::GenerateDynamicData(const F for (const FNiagaraComponentPropertyBinding& PropertyBinding : BindingsCopy) { - const FNiagaraVariable& DataVariable = PropertyBinding.AttributeBinding.DataSetVariable; + const FNiagaraVariable& DataVariable = PropertyBinding.WritableValue; if (!DataVariable.IsDataAllocated()) { continue; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererLights.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererLights.cpp index b23f9127df93..86ed9d85be8b 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererLights.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererLights.cpp @@ -77,11 +77,18 @@ FNiagaraDynamicDataBase* FNiagaraRendererLights::GenerateDynamicData(const FNiag const auto ScatteringReader = Properties->ScatteringDataSetAccessor.GetReader(Data); const auto EnabledReader = Properties->EnabledDataSetAccessor.GetReader(Data); - const FMatrix& LocalToWorldMatrix = Proxy->GetLocalToWorld(); - const FLinearColor DefaultColor = Properties->ColorBinding.DefaultValueIfNonExistent.GetValue(); - const FVector DefaultPos = LocalToWorldMatrix.GetOrigin(); - const float DefaultRadius = Properties->RadiusBinding.DefaultValueIfNonExistent.GetValue(); - const float DefaultScattering = Properties->VolumetricScatteringBinding.DefaultValueIfNonExistent.GetValue(); + // This used to use Proxy->GetLocalToWorld(), but that's a bad thing to do here, because the proxy gets updated on the render thread, + // and this function happens during EndOfFrame updates. So instead, use the most up-to-date transform here (fixes local-space frame-behind issues) + FTransform LocalToWorld = FTransform::Identity; + if (FNiagaraSystemInstance* SystemInstance = Emitter->GetParentSystemInstance()) + { + LocalToWorld = SystemInstance->GetWorldTransform(); + } + + const FLinearColor DefaultColor = Properties->ColorBinding.GetDefaultValue(); + const FVector DefaultPos = bLocalSpace ? FVector::ZeroVector : LocalToWorld.GetLocation(); + const float DefaultRadius = Properties->RadiusBinding.GetDefaultValue(); + const float DefaultScattering = Properties->VolumetricScatteringBinding.GetDefaultValue(); const FNiagaraBool DefaultEnabled(true); for (uint32 ParticleIndex = 0; ParticleIndex < DataToRender->GetNumInstances(); ParticleIndex++) @@ -100,7 +107,7 @@ FNiagaraDynamicDataBase* FNiagaraRendererLights::GenerateDynamicData(const FNiag LightData.PerViewEntry.Position = PositionReader.GetSafe(ParticleIndex, DefaultPos); if (bLocalSpace) { - LightData.PerViewEntry.Position = LocalToWorldMatrix.TransformPosition(LightData.PerViewEntry.Position); + LightData.PerViewEntry.Position = LocalToWorld.TransformPosition(LightData.PerViewEntry.Position); } } } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererMeshes.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererMeshes.cpp index e1382a452b74..f6a788d3d9e0 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererMeshes.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererMeshes.cpp @@ -113,7 +113,7 @@ FNiagaraRendererMeshes::FNiagaraRendererMeshes(ERHIFeatureLevel::Type FeatureLev RendererVisTagOffset = INDEX_NONE; int32 FloatOffset; int32 HalfOffset; - if (Data.GetVariableComponentOffsets(Properties->RendererVisibilityTagBinding.DataSetVariable, FloatOffset, RendererVisTagOffset, HalfOffset)) + if (Data.GetVariableComponentOffsets(Properties->RendererVisibilityTagBinding.GetDataSetBindableVariable(), FloatOffset, RendererVisTagOffset, HalfOffset)) { // If the renderer visibility tag is bound, we have to do it in the culling pass bEnableCulling = true; @@ -771,7 +771,7 @@ void FNiagaraRendererMeshes::GetDynamicRayTracingInstances(FRayTracingMaterialGa if (SimTarget == ENiagaraSimTarget::CPUSim) { - FVector4 InstancePos = GetInstancePosition(InstanceIndex); + FVector4 InstancePos = bHasPosition ? GetInstancePosition(InstanceIndex) : FVector4(0,0,0,0); FVector4 Transform1 = FVector4(1.0f, 0.0f, 0.0f, InstancePos.X); FVector4 Transform2 = FVector4(0.0f, 1.0f, 0.0f, InstancePos.Y); @@ -934,7 +934,7 @@ int FNiagaraRendererMeshes::GetDynamicDataSize()const return Size; } -bool FNiagaraRendererMeshes::IsMaterialValid(UMaterialInterface* Mat)const +bool FNiagaraRendererMeshes::IsMaterialValid(const UMaterialInterface* Mat)const { return Mat && Mat->CheckMaterialUsage_Concurrent(MATUSAGE_NiagaraMeshParticles); } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererProperties.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererProperties.cpp index b8f97ddf99f0..f0551dd3c276 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererProperties.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererProperties.cpp @@ -3,6 +3,8 @@ #include "NiagaraTypes.h" #include "NiagaraCommon.h" #include "NiagaraDataSet.h" +#include "NiagaraConstants.h" +#include "NiagaraEmitter.h" #include "Interfaces/ITargetPlatform.h" #include "Styling/SlateIconFinder.h" @@ -74,6 +76,14 @@ bool FNiagaraRendererLayout::SetVariable(const FNiagaraDataSetCompiledData* Comp return Offset != INDEX_NONE; } + +bool FNiagaraRendererLayout::SetVariableFromBinding(const FNiagaraDataSetCompiledData* CompiledData, const FNiagaraVariableAttributeBinding& VariableBinding, int32 VFVarOffset) +{ + if (VariableBinding.IsParticleBinding()) + return SetVariable(CompiledData, VariableBinding.GetDataSetBindableVariable(), VFVarOffset); + return false; +} + void FNiagaraRendererLayout::Finalize() { ENQUEUE_RENDER_COMMAND(NiagaraFinalizeLayout) @@ -88,16 +98,26 @@ void FNiagaraRendererLayout::Finalize() } #if WITH_EDITORONLY_DATA +bool UNiagaraRendererProperties::IsSupportedVariableForBinding(const FNiagaraVariableBase& InSourceForBinding, const FName& InTargetBindingName) const +{ + if (InSourceForBinding.IsInNameSpace(FNiagaraConstants::ParticleAttributeNamespace)) + { + return true; + } + return false; +} + const TArray& UNiagaraRendererProperties::GetBoundAttributes() { CurrentBoundAttributes.Reset(); for (const FNiagaraVariableAttributeBinding* AttributeBinding : AttributeBindings) { - if (AttributeBinding->BoundVariable.IsValid()) + if (AttributeBinding->GetParamMapBindableVariable().IsValid()) { - CurrentBoundAttributes.Add(AttributeBinding->BoundVariable); + CurrentBoundAttributes.Add(AttributeBinding->GetParamMapBindableVariable()); } + /* else if (AttributeBinding->DataSetVariable.IsValid()) { CurrentBoundAttributes.Add(AttributeBinding->DataSetVariable); @@ -105,7 +125,7 @@ const TArray& UNiagaraRendererProperties::GetBoundAttributes() else { CurrentBoundAttributes.Add(AttributeBinding->DefaultValueIfNonExistent); - } + }*/ } return CurrentBoundAttributes; @@ -147,7 +167,7 @@ uint32 UNiagaraRendererProperties::ComputeMaxUsedComponents(const FNiagaraDataSe for (const FNiagaraVariableAttributeBinding* Binding : AttributeBindings) { - const FNiagaraVariable& Var = Binding->DataSetVariable; + const FNiagaraVariable& Var = Binding->GetDataSetBindableVariable(); const int32 VariableIndex = CompiledDataSetData->Variables.IndexOfByKey(Var); if ( VariableIndex != INDEX_NONE ) @@ -185,3 +205,48 @@ bool UNiagaraRendererProperties::NeedsLoadForTargetPlatform(const class ITargetP { return bIsEnabled && Platforms.IsEnabledForPlatform(TargetPlatform->IniPlatformName()); } + +void UNiagaraRendererProperties::PostLoadBindings(ENiagaraRendererSourceDataMode InSourceMode) +{ + + for (int32 i = 0; i < AttributeBindings.Num(); i++) + { + FNiagaraVariableAttributeBinding* Binding = const_cast(AttributeBindings[i]); + Binding->PostLoad(InSourceMode); + } + + UpdateSourceModeDerivates(InSourceMode); + +} + +void UNiagaraRendererProperties::PostInitProperties() +{ + Super::PostInitProperties(); +#if WITH_EDITOR + if (HasAnyFlags(RF_ClassDefaultObject) == false) + { + SetFlags(RF_Transactional); + } +#endif +} + +void UNiagaraRendererProperties::UpdateSourceModeDerivates(ENiagaraRendererSourceDataMode InSourceMode, bool bFromPropertyEdit) +{ + UNiagaraEmitter* SrcEmitter = GetTypedOuter(); + if (SrcEmitter) + { + //TArray AttributeBindings; + //GetBindingsArray(AttributeBindings); + for (const FNiagaraVariableAttributeBinding* Binding : AttributeBindings) + { + ((FNiagaraVariableAttributeBinding*)Binding)->CacheValues(SrcEmitter, InSourceMode); + } + +#if WITH_EDITORONLY_DATA + if (bFromPropertyEdit) + { + FNiagaraSystemUpdateContext Context(SrcEmitter, true); + } +#endif + } +} \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererRibbons.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererRibbons.cpp index bf3ca868b532..656197123199 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererRibbons.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererRibbons.cpp @@ -128,14 +128,14 @@ struct FNiagaraDynamicDataRibbon : public FNiagaraDynamicDataBase /** Ribbon perperties required for sorting. */ TArray MultiRibbonInfos; - void PackPerRibbonData(float U0Scale, float U0Offset, float U1Scale, float U1Offset, uint32 NumSegments, uint32 FirstParticleId) + void PackPerRibbonData(float U0Scale, float U0Offset, float U0DistributionScaler, float U1Scale, float U1Offset, float U1DistributionScaler, uint32 FirstParticleId) { - const float OneOverNumSegments = 1 / (float)FMath::Max(1, NumSegments); PackedPerRibbonDataByIndex.Add(U0Scale); PackedPerRibbonDataByIndex.Add(U0Offset); + PackedPerRibbonDataByIndex.Add(U0DistributionScaler); PackedPerRibbonDataByIndex.Add(U1Scale); PackedPerRibbonDataByIndex.Add(U1Offset); - PackedPerRibbonDataByIndex.Add(OneOverNumSegments); + PackedPerRibbonDataByIndex.Add(U1DistributionScaler); PackedPerRibbonDataByIndex.Add(*(float*)&FirstParticleId); } }; @@ -155,12 +155,6 @@ public: FNiagaraRendererRibbons::FNiagaraRendererRibbons(ERHIFeatureLevel::Type FeatureLevel, const UNiagaraRendererProperties *InProps, const FNiagaraEmitterInstance* Emitter) : FNiagaraRenderer(FeatureLevel, InProps, Emitter) , FacingMode(ENiagaraRibbonFacingMode::Screen) - , UV0TilingDistance(0.0f) - , UV0Scale(FVector2D(1.0f, 1.0f)) - , UV0AgeOffsetMode(ENiagaraRibbonAgeOffsetMode::Scale) - , UV1TilingDistance(0.0f) - , UV1Scale(FVector2D(1.0f, 1.0f)) - , UV1AgeOffsetMode(ENiagaraRibbonAgeOffsetMode::Scale) , TessellationMode(ENiagaraRibbonTessellationMode::Automatic) , CustomCurveTension(0.f) , CustomTessellationFactor(16) @@ -170,14 +164,8 @@ FNiagaraRendererRibbons::FNiagaraRendererRibbons(ERHIFeatureLevel::Type FeatureL { const UNiagaraRibbonRendererProperties* Properties = CastChecked(InProps); FacingMode = Properties->FacingMode; - UV0TilingDistance = Properties->UV0TilingDistance; - UV0Scale = Properties->UV0Scale; - UV0Offset = Properties->UV0Offset; - UV0AgeOffsetMode = Properties->UV0AgeOffsetMode; - UV1TilingDistance = Properties->UV1TilingDistance; - UV1Scale = Properties->UV1Scale; - UV1Offset = Properties->UV1Offset; - UV1AgeOffsetMode = Properties->UV1AgeOffsetMode; + UV0Settings = Properties->UV0Settings; + UV1Settings = Properties->UV1Settings; DrawDirection = Properties->DrawDirection; TessellationMode = Properties->TessellationMode; CustomCurveTension = FMath::Clamp(Properties->CurveTension, 0.f, 0.9999f); @@ -366,57 +354,98 @@ int FNiagaraRendererRibbons::GetDynamicDataSize()const } void CalculateUVScaleAndOffsets( - const FNiagaraDataSetReaderFloat& SortKeyReader, const TArray& RibbonIndices, - bool bSortKeyIsAge, int32 StartIndex, int32 EndIndex, int32 NumSegments, - float InUTilingDistance, float InUScale, float InUOffset, ENiagaraRibbonAgeOffsetMode InAgeOffsetMode, - float& OutUScale, float& OutUOffset) + const FNiagaraRibbonUVSettings& UVSettings, + const TArray& RibbonIndices, + const TArray& RibbonTangentsAndDistances, + const FNiagaraDataSetReaderFloat& SortKeyReader, + int32 StartIndex, int32 EndIndex, + int32 NumSegments, float TotalLength, + float& OutUScale, float& OutUOffset, float& OutUDistributionScaler) { - if (EndIndex - StartIndex > 0 && bSortKeyIsAge && InUTilingDistance == 0) + float NormalizedLeadingSegmentOffset; + if (UVSettings.LeadingEdgeMode == ENiagaraRibbonUVEdgeMode::SmoothTransition) { - float AgeUScale; - float AgeUOffset; - if (InAgeOffsetMode == ENiagaraRibbonAgeOffsetMode::Scale) - { - // In scale mode we scale and offset the UVs so that no part of the texture is clipped. In order to prevent - // clipping at the ends we'll have to move the UVs in up to the size of a single segment of the ribbon since - // that's the distance we'll need to to smoothly interpolate when a new segment is added, or when an old segment - // is removed. We calculate the end offset when the end of the ribbon is within a single time step of 0 or 1 - // which is then normalized to the range of a single segment. We can then calculate how many segments we actually - // have to draw the scaled ribbon, and can offset the start by the correctly scaled offset. - float FirstAge = SortKeyReader[RibbonIndices[StartIndex]]; - float SecondAge = SortKeyReader[RibbonIndices[StartIndex + 1]]; - float SecondToLastAge = SortKeyReader[RibbonIndices[EndIndex - 1]]; - float LastAge = SortKeyReader[RibbonIndices[EndIndex]]; + float FirstAge = SortKeyReader[RibbonIndices[StartIndex]]; + float SecondAge = SortKeyReader[RibbonIndices[StartIndex + 1]]; - float StartTimeStep = SecondAge - FirstAge; - float StartTimeOffset = FirstAge < StartTimeStep ? StartTimeStep - FirstAge : 0; - float StartSegmentOffset = StartTimeOffset / StartTimeStep; + float StartTimeStep = SecondAge - FirstAge; + float StartTimeOffset = FirstAge < StartTimeStep ? StartTimeStep - FirstAge : 0; - float EndTimeStep = LastAge - SecondToLastAge; - float EndTimeOffset = 1 - LastAge < EndTimeStep ? EndTimeStep - (1 - LastAge) : 0; - float EndSegmentOffset = EndTimeOffset / EndTimeStep; - - float AvailableSegments = NumSegments - (StartSegmentOffset + EndSegmentOffset); - AgeUScale = NumSegments / AvailableSegments; - AgeUOffset = -((StartSegmentOffset / NumSegments) * AgeUScale); - } - else - { - float FirstAge = SortKeyReader[RibbonIndices[StartIndex]]; - float LastAge = SortKeyReader[RibbonIndices[EndIndex]]; - - AgeUScale = LastAge - FirstAge; - AgeUOffset = FirstAge; - } - - OutUScale = AgeUScale * InUScale; - OutUOffset = (AgeUOffset * InUScale) + InUOffset; + NormalizedLeadingSegmentOffset = StartTimeOffset / StartTimeStep; + } + else if (UVSettings.LeadingEdgeMode == ENiagaraRibbonUVEdgeMode::Locked) + { + NormalizedLeadingSegmentOffset = 0; } else { - OutUScale = InUScale; - OutUOffset = InUOffset; + NormalizedLeadingSegmentOffset = 0; + checkf(false, TEXT("Unsupported ribbon uv edge mode")); } + + float NormalizedTrailingSegmentOffset; + if (UVSettings.TrailingEdgeMode == ENiagaraRibbonUVEdgeMode::SmoothTransition) + { + float SecondToLastAge = SortKeyReader[RibbonIndices[EndIndex - 1]]; + float LastAge = SortKeyReader[RibbonIndices[EndIndex]]; + + float EndTimeStep = LastAge - SecondToLastAge; + float EndTimeOffset = 1 - LastAge < EndTimeStep ? EndTimeStep - (1 - LastAge) : 0; + + NormalizedTrailingSegmentOffset = EndTimeOffset / EndTimeStep; + } + else if (UVSettings.TrailingEdgeMode == ENiagaraRibbonUVEdgeMode::Locked) + { + NormalizedTrailingSegmentOffset = 0; + } + else + { + NormalizedTrailingSegmentOffset = 0; + checkf(false, TEXT("Unsupported ribbon uv edge mode")); + } + + float CalculatedUOffset; + float CalculatedUScale; + if (UVSettings.DistributionMode == ENiagaraRibbonUVDistributionMode::ScaledUniformly) + { + float AvailableSegments = NumSegments - (NormalizedLeadingSegmentOffset + NormalizedTrailingSegmentOffset); + CalculatedUScale = NumSegments / AvailableSegments; + CalculatedUOffset = -((NormalizedLeadingSegmentOffset / NumSegments) * CalculatedUScale); + OutUDistributionScaler = 1.0f / NumSegments; + } + else if (UVSettings.DistributionMode == ENiagaraRibbonUVDistributionMode::ScaledUsingRibbonSegmentLength) + { + float SecondDistance = RibbonTangentsAndDistances[StartIndex + 1].W; + float LeadingDistanceOffset = SecondDistance * NormalizedLeadingSegmentOffset; + + float SecondToLastDistance = RibbonTangentsAndDistances[EndIndex - 1].W; + float LastDistance = RibbonTangentsAndDistances[EndIndex].W; + float TrailingDistanceOffset = (LastDistance - SecondToLastDistance) * NormalizedTrailingSegmentOffset; + + float AvailableLength = TotalLength - (LeadingDistanceOffset + TrailingDistanceOffset); + + CalculatedUScale = TotalLength / AvailableLength; + CalculatedUOffset = -((LeadingDistanceOffset / TotalLength) * CalculatedUScale); + OutUDistributionScaler = 1.0f / TotalLength; + } + else if (UVSettings.DistributionMode == ENiagaraRibbonUVDistributionMode::TiledOverRibbonLength) + { + float SecondDistance = RibbonTangentsAndDistances[StartIndex + 1].W; + float LeadingDistanceOffset = SecondDistance * NormalizedLeadingSegmentOffset; + + CalculatedUScale = TotalLength / UVSettings.TilingLength; + CalculatedUOffset = -(LeadingDistanceOffset / UVSettings.TilingLength); + OutUDistributionScaler = 1.0f / TotalLength; + } + else + { + CalculatedUScale = 1; + CalculatedUOffset = 0; + checkf(false, TEXT("Unsupported ribbon distribution mode")); + } + + OutUScale = CalculatedUScale * UVSettings.Scale.X; + OutUOffset = (CalculatedUOffset * UVSettings.Scale.X) + UVSettings.Offset.X; } FNiagaraDynamicDataBase* FNiagaraRendererRibbons::GenerateDynamicData(const FNiagaraSceneProxy* Proxy, const UNiagaraRendererProperties* InProperties, const FNiagaraEmitterInstance* Emitter)const @@ -450,6 +479,9 @@ FNiagaraDynamicDataBase* FNiagaraRendererRibbons::GenerateDynamicData(const FNia const auto MaterialParam2Data = Properties->MaterialParam2DataSetAccessor.GetReader(Data); const auto MaterialParam3Data = Properties->MaterialParam3DataSetAccessor.GetReader(Data); + bool U0OverrideIsBound = Properties->U0OverrideIsBound; + bool U1OverrideIsBound = Properties->U1OverrideIsBound; + const auto RibbonIdData = Properties->RibbonIdDataSetAccessor.GetReader(Data); const auto RibbonFullIDData = Properties->RibbonFullIDDataSetAccessor.GetReader(Data); @@ -614,19 +646,50 @@ FNiagaraDynamicDataBase* FNiagaraRendererRibbons::GenerateDynamicData(const FNia { SegmentData.Add(SegmentIndex); } + + float U0Offset; + float U0Scale; + float U0DistributionScaler; + if(UV0Settings.bEnablePerParticleUOverride && U0OverrideIsBound) + { + U0Offset = 0; + U0Scale = 1.0f; + U0DistributionScaler = 1; + } + else + { + CalculateUVScaleAndOffsets( + UV0Settings, DynamicData->SortedIndices, DynamicData->TangentAndDistances, + SortKeyReader, + StartIndex, DynamicData->SortedIndices.Num() - 1, + NumSegments, TotalDistance, + U0Scale, U0Offset, U0DistributionScaler); + } - float U0Offset, U0Scale, U1Offset, U1Scale; + float U1Offset; + float U1Scale; + float U1DistributionScaler; + if (UV1Settings.bEnablePerParticleUOverride && U1OverrideIsBound) + { + U1Offset = 0; + U1Scale = 1.0f; + U1DistributionScaler = 1; + } + else + { + CalculateUVScaleAndOffsets( + UV1Settings, DynamicData->SortedIndices, DynamicData->TangentAndDistances, + SortKeyReader, + StartIndex, DynamicData->SortedIndices.Num() - 1, + NumSegments, TotalDistance, + U1Scale, U1Offset, U1DistributionScaler); + } - CalculateUVScaleAndOffsets(SortKeyReader, DynamicData->SortedIndices, bSortKeyIsAge, StartIndex, DynamicData->SortedIndices.Num() - 1, NumSegments, - Properties->UV0TilingDistance, Properties->UV0Scale.X, Properties->UV0Offset.X, Properties->UV0AgeOffsetMode, U0Scale, U0Offset); - CalculateUVScaleAndOffsets(SortKeyReader, DynamicData->SortedIndices, bSortKeyIsAge, StartIndex, DynamicData->SortedIndices.Num() - 1, NumSegments, - Properties->UV1TilingDistance, Properties->UV1Scale.X, Properties->UV1Offset.X, Properties->UV1AgeOffsetMode, U1Scale, U1Offset); - - DynamicData->PackPerRibbonData(U0Scale, U0Offset, U1Scale, U1Offset, NumSegments, StartIndex); + DynamicData->PackPerRibbonData(U0Scale, U0Offset, U0DistributionScaler, U1Scale, U1Offset, U1DistributionScaler, StartIndex); } else { - DynamicData->PackPerRibbonData(0, 0, 0, 0, 0, 0); + DynamicData->PackPerRibbonData(0, 0, 0, 0, 0, 0, 0); } }; @@ -748,7 +811,7 @@ void FNiagaraRendererRibbons::AddDynamicParam(TArrayCheckMaterialUsage_Concurrent(MATUSAGE_NiagaraRibbons); } @@ -1006,18 +1069,19 @@ void FNiagaraRendererRibbons::CreatePerViewResources( PerViewUniformParameters.MaterialParam1DataOffset = VFVariables[ENiagaraRibbonVFLayout::MaterialParam1].GetGPUOffset(); PerViewUniformParameters.MaterialParam2DataOffset = VFVariables[ENiagaraRibbonVFLayout::MaterialParam2].GetGPUOffset(); PerViewUniformParameters.MaterialParam3DataOffset = VFVariables[ENiagaraRibbonVFLayout::MaterialParam3].GetGPUOffset(); + PerViewUniformParameters.U0OverrideDataOffset = UV0Settings.bEnablePerParticleUOverride ? VFVariables[ENiagaraRibbonVFLayout::U0Override].GetGPUOffset() : -1; + PerViewUniformParameters.V0RangeOverrideDataOffset = UV0Settings.bEnablePerParticleVRangeOverride ? VFVariables[ENiagaraRibbonVFLayout::V0RangeOverride].GetGPUOffset() : -1; + PerViewUniformParameters.U1OverrideDataOffset = UV1Settings.bEnablePerParticleUOverride ? VFVariables[ENiagaraRibbonVFLayout::U1Override].GetGPUOffset() : -1; + PerViewUniformParameters.V1RangeOverrideDataOffset = UV1Settings.bEnablePerParticleVRangeOverride ? VFVariables[ENiagaraRibbonVFLayout::V1RangeOverride].GetGPUOffset() : -1; PerViewUniformParameters.MaterialParamValidMask = MaterialParamValidMask; bool bShouldDoFacing = FacingMode == ENiagaraRibbonFacingMode::Custom || FacingMode == ENiagaraRibbonFacingMode::CustomSideVector; PerViewUniformParameters.FacingDataOffset = bShouldDoFacing ? VFVariables[ENiagaraRibbonVFLayout::Facing].GetGPUOffset() : -1; - PerViewUniformParameters.OneOverUV0TilingDistance = UV0TilingDistance ? 1.f / (UV0TilingDistance) : 0.f; - PerViewUniformParameters.OneOverUV1TilingDistance = UV1TilingDistance ? 1.f / (UV1TilingDistance) : 0.f; - PerViewUniformParameters.PackedVData = FVector4(UV0Scale.Y, UV0Offset.Y, UV1Scale.Y, UV1Offset.Y); - PerViewUniformParameters.OneOverUV0TilingDistance = UV0TilingDistance ? 1.f / (UV0TilingDistance) : 0.f; - PerViewUniformParameters.OneOverUV1TilingDistance = UV1TilingDistance ? 1.f / (UV1TilingDistance) : 0.f; - PerViewUniformParameters.PackedVData = FVector4(UV0Scale.Y, UV0Offset.Y, UV1Scale.Y, UV1Offset.Y); + PerViewUniformParameters.U0DistributionMode = (int32)UV0Settings.DistributionMode; + PerViewUniformParameters.U1DistributionMode = (int32)UV1Settings.DistributionMode; + PerViewUniformParameters.PackedVData = FVector4(UV0Settings.Scale.Y, UV0Settings.Offset.Y, UV1Settings.Scale.Y, UV1Settings.Offset.Y); OutUniformBuffer = FNiagaraRibbonUniformBufferRef::CreateUniformBufferImmediate(PerViewUniformParameters, UniformBuffer_SingleFrame); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererSprites.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererSprites.cpp index 92486c88bca7..39c8948183d7 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererSprites.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererSprites.cpp @@ -11,6 +11,7 @@ #include "RayTracingDefinitions.h" #include "RayTracingDynamicGeometryCollection.h" #include "RayTracingInstance.h" +#include "Materials/MaterialInstanceDynamic.h" DECLARE_CYCLE_STAT(TEXT("Generate Sprite Dynamic Data [GT]"), STAT_NiagaraGenSpriteDynamicData, STATGROUP_Niagara); DECLARE_CYCLE_STAT(TEXT("Render Sprites [RT]"), STAT_NiagaraRenderSprites, STATGROUP_Niagara); @@ -54,6 +55,9 @@ struct FNiagaraDynamicDataSprites : public FNiagaraDynamicDataBase } FMaterialRenderProxy* Material = nullptr; + TArray DataInterfacesBound; + TArray ObjectsBound; + TArray ParameterDataBound; }; /* Mesh collector classes */ @@ -82,6 +86,7 @@ FNiagaraRendererSprites::FNiagaraRendererSprites(ERHIFeatureLevel::Type FeatureL , bSubImageBlend(false) , bRemoveHMDRollInVR(false) , bSortOnlyWhenTranslucent(true) + , bGpuLowLatencyTranslucency(true) , MinFacingCameraBlendDistance(0.0f) , MaxFacingCameraBlendDistance(0.0f) , MaterialParamValidMask(0) @@ -89,7 +94,7 @@ FNiagaraRendererSprites::FNiagaraRendererSprites(ERHIFeatureLevel::Type FeatureL check(InProps && Emitter); const UNiagaraSpriteRendererProperties* Properties = CastChecked(InProps); - + SourceMode = Properties->SourceMode; Alignment = Properties->Alignment; FacingMode = Properties->FacingMode; PivotInUVSpace = Properties->PivotInUVSpace; @@ -98,6 +103,7 @@ FNiagaraRendererSprites::FNiagaraRendererSprites(ERHIFeatureLevel::Type FeatureL bSubImageBlend = Properties->bSubImageBlend; bRemoveHMDRollInVR = Properties->bRemoveHMDRollInVR; bSortOnlyWhenTranslucent = Properties->bSortOnlyWhenTranslucent; + bGpuLowLatencyTranslucency = Properties->bGpuLowLatencyTranslucency && (SortMode == ENiagaraSortMode::None); MinFacingCameraBlendDistance = Properties->MinFacingCameraBlendDistance; MaxFacingCameraBlendDistance = Properties->MaxFacingCameraBlendDistance; @@ -108,6 +114,31 @@ FNiagaraRendererSprites::FNiagaraRendererSprites(ERHIFeatureLevel::Type FeatureL RendererLayoutWithCustomSort = &Properties->RendererLayoutWithCustomSort; RendererLayoutWithoutCustomSort = &Properties->RendererLayoutWithoutCustomSort; + + bSetAnyBoundVars = false; + if (Emitter->GetRendererBoundVariables().IsEmpty() == false) + { + const TArray< const FNiagaraVariableAttributeBinding*>& VFBindings = Properties->GetAttributeBindings(); + check(VFBindings.Num() == ENiagaraSpriteVFLayout::Type::Num); + + for (int32 i = 0; i < ENiagaraSpriteVFLayout::Type::Num; i++) + { + VFBoundOffsetsInParamStore[i] = INDEX_NONE; + if (VFBindings[i] && VFBindings[i]->CanBindToHostParameterMap()) + { + VFBoundOffsetsInParamStore[i] = Emitter->GetRendererBoundVariables().IndexOf(VFBindings[i]->GetParamMapBindableVariable()); + if (VFBoundOffsetsInParamStore[i] != INDEX_NONE) + bSetAnyBoundVars = true; + } + } + } + else + { + for (int32 i = 0; i < ENiagaraSpriteVFLayout::Type::Num; i++) + { + VFBoundOffsetsInParamStore[i] = INDEX_NONE; + } + } } FNiagaraRendererSprites::~FNiagaraRendererSprites() @@ -157,7 +188,7 @@ FNiagaraRendererSprites::FCPUSimParticleDataAllocation FNiagaraRendererSprites:: FCPUSimParticleDataAllocation CPUSimParticleDataAllocation { DynamicReadBuffer }; - if (SimTarget == ENiagaraSimTarget::CPUSim) + if (SimTarget == ENiagaraSimTarget::CPUSim && SourceMode == ENiagaraRendererSourceDataMode::Particles) { SCOPE_CYCLE_COUNTER(STAT_NiagaraRenderSpritesCPUSimCopy); @@ -165,6 +196,7 @@ FNiagaraRendererSprites::FCPUSimParticleDataAllocation FNiagaraRendererSprites:: { SCOPE_CYCLE_COUNTER(STAT_NiagaraRenderSpritesCPUSimMemCopy); CPUSimParticleDataAllocation.ParticleData = TransferDataToGPU(DynamicReadBuffer, RendererLayout, SourceParticleData); + } else { @@ -181,7 +213,7 @@ FNiagaraRendererSprites::FCPUSimParticleDataAllocation FNiagaraRendererSprites:: return CPUSimParticleDataAllocation; } -FNiagaraSpriteUniformBufferRef FNiagaraRendererSprites::CreatePerViewUniformBuffer(const FSceneView* View, const FSceneViewFamily& ViewFamily, const FNiagaraSceneProxy *SceneProxy, const FNiagaraRendererLayout* RendererLayout) const +FNiagaraSpriteUniformBufferRef FNiagaraRendererSprites::CreatePerViewUniformBuffer(const FSceneView* View, const FSceneViewFamily& ViewFamily, const FNiagaraSceneProxy *SceneProxy, const FNiagaraRendererLayout* RendererLayout, const FNiagaraDynamicDataSprites* DynamicDataSprites) const { FNiagaraSpriteUniformParameters PerViewUniformParameters; FMemory::Memzero(&PerViewUniformParameters,sizeof(PerViewUniformParameters)); // Clear unset bytes @@ -200,60 +232,170 @@ FNiagaraSpriteUniformBufferRef FNiagaraRendererSprites::CreatePerViewUniformBuff PerViewUniformParameters.RemoveHMDRoll = bRemoveHMDRollInVR; PerViewUniformParameters.SubImageSize = FVector4(SubImageSize.X, SubImageSize.Y, 1.0f / SubImageSize.X, 1.0f / SubImageSize.Y); + + PerViewUniformParameters.DefaultPos = bLocalSpace ? FVector4(0.0f, 0.0f, 0.0f, 1.0f) : FVector4(SceneProxy->GetLocalToWorld().GetOrigin()); + PerViewUniformParameters.DefaultSize = FVector2D(50.f, 50.0f); + PerViewUniformParameters.DefaultUVScale = FVector2D(1.0f, 1.0f); + PerViewUniformParameters.DefaultVelocity = FVector(0.f, 0.0f, 0.0f); + PerViewUniformParameters.DefaultRotation = 0.0f; + PerViewUniformParameters.DefaultColor = FVector4(1.0f, 1.0f, 1.0f, 1.0f); + PerViewUniformParameters.DefaultMatRandom = 0.0f; + PerViewUniformParameters.DefaultCamOffset = 0.0f; + PerViewUniformParameters.DefaultNormAge = 0.0f; + PerViewUniformParameters.DefaultSubImage = 0.0f; + PerViewUniformParameters.DefaultFacing = FVector4(1.0f, 0.0f, 0.0f, 0.0f); + PerViewUniformParameters.DefaultAlignment = FVector4(1.0f, 0.0f, 0.0f, 0.0f); + PerViewUniformParameters.DefaultDynamicMaterialParameter0 = FVector4(1.0f, 1.0f, 1.0f, 1.0f); + PerViewUniformParameters.DefaultDynamicMaterialParameter1 = FVector4(1.0f, 1.0f, 1.0f, 1.0f); + PerViewUniformParameters.DefaultDynamicMaterialParameter2 = FVector4(1.0f, 1.0f, 1.0f, 1.0f); + PerViewUniformParameters.DefaultDynamicMaterialParameter3 = FVector4(1.0f, 1.0f, 1.0f, 1.0f); + TConstArrayView VFVariables = RendererLayout->GetVFVariables_RenderThread(); - PerViewUniformParameters.PositionDataOffset = VFVariables[ENiagaraSpriteVFLayout::Position].GetGPUOffset(); - PerViewUniformParameters.VelocityDataOffset = VFVariables[ENiagaraSpriteVFLayout::Velocity].GetGPUOffset(); - PerViewUniformParameters.RotationDataOffset = VFVariables[ENiagaraSpriteVFLayout::Rotation].GetGPUOffset(); - PerViewUniformParameters.SizeDataOffset = VFVariables[ENiagaraSpriteVFLayout::Size].GetGPUOffset(); - PerViewUniformParameters.ColorDataOffset = VFVariables[ENiagaraSpriteVFLayout::Color].GetGPUOffset(); - PerViewUniformParameters.MaterialParamDataOffset = VFVariables[ENiagaraSpriteVFLayout::MaterialParam0].GetGPUOffset(); - PerViewUniformParameters.MaterialParam1DataOffset = VFVariables[ENiagaraSpriteVFLayout::MaterialParam1].GetGPUOffset(); - PerViewUniformParameters.MaterialParam2DataOffset = VFVariables[ENiagaraSpriteVFLayout::MaterialParam2].GetGPUOffset(); - PerViewUniformParameters.MaterialParam3DataOffset = VFVariables[ENiagaraSpriteVFLayout::MaterialParam3].GetGPUOffset(); - PerViewUniformParameters.SubimageDataOffset = VFVariables[ENiagaraSpriteVFLayout::SubImage].GetGPUOffset(); - PerViewUniformParameters.FacingDataOffset = VFVariables[ENiagaraSpriteVFLayout::Facing].GetGPUOffset(); - PerViewUniformParameters.AlignmentDataOffset = VFVariables[ENiagaraSpriteVFLayout::Alignment].GetGPUOffset(); - PerViewUniformParameters.CameraOffsetDataOffset = VFVariables[ENiagaraSpriteVFLayout::CameraOffset].GetGPUOffset(); - PerViewUniformParameters.UVScaleDataOffset = VFVariables[ENiagaraSpriteVFLayout::UVScale].GetGPUOffset(); - PerViewUniformParameters.NormalizedAgeDataOffset = VFVariables[ENiagaraSpriteVFLayout::NormalizedAge].GetGPUOffset(); - PerViewUniformParameters.MaterialRandomDataOffset = VFVariables[ENiagaraSpriteVFLayout::MaterialRandom].GetGPUOffset(); + if (SourceMode == ENiagaraRendererSourceDataMode::Particles) + { + PerViewUniformParameters.PositionDataOffset = VFVariables[ENiagaraSpriteVFLayout::Position].GetGPUOffset(); + PerViewUniformParameters.VelocityDataOffset = VFVariables[ENiagaraSpriteVFLayout::Velocity].GetGPUOffset(); + PerViewUniformParameters.RotationDataOffset = VFVariables[ENiagaraSpriteVFLayout::Rotation].GetGPUOffset(); + PerViewUniformParameters.SizeDataOffset = VFVariables[ENiagaraSpriteVFLayout::Size].GetGPUOffset(); + PerViewUniformParameters.ColorDataOffset = VFVariables[ENiagaraSpriteVFLayout::Color].GetGPUOffset(); + PerViewUniformParameters.MaterialParamDataOffset = VFVariables[ENiagaraSpriteVFLayout::MaterialParam0].GetGPUOffset(); + PerViewUniformParameters.MaterialParam1DataOffset = VFVariables[ENiagaraSpriteVFLayout::MaterialParam1].GetGPUOffset(); + PerViewUniformParameters.MaterialParam2DataOffset = VFVariables[ENiagaraSpriteVFLayout::MaterialParam2].GetGPUOffset(); + PerViewUniformParameters.MaterialParam3DataOffset = VFVariables[ENiagaraSpriteVFLayout::MaterialParam3].GetGPUOffset(); + PerViewUniformParameters.SubimageDataOffset = VFVariables[ENiagaraSpriteVFLayout::SubImage].GetGPUOffset(); + PerViewUniformParameters.FacingDataOffset = VFVariables[ENiagaraSpriteVFLayout::Facing].GetGPUOffset(); + PerViewUniformParameters.AlignmentDataOffset = VFVariables[ENiagaraSpriteVFLayout::Alignment].GetGPUOffset(); + PerViewUniformParameters.CameraOffsetDataOffset = VFVariables[ENiagaraSpriteVFLayout::CameraOffset].GetGPUOffset(); + PerViewUniformParameters.UVScaleDataOffset = VFVariables[ENiagaraSpriteVFLayout::UVScale].GetGPUOffset(); + PerViewUniformParameters.NormalizedAgeDataOffset = VFVariables[ENiagaraSpriteVFLayout::NormalizedAge].GetGPUOffset(); + PerViewUniformParameters.MaterialRandomDataOffset = VFVariables[ENiagaraSpriteVFLayout::MaterialRandom].GetGPUOffset(); + } + else if (SourceMode == ENiagaraRendererSourceDataMode::Emitter) // Clear all these out because we will be using the defaults to specify them + { + PerViewUniformParameters.PositionDataOffset = INDEX_NONE; + PerViewUniformParameters.VelocityDataOffset = INDEX_NONE; + PerViewUniformParameters.RotationDataOffset = INDEX_NONE; + PerViewUniformParameters.SizeDataOffset = INDEX_NONE; + PerViewUniformParameters.ColorDataOffset = INDEX_NONE; + PerViewUniformParameters.MaterialParamDataOffset = INDEX_NONE; + PerViewUniformParameters.MaterialParam1DataOffset = INDEX_NONE; + PerViewUniformParameters.MaterialParam2DataOffset = INDEX_NONE; + PerViewUniformParameters.MaterialParam3DataOffset = INDEX_NONE; + PerViewUniformParameters.SubimageDataOffset = INDEX_NONE; + PerViewUniformParameters.FacingDataOffset = INDEX_NONE; + PerViewUniformParameters.AlignmentDataOffset = INDEX_NONE; + PerViewUniformParameters.CameraOffsetDataOffset = INDEX_NONE; + PerViewUniformParameters.UVScaleDataOffset = INDEX_NONE; + PerViewUniformParameters.NormalizedAgeDataOffset = INDEX_NONE; + PerViewUniformParameters.MaterialRandomDataOffset = INDEX_NONE; + } + else + { + // Unsupported source data mode detected + check(SourceMode <= ENiagaraRendererSourceDataMode::Emitter); + } + + if (bSetAnyBoundVars && DynamicDataSprites) + { + for (int32 i = 0; i < ENiagaraSpriteVFLayout::Type::Num; i++) + { + if (VFBoundOffsetsInParamStore[i] != INDEX_NONE && DynamicDataSprites->ParameterDataBound.IsValidIndex(VFBoundOffsetsInParamStore[i])) + { + switch (i) + { + case ENiagaraSpriteVFLayout::Type::Position: + memcpy(&PerViewUniformParameters.DefaultPos, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(FVector)); + break; + case ENiagaraSpriteVFLayout::Type::Color: + memcpy(&PerViewUniformParameters.DefaultColor, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(FLinearColor)); + break; + case ENiagaraSpriteVFLayout::Type::Velocity: + memcpy(&PerViewUniformParameters.DefaultVelocity, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(FVector)); + break; + case ENiagaraSpriteVFLayout::Type::Rotation: + memcpy(&PerViewUniformParameters.DefaultRotation, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(float)); + break; + case ENiagaraSpriteVFLayout::Type::Size: + memcpy(&PerViewUniformParameters.DefaultSize, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(FVector2D)); + break; + case ENiagaraSpriteVFLayout::Type::Facing: + memcpy(&PerViewUniformParameters.DefaultFacing, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(FVector)); + break; + case ENiagaraSpriteVFLayout::Type::Alignment: + memcpy(&PerViewUniformParameters.DefaultAlignment, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(FVector)); + break; + case ENiagaraSpriteVFLayout::Type::SubImage: + memcpy(&PerViewUniformParameters.DefaultSubImage, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(float)); + break; + case ENiagaraSpriteVFLayout::Type::MaterialParam0: + memcpy(&PerViewUniformParameters.DefaultDynamicMaterialParameter0, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(FVector4)); + break; + case ENiagaraSpriteVFLayout::Type::MaterialParam1: + memcpy(&PerViewUniformParameters.DefaultDynamicMaterialParameter1, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(FVector4)); + break; + case ENiagaraSpriteVFLayout::Type::MaterialParam2: + memcpy(&PerViewUniformParameters.DefaultDynamicMaterialParameter2, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(FVector4)); + break; + case ENiagaraSpriteVFLayout::Type::MaterialParam3: + memcpy(&PerViewUniformParameters.DefaultDynamicMaterialParameter3, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(FVector4)); + break; + case ENiagaraSpriteVFLayout::Type::CameraOffset: + memcpy(&PerViewUniformParameters.DefaultCamOffset, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(float)); + break; + case ENiagaraSpriteVFLayout::Type::UVScale: + memcpy(&PerViewUniformParameters.DefaultUVScale, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(FVector2D)); + break; + case ENiagaraSpriteVFLayout::Type::MaterialRandom: + memcpy(&PerViewUniformParameters.DefaultMatRandom, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(float)); + break; + case ENiagaraSpriteVFLayout::Type::CustomSorting: + // unsupport for now... + break; + case ENiagaraSpriteVFLayout::Type::NormalizedAge: + memcpy(&PerViewUniformParameters.DefaultNormAge, DynamicDataSprites->ParameterDataBound.GetData() + VFBoundOffsetsInParamStore[i], sizeof(float)); + break; + } + } + } + } PerViewUniformParameters.SubImageBlendMode = bSubImageBlend; PerViewUniformParameters.MaterialParamValidMask = MaterialParamValidMask; - PerViewUniformParameters.DefaultPos = bLocalSpace ? FVector4(0.0f, 0.0f, 0.0f, 1.0f) : FVector4(SceneProxy->GetLocalToWorld().GetOrigin()); - ENiagaraSpriteFacingMode ActualFacingMode = FacingMode; - ENiagaraSpriteAlignment ActualAlignmentMode = Alignment; - - const int32 FacingOffset = VFVariables[ENiagaraSpriteVFLayout::Facing].GetGPUOffset(); - if (FacingOffset == -1 && FacingMode == ENiagaraSpriteFacingMode::CustomFacingVector) { - ActualFacingMode = ENiagaraSpriteFacingMode::FaceCamera; - } + ENiagaraSpriteFacingMode ActualFacingMode = FacingMode; + ENiagaraSpriteAlignment ActualAlignmentMode = Alignment; - const int32 AlignmentOffset = VFVariables[ENiagaraSpriteVFLayout::Alignment].GetGPUOffset(); - if (AlignmentOffset == -1 && ActualAlignmentMode == ENiagaraSpriteAlignment::CustomAlignment) - { - ActualAlignmentMode = ENiagaraSpriteAlignment::Unaligned; - } + const int32 FacingOffset = SourceMode == ENiagaraRendererSourceDataMode::Particles ? PerViewUniformParameters.FacingDataOffset : VFBoundOffsetsInParamStore[ENiagaraSpriteVFLayout::Facing]; + if (FacingOffset == -1 && FacingMode == ENiagaraSpriteFacingMode::CustomFacingVector) + { + ActualFacingMode = ENiagaraSpriteFacingMode::FaceCamera; + } - if (ActualFacingMode == ENiagaraSpriteFacingMode::FaceCameraDistanceBlend) - { - float DistanceBlendMinSq = MinFacingCameraBlendDistance * MinFacingCameraBlendDistance; - float DistanceBlendMaxSq = MaxFacingCameraBlendDistance * MaxFacingCameraBlendDistance; - float InvBlendRange = 1.0f / FMath::Max(DistanceBlendMaxSq - DistanceBlendMinSq, 1.0f); - float BlendScaledMinDistance = DistanceBlendMinSq * InvBlendRange; + const int32 AlignmentOffset = SourceMode == ENiagaraRendererSourceDataMode::Particles ? PerViewUniformParameters.AlignmentDataOffset : VFBoundOffsetsInParamStore[ENiagaraSpriteVFLayout::Alignment]; + if (AlignmentOffset == -1 && ActualAlignmentMode == ENiagaraSpriteAlignment::CustomAlignment) + { + ActualAlignmentMode = ENiagaraSpriteAlignment::Unaligned; + } - PerViewUniformParameters.CameraFacingBlend.X = 1.0f; - PerViewUniformParameters.CameraFacingBlend.Y = InvBlendRange; - PerViewUniformParameters.CameraFacingBlend.Z = BlendScaledMinDistance; - } + if (ActualFacingMode == ENiagaraSpriteFacingMode::FaceCameraDistanceBlend) + { + float DistanceBlendMinSq = MinFacingCameraBlendDistance * MinFacingCameraBlendDistance; + float DistanceBlendMaxSq = MaxFacingCameraBlendDistance * MaxFacingCameraBlendDistance; + float InvBlendRange = 1.0f / FMath::Max(DistanceBlendMaxSq - DistanceBlendMinSq, 1.0f); + float BlendScaledMinDistance = DistanceBlendMinSq * InvBlendRange; - if (ActualAlignmentMode == ENiagaraSpriteAlignment::VelocityAligned) - { - // velocity aligned - PerViewUniformParameters.RotationScale = 0.0f; - PerViewUniformParameters.TangentSelector = FVector4(0.0f, 1.0f, 0.0f, 0.0f); + PerViewUniformParameters.CameraFacingBlend.X = 1.0f; + PerViewUniformParameters.CameraFacingBlend.Y = InvBlendRange; + PerViewUniformParameters.CameraFacingBlend.Z = BlendScaledMinDistance; + } + + if (ActualAlignmentMode == ENiagaraSpriteAlignment::VelocityAligned) + { + // velocity aligned + PerViewUniformParameters.RotationScale = 0.0f; + PerViewUniformParameters.TangentSelector = FVector4(0.0f, 1.0f, 0.0f, 0.0f); + } } return FNiagaraSpriteUniformBufferRef::CreateUniformBufferImmediate(PerViewUniformParameters, UniformBuffer_SingleFrame); @@ -288,10 +430,8 @@ void FNiagaraRendererSprites::SetVertexFactoryParticleData( } } - // Set common data on vertex factory - DynamicDataSprites->SetVertexFactoryData(OutVertexFactory); - //Sort particles if needed. + if (SourceMode == ENiagaraRendererSourceDataMode::Particles) { SCOPE_CYCLE_COUNTER(STAT_NiagaraRenderSpritesSorting) @@ -300,12 +440,13 @@ void FNiagaraRendererSprites::SetVertexFactoryParticleData( EBlendMode BlendMode = MaterialRenderProxy->GetMaterial(FeatureLevel)->GetBlendMode(); OutVertexFactory.SetSortedIndices(nullptr, 0xFFFFFFFF); - FNiagaraDataBuffer* SourceParticleData = DynamicDataSprites->GetParticleDataToRender(IsTranslucentBlendMode(BlendMode)); + const bool bHasTranslucentMaterials = IsTranslucentBlendMode(BlendMode); + FNiagaraDataBuffer* SourceParticleData = DynamicDataSprites->GetParticleDataToRender(bHasTranslucentMaterials && bGpuLowLatencyTranslucency); check(SourceParticleData);//Can be null but should be checked before here. const int32 NumInstances = SourceParticleData->GetNumInstances(); + FNiagaraGPUSortInfo SortInfo; - const bool bHasTranslucentMaterials = IsTranslucentBlendMode(BlendMode); const bool bShouldSort = SortMode != ENiagaraSortMode::None && (bHasTranslucentMaterials || !bSortOnlyWhenTranslucent); const bool bCustomSorting = SortMode == ENiagaraSortMode::CustomAscending || SortMode == ENiagaraSortMode::CustomDecending; TConstArrayView VFVariables = RendererLayout->GetVFVariables_RenderThread(); @@ -400,6 +541,12 @@ void FNiagaraRendererSprites::SetVertexFactoryParticleData( VFLooseParams.NiagaraParticleDataHalf = HalfSRV; } } + else if (SourceMode == ENiagaraRendererSourceDataMode::Emitter) + { + VFLooseParams.NiagaraFloatDataStride = 0; + VFLooseParams.NiagaraParticleDataFloat = (FRHIShaderResourceView*)FNiagaraRenderer::GetDummyFloatBuffer(); + VFLooseParams.NiagaraParticleDataHalf = (FRHIShaderResourceView*)FNiagaraRenderer::GetDummyHalfBuffer(); + } } void FNiagaraRendererSprites::CreateMeshBatchForView( @@ -416,7 +563,7 @@ void FNiagaraRendererSprites::CreateMeshBatchForView( { FNiagaraDataBuffer* SourceParticleData = DynamicDataSprites->GetParticleDataToRender(); check(SourceParticleData);//Can be null but should be checked before here. - int32 NumInstances = SourceParticleData->GetNumInstances(); + int32 NumInstances = SourceMode == ENiagaraRendererSourceDataMode::Particles ? SourceParticleData->GetNumInstances() : 1; const bool bIsWireframe = ViewFamily.EngineShowFlags.Wireframe; @@ -427,20 +574,22 @@ void FNiagaraRendererSprites::CreateMeshBatchForView( ENiagaraSpriteAlignment ActualAlignmentMode = Alignment; TConstArrayView VFVariables = RendererLayout->GetVFVariables_RenderThread(); - const int32 FacingOffset = VFVariables[ENiagaraSpriteVFLayout::Facing].GetGPUOffset(); - if (FacingOffset == -1 && FacingMode == ENiagaraSpriteFacingMode::CustomFacingVector) { - ActualFacingMode = ENiagaraSpriteFacingMode::FaceCamera; - } + const int32 FacingOffset = SourceMode == ENiagaraRendererSourceDataMode::Particles ? VFVariables[ENiagaraSpriteVFLayout::Facing].GetGPUOffset() : VFBoundOffsetsInParamStore[ENiagaraSpriteVFLayout::Facing]; + if (FacingOffset == -1 && FacingMode == ENiagaraSpriteFacingMode::CustomFacingVector ) + { + ActualFacingMode = ENiagaraSpriteFacingMode::FaceCamera; + } - const int32 AlignmentOffset = VFVariables[ENiagaraSpriteVFLayout::Alignment].GetGPUOffset(); - if (AlignmentOffset == -1 && ActualAlignmentMode == ENiagaraSpriteAlignment::CustomAlignment) - { - ActualAlignmentMode = ENiagaraSpriteAlignment::Unaligned; - } + const int32 AlignmentOffset = SourceMode == ENiagaraRendererSourceDataMode::Particles ? VFVariables[ENiagaraSpriteVFLayout::Alignment].GetGPUOffset() : VFBoundOffsetsInParamStore[ENiagaraSpriteVFLayout::Alignment]; + if (AlignmentOffset == -1 && ActualAlignmentMode == ENiagaraSpriteAlignment::CustomAlignment) + { + ActualAlignmentMode = ENiagaraSpriteAlignment::Unaligned; + } - CollectorResources.VertexFactory.SetAlignmentMode((uint32)ActualAlignmentMode); - CollectorResources.VertexFactory.SetFacingMode((uint32)FacingMode); + CollectorResources.VertexFactory.SetAlignmentMode((uint32)ActualAlignmentMode); + CollectorResources.VertexFactory.SetFacingMode((uint32)FacingMode); + } CollectorResources.VertexFactory.SetParticleFactoryType(NVFT_Sprite); CollectorResources.VertexFactory.InitResource(); CollectorResources.VertexFactory.SetSpriteUniformBuffer(CollectorResources.UniformBuffer); @@ -530,7 +679,7 @@ void FNiagaraRendererSprites::GetDynamicMeshElements(const TArrayGetParticleDataToRender(); if (SourceParticleData == nullptr || - SourceParticleData->GetNumInstances() == 0 || + (SourceMode == ENiagaraRendererSourceDataMode::Particles && SourceParticleData->GetNumInstances() == 0) || GbEnableNiagaraSpriteRendering == 0 || !GSupportsResourceView // Current shader requires SRV to draw properly in all cases. ) @@ -551,7 +700,7 @@ void FNiagaraRendererSprites::GetDynamicMeshElements(const TArrayGetGPUInstanceCounterManager().AddDrawIndirect(SourceParticleData->GetGPUInstanceCountBufferOffset(), NumIndicesPerInstance, 0); } @@ -565,7 +714,7 @@ void FNiagaraRendererSprites::GetDynamicMeshElements(const TArray(); FNiagaraSpriteVFLooseParameters VFLooseParams; SetVertexFactoryParticleData(CollectorResources.VertexFactory, DynamicDataSprites, CPUSimParticleDataAllocation, View, VFLooseParams, SceneProxy, RendererLayout); - CollectorResources.UniformBuffer = CreatePerViewUniformBuffer(View, ViewFamily, SceneProxy, RendererLayout); + CollectorResources.UniformBuffer = CreatePerViewUniformBuffer(View, ViewFamily, SceneProxy, RendererLayout, DynamicDataSprites); FMeshBatch& MeshBatch = Collector.AllocateMesh(); CreateMeshBatchForView(View, ViewFamily, SceneProxy, DynamicDataSprites, IndirectArgsOffset, MeshBatch, VFLooseParams, CollectorResources, RendererLayout); @@ -596,8 +745,8 @@ void FNiagaraRendererSprites::GetDynamicRayTracingInstances(FRayTracingMaterialG FNiagaraDataBuffer* SourceParticleData = DynamicDataSprites->GetParticleDataToRender(); if (SourceParticleData == nullptr || - SourceParticleData->GetNumInstancesAllocated() == 0 || - SourceParticleData->GetNumInstances() == 0 || + (SourceMode == ENiagaraRendererSourceDataMode::Particles && SourceParticleData->GetNumInstancesAllocated() == 0) || + (SourceMode == ENiagaraRendererSourceDataMode::Particles && SourceParticleData->GetNumInstances() == 0) || GbEnableNiagaraSpriteRendering == 0 || !GSupportsResourceView // Current shader requires SRV to draw properly in all cases. ) @@ -605,8 +754,10 @@ void FNiagaraRendererSprites::GetDynamicRayTracingInstances(FRayTracingMaterialG return; } + uint32 NumInstances = SourceMode == ENiagaraRendererSourceDataMode::Particles ? SourceParticleData->GetNumInstances() : 1; + uint32 IndirectArgsOffset = INDEX_NONE; - if (SimTarget == ENiagaraSimTarget::GPUComputeSim) + if (SimTarget == ENiagaraSimTarget::GPUComputeSim && SourceMode == ENiagaraRendererSourceDataMode::Particles) { IndirectArgsOffset = Batcher->GetGPUInstanceCounterManager().AddDrawIndirect(SourceParticleData->GetGPUInstanceCountBufferOffset(), NumIndicesPerInstance, 0); } @@ -623,26 +774,30 @@ void FNiagaraRendererSprites::GetDynamicRayTracingInstances(FRayTracingMaterialG FNiagaraMeshCollectorResourcesSprite& CollectorResources = Context.RayTracingMeshResourceCollector.AllocateOneFrameResource(); FNiagaraSpriteVFLooseParameters VFLooseParams; SetVertexFactoryParticleData(CollectorResources.VertexFactory, DynamicDataSprites, CPUSimParticleDataAllocation, Context.ReferenceView, VFLooseParams, SceneProxy, RendererLayout); - CollectorResources.UniformBuffer = CreatePerViewUniformBuffer(Context.ReferenceView, Context.ReferenceViewFamily, SceneProxy, RendererLayout); + CollectorResources.UniformBuffer = CreatePerViewUniformBuffer(Context.ReferenceView, Context.ReferenceViewFamily, SceneProxy, RendererLayout, DynamicDataSprites); FMeshBatch MeshBatch; CreateMeshBatchForView(Context.ReferenceView, Context.ReferenceViewFamily, SceneProxy, DynamicDataSprites, IndirectArgsOffset, MeshBatch, VFLooseParams, CollectorResources, RendererLayout); - ensureMsgf(MeshBatch.Elements[0].IndexBuffer != &GSixTriangleParticleIndexBuffer, TEXT("Cutout geometry is not supported by ray tracing")); - RayTracingInstance.Materials.Add(MeshBatch); // USe the internal vertex buffer only when initialized otherwise used the shared vertex buffer - needs to be updated every frame FRWBuffer* VertexBuffer = RayTracingDynamicVertexBuffer.NumBytes > 0 ? &RayTracingDynamicVertexBuffer : nullptr; + // Different numbers of cutout vertices correspond to different index buffers + // For 8 verts, use GSixTriangleParticleIndexBuffer + // For 4 verts cutout geometry and normal particle geometry, use the typical 6 indices + const int32 NumVerticesPerInstance = NumCutoutVertexPerSubImage == 8 ? 18 : 6; + const int32 NumTrianglesPerInstance = NumCutoutVertexPerSubImage == 8 ? 6 : 2; + // Update dynamic ray tracing geometry Context.DynamicRayTracingGeometriesToUpdate.Add( FRayTracingDynamicGeometryUpdateParams { RayTracingInstance.Materials, MeshBatch.Elements[0].NumPrimitives == 0, - 6 * SourceParticleData->GetNumInstances(), - 6 * SourceParticleData->GetNumInstances() * (uint32)sizeof(FVector), - 2 * SourceParticleData->GetNumInstances(), + NumVerticesPerInstance * SourceParticleData->GetNumInstances(), + NumVerticesPerInstance * SourceParticleData->GetNumInstances() * (uint32)sizeof(FVector), + NumTrianglesPerInstance * SourceParticleData->GetNumInstances(), &RayTracingGeometry, VertexBuffer, true @@ -667,7 +822,7 @@ FNiagaraDynamicDataBase *FNiagaraRendererSprites::GenerateDynamicData(const FNia SCOPE_CYCLE_COUNTER(STAT_NiagaraGenSpriteDynamicData); FNiagaraDataBuffer* DataToRender = Emitter->GetData().GetCurrentData(); - if(SimTarget == ENiagaraSimTarget::GPUComputeSim || (DataToRender != nullptr && DataToRender->GetNumInstances() > 0)) + if(SimTarget == ENiagaraSimTarget::GPUComputeSim || (DataToRender != nullptr && (SourceMode == ENiagaraRendererSourceDataMode::Emitter || (SourceMode == ENiagaraRendererSourceDataMode::Particles && DataToRender->GetNumInstances() > 0)))) { DynamicData = new FNiagaraDynamicDataSprites(Emitter); @@ -679,6 +834,19 @@ FNiagaraDynamicDataBase *FNiagaraRendererSprites::GenerateDynamicData(const FNia DynamicData->Material = BaseMaterials_GT[0]->GetRenderProxy(); DynamicData->SetMaterialRelevance(BaseMaterialRelevance_GT); } + + if (DynamicData) + { + const FNiagaraParameterStore& ParameterData = Emitter->GetRendererBoundVariables(); + DynamicData->DataInterfacesBound = ParameterData.GetDataInterfaces(); + DynamicData->ObjectsBound = ParameterData.GetUObjects(); + DynamicData->ParameterDataBound = ParameterData.GetParameterDataArray(); + } + + if (DynamicData && Properties->MaterialParameterBindings.Num() != 0) + { + ProcessMaterialParameterBindings(MakeArrayView(Properties->MaterialParameterBindings), Emitter, MakeArrayView(BaseMaterials_GT)); + } } return DynamicData; // for VF that can fetch from particle data directly @@ -690,7 +858,7 @@ int FNiagaraRendererSprites::GetDynamicDataSize()const return Size; } -bool FNiagaraRendererSprites::IsMaterialValid(UMaterialInterface* Mat)const +bool FNiagaraRendererSprites::IsMaterialValid(const UMaterialInterface* Mat)const { return Mat && Mat->CheckMaterialUsage_Concurrent(MATUSAGE_NiagaraSprites); } \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRibbonRendererProperties.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRibbonRendererProperties.cpp index 28d7068d53c9..b4ec0cc43a47 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRibbonRendererProperties.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRibbonRendererProperties.cpp @@ -4,12 +4,12 @@ #include "NiagaraRendererRibbons.h" #include "NiagaraConstants.h" #include "NiagaraBoundsCalculatorHelper.h" +#include "NiagaraCustomVersion.h" #include "Modules/ModuleManager.h" #if WITH_EDITOR #include "Widgets/Images/SImage.h" #include "Styling/SlateIconFinder.h" #include "Widgets/SWidget.h" -#include "Styling/SlateBrush.h" #include "AssetThumbnail.h" #include "Widgets/Text/STextBlock.h" #endif @@ -18,15 +18,29 @@ TArray> UNiagaraRibbonRendererProperties::RibbonRendererPropertiesToDeferredInit; +FNiagaraRibbonUVSettings::FNiagaraRibbonUVSettings() + : LeadingEdgeMode(ENiagaraRibbonUVEdgeMode::Locked) + , TrailingEdgeMode(ENiagaraRibbonUVEdgeMode::Locked) + , DistributionMode(ENiagaraRibbonUVDistributionMode::ScaledUsingRibbonSegmentLength) + , TilingLength(100.0f) + , Offset(FVector2D(0.0f, 0.0f)) + , Scale(FVector2D(1.0f, 1.0f)) + , bEnablePerParticleUOverride(false) + , bEnablePerParticleVRangeOverride(false) +{ +} + UNiagaraRibbonRendererProperties::UNiagaraRibbonRendererProperties() : Material(nullptr) , FacingMode(ENiagaraRibbonFacingMode::Screen) - , UV0TilingDistance(0.0f) - , UV0Scale(FVector2D(1.0f, 1.0f)) - , UV0AgeOffsetMode(ENiagaraRibbonAgeOffsetMode::Scale) - , UV1TilingDistance(0.0f) - , UV1Scale(FVector2D(1.0f, 1.0f)) - , UV1AgeOffsetMode(ENiagaraRibbonAgeOffsetMode::Scale) +#if WITH_EDITORONLY_DATA + , UV0TilingDistance_DEPRECATED(0.0f) + , UV0Scale_DEPRECATED(FVector2D(1.0f, 1.0f)) + , UV0AgeOffsetMode_DEPRECATED(ENiagaraRibbonAgeOffsetMode::Scale) + , UV1TilingDistance_DEPRECATED(0.0f) + , UV1Scale_DEPRECATED(FVector2D(1.0f, 1.0f)) + , UV1AgeOffsetMode_DEPRECATED(ENiagaraRibbonAgeOffsetMode::Scale) +#endif , CurveTension(0.f) , TessellationMode(ENiagaraRibbonTessellationMode::Automatic) , TessellationFactor(16) @@ -37,7 +51,7 @@ UNiagaraRibbonRendererProperties::UNiagaraRibbonRendererProperties() FNiagaraTypeDefinition MaterialDef(UMaterialInterface::StaticClass()); MaterialUserParamBinding.Parameter.SetType(MaterialDef); - AttributeBindings.Reserve(14); + AttributeBindings.Reserve(18); AttributeBindings.Add(&PositionBinding); AttributeBindings.Add(&ColorBinding); AttributeBindings.Add(&VelocityBinding); @@ -52,15 +66,39 @@ UNiagaraRibbonRendererProperties::UNiagaraRibbonRendererProperties() AttributeBindings.Add(&DynamicMaterial1Binding); AttributeBindings.Add(&DynamicMaterial2Binding); AttributeBindings.Add(&DynamicMaterial3Binding); + AttributeBindings.Add(&U0OverrideBinding); + AttributeBindings.Add(&V0RangeOverrideBinding); + AttributeBindings.Add(&U1OverrideBinding); + AttributeBindings.Add(&V1RangeOverrideBinding); } -FNiagaraRenderer* UNiagaraRibbonRendererProperties::CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter) +FNiagaraRenderer* UNiagaraRibbonRendererProperties::CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) { FNiagaraRenderer* NewRenderer = new FNiagaraRendererRibbons(FeatureLevel, this, Emitter); - NewRenderer->Initialize(this, Emitter); + NewRenderer->Initialize(this, Emitter, InComponent); return NewRenderer; } +#if WITH_EDITORONLY_DATA +void UpgradeUVSettings(FNiagaraRibbonUVSettings& UVSettings, float TilingDistance, const FVector2D& Offset, const FVector2D& Scale) +{ + if (TilingDistance == 0) + { + UVSettings.LeadingEdgeMode = ENiagaraRibbonUVEdgeMode::SmoothTransition; + UVSettings.TrailingEdgeMode = ENiagaraRibbonUVEdgeMode::SmoothTransition; + UVSettings.DistributionMode = ENiagaraRibbonUVDistributionMode::ScaledUniformly; + } + else + { + UVSettings.LeadingEdgeMode = ENiagaraRibbonUVEdgeMode::Locked; + UVSettings.TrailingEdgeMode = ENiagaraRibbonUVEdgeMode::Locked; + UVSettings.DistributionMode = ENiagaraRibbonUVDistributionMode::TiledOverRibbonLength; + UVSettings.TilingLength = TilingDistance; + } + UVSettings.Offset = Offset; + UVSettings.Scale = Scale; +} +#endif void UNiagaraRibbonRendererProperties::PostLoad() { @@ -73,7 +111,17 @@ void UNiagaraRibbonRendererProperties::PostLoad() FNiagaraTypeDefinition MaterialDef(UMaterialInterface::StaticClass()); MaterialUserParamBinding.Parameter.SetType(MaterialDef); } + + const int32 NiagaraVer = GetLinkerCustomVersion(FNiagaraCustomVersion::GUID); + if (NiagaraVer < FNiagaraCustomVersion::RibbonRendererUVRefactor) + { + UpgradeUVSettings(UV0Settings, UV0TilingDistance_DEPRECATED, UV0Offset_DEPRECATED, UV0Scale_DEPRECATED); + UpgradeUVSettings(UV1Settings, UV1TilingDistance_DEPRECATED, UV1Offset_DEPRECATED, UV1Scale_DEPRECATED); + } + #endif + + PostLoadBindings(ENiagaraRendererSourceDataMode::Particles); } FNiagaraBoundsCalculator* UNiagaraRibbonRendererProperties::CreateBoundsCalculator() @@ -125,7 +173,7 @@ void UNiagaraRibbonRendererProperties::InitCDOPropertiesAfterModuleStartup() void UNiagaraRibbonRendererProperties::InitBindings() { - if (PositionBinding.BoundVariable.GetName() == NAME_None) + if (!PositionBinding.IsValid()) { PositionBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_POSITION); ColorBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_COLOR); @@ -141,6 +189,10 @@ void UNiagaraRibbonRendererProperties::InitBindings() RibbonIdBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_RIBBONID); RibbonLinkOrderBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_RIBBONLINKORDER); MaterialRandomBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_MATERIAL_RANDOM); + U0OverrideBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_RIBBONU0OVERRIDE); + V0RangeOverrideBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_RIBBONV0RANGEOVERRIDE); + U1OverrideBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_RIBBONU1OVERRIDE); + V1RangeOverrideBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_RIBBONV1RANGEOVERRIDE); } } @@ -148,51 +200,62 @@ void UNiagaraRibbonRendererProperties::CacheFromCompiledData(const FNiagaraDataS { // Initialize accessors bSortKeyDataSetAccessorIsAge = false; - SortKeyDataSetAccessor.Init(CompiledData, RibbonLinkOrderBinding.DataSetVariable.GetName()); + SortKeyDataSetAccessor.Init(CompiledData, RibbonLinkOrderBinding.GetDataSetBindableVariable().GetName()); if (!SortKeyDataSetAccessor.IsValid()) { bSortKeyDataSetAccessorIsAge = true; - SortKeyDataSetAccessor.Init(CompiledData, NormalizedAgeBinding.DataSetVariable.GetName()); + SortKeyDataSetAccessor.Init(CompiledData, NormalizedAgeBinding.GetDataSetBindableVariable().GetName()); } - PositionDataSetAccessor.Init(CompiledData, PositionBinding.DataSetVariable.GetName()); - SizeDataSetAccessor.Init(CompiledData, RibbonWidthBinding.DataSetVariable.GetName()); - TwistDataSetAccessor.Init(CompiledData, RibbonTwistBinding.DataSetVariable.GetName()); - FacingDataSetAccessor.Init(CompiledData, RibbonFacingBinding.DataSetVariable.GetName()); + PositionDataSetAccessor.Init(CompiledData, PositionBinding.GetDataSetBindableVariable().GetName()); + SizeDataSetAccessor.Init(CompiledData, RibbonWidthBinding.GetDataSetBindableVariable().GetName()); + TwistDataSetAccessor.Init(CompiledData, RibbonTwistBinding.GetDataSetBindableVariable().GetName()); + FacingDataSetAccessor.Init(CompiledData, RibbonFacingBinding.GetDataSetBindableVariable().GetName()); - MaterialParam0DataSetAccessor.Init(CompiledData, DynamicMaterialBinding.DataSetVariable.GetName()); - MaterialParam1DataSetAccessor.Init(CompiledData, DynamicMaterial1Binding.DataSetVariable.GetName()); - MaterialParam2DataSetAccessor.Init(CompiledData, DynamicMaterial2Binding.DataSetVariable.GetName()); - MaterialParam3DataSetAccessor.Init(CompiledData, DynamicMaterial3Binding.DataSetVariable.GetName()); + MaterialParam0DataSetAccessor.Init(CompiledData, DynamicMaterialBinding.GetDataSetBindableVariable().GetName()); + MaterialParam1DataSetAccessor.Init(CompiledData, DynamicMaterial1Binding.GetDataSetBindableVariable().GetName()); + MaterialParam2DataSetAccessor.Init(CompiledData, DynamicMaterial2Binding.GetDataSetBindableVariable().GetName()); + MaterialParam3DataSetAccessor.Init(CompiledData, DynamicMaterial3Binding.GetDataSetBindableVariable().GetName()); - if (RibbonIdBinding.DataSetVariable.GetType() == FNiagaraTypeDefinition::GetIDDef()) + FNiagaraDataSetAccessor U0OverrideDataSetAccessor; + U0OverrideDataSetAccessor.Init(CompiledData, U0OverrideBinding.GetDataSetBindableVariable().GetName()); + U0OverrideIsBound = U0OverrideDataSetAccessor.IsValid(); + FNiagaraDataSetAccessor U1OverrideDataSetAccessor; + U1OverrideDataSetAccessor.Init(CompiledData, U1OverrideBinding.GetDataSetBindableVariable().GetName()); + U1OverrideIsBound = U1OverrideDataSetAccessor.IsValid(); + + if (RibbonIdBinding.GetDataSetBindableVariable().GetType() == FNiagaraTypeDefinition::GetIDDef()) { - RibbonFullIDDataSetAccessor.Init(CompiledData, RibbonIdBinding.DataSetVariable.GetName()); + RibbonFullIDDataSetAccessor.Init(CompiledData, RibbonIdBinding.GetDataSetBindableVariable().GetName()); } else { - RibbonIdDataSetAccessor.Init(CompiledData, RibbonIdBinding.DataSetVariable.GetName()); + RibbonIdDataSetAccessor.Init(CompiledData, RibbonIdBinding.GetDataSetBindableVariable().GetName()); } const bool bShouldDoFacing = FacingMode == ENiagaraRibbonFacingMode::Custom || FacingMode == ENiagaraRibbonFacingMode::CustomSideVector; // Initialize layout RendererLayout.Initialize(ENiagaraRibbonVFLayout::Num); - RendererLayout.SetVariable(CompiledData, PositionBinding.DataSetVariable, ENiagaraRibbonVFLayout::Position); - RendererLayout.SetVariable(CompiledData, VelocityBinding.DataSetVariable, ENiagaraRibbonVFLayout::Velocity); - RendererLayout.SetVariable(CompiledData, ColorBinding.DataSetVariable, ENiagaraRibbonVFLayout::Color); - RendererLayout.SetVariable(CompiledData, RibbonWidthBinding.DataSetVariable, ENiagaraRibbonVFLayout::Width); - RendererLayout.SetVariable(CompiledData, RibbonTwistBinding.DataSetVariable, ENiagaraRibbonVFLayout::Twist); + RendererLayout.SetVariableFromBinding(CompiledData, PositionBinding, ENiagaraRibbonVFLayout::Position); + RendererLayout.SetVariableFromBinding(CompiledData, VelocityBinding, ENiagaraRibbonVFLayout::Velocity); + RendererLayout.SetVariableFromBinding(CompiledData, ColorBinding, ENiagaraRibbonVFLayout::Color); + RendererLayout.SetVariableFromBinding(CompiledData, RibbonWidthBinding, ENiagaraRibbonVFLayout::Width); + RendererLayout.SetVariableFromBinding(CompiledData, RibbonTwistBinding, ENiagaraRibbonVFLayout::Twist); if (bShouldDoFacing) { - RendererLayout.SetVariable(CompiledData, RibbonFacingBinding.DataSetVariable, ENiagaraRibbonVFLayout::Facing); + RendererLayout.SetVariableFromBinding(CompiledData, RibbonFacingBinding, ENiagaraRibbonVFLayout::Facing); } - RendererLayout.SetVariable(CompiledData, NormalizedAgeBinding.DataSetVariable, ENiagaraRibbonVFLayout::NormalizedAge); - RendererLayout.SetVariable(CompiledData, MaterialRandomBinding.DataSetVariable, ENiagaraRibbonVFLayout::MaterialRandom); - MaterialParamValidMask = RendererLayout.SetVariable(CompiledData, DynamicMaterialBinding.DataSetVariable, ENiagaraRibbonVFLayout::MaterialParam0) ? 1 : 0; - MaterialParamValidMask |= RendererLayout.SetVariable(CompiledData, DynamicMaterial1Binding.DataSetVariable, ENiagaraRibbonVFLayout::MaterialParam1) ? 2 : 0; - MaterialParamValidMask |= RendererLayout.SetVariable(CompiledData, DynamicMaterial2Binding.DataSetVariable, ENiagaraRibbonVFLayout::MaterialParam2) ? 4 : 0; - MaterialParamValidMask |= RendererLayout.SetVariable(CompiledData, DynamicMaterial3Binding.DataSetVariable, ENiagaraRibbonVFLayout::MaterialParam3) ? 8 : 0; + RendererLayout.SetVariableFromBinding(CompiledData, NormalizedAgeBinding, ENiagaraRibbonVFLayout::NormalizedAge); + RendererLayout.SetVariableFromBinding(CompiledData, MaterialRandomBinding, ENiagaraRibbonVFLayout::MaterialRandom); + RendererLayout.SetVariableFromBinding(CompiledData, U0OverrideBinding, ENiagaraRibbonVFLayout::U0Override); + RendererLayout.SetVariableFromBinding(CompiledData, V0RangeOverrideBinding, ENiagaraRibbonVFLayout::V0RangeOverride); + RendererLayout.SetVariableFromBinding(CompiledData, U1OverrideBinding, ENiagaraRibbonVFLayout::U1Override); + RendererLayout.SetVariableFromBinding(CompiledData, V1RangeOverrideBinding, ENiagaraRibbonVFLayout::V1RangeOverride); + MaterialParamValidMask = RendererLayout.SetVariableFromBinding(CompiledData, DynamicMaterialBinding, ENiagaraRibbonVFLayout::MaterialParam0) ? 1 : 0; + MaterialParamValidMask |= RendererLayout.SetVariableFromBinding(CompiledData, DynamicMaterial1Binding, ENiagaraRibbonVFLayout::MaterialParam1) ? 2 : 0; + MaterialParamValidMask |= RendererLayout.SetVariableFromBinding(CompiledData, DynamicMaterial2Binding, ENiagaraRibbonVFLayout::MaterialParam2) ? 4 : 0; + MaterialParamValidMask |= RendererLayout.SetVariableFromBinding(CompiledData, DynamicMaterial3Binding, ENiagaraRibbonVFLayout::MaterialParam3) ? 8 : 0; RendererLayout.Finalize(); } @@ -224,6 +287,10 @@ const TArray& UNiagaraRibbonRendererProperties::GetOptionalAtt Attrs.Add(SYS_PARAM_PARTICLES_RIBBONWIDTH); Attrs.Add(SYS_PARAM_PARTICLES_RIBBONFACING); Attrs.Add(SYS_PARAM_PARTICLES_RIBBONLINKORDER); + Attrs.Add(SYS_PARAM_PARTICLES_RIBBONU0OVERRIDE); + Attrs.Add(SYS_PARAM_PARTICLES_RIBBONV0RANGEOVERRIDE); + Attrs.Add(SYS_PARAM_PARTICLES_RIBBONU1OVERRIDE); + Attrs.Add(SYS_PARAM_PARTICLES_RIBBONV1RANGEOVERRIDE); } return Attrs; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScalabilityManager.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScalabilityManager.cpp index 1207822a6087..4fe84f39731e 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScalabilityManager.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScalabilityManager.cpp @@ -188,7 +188,7 @@ void FNiagaraScalabilityManager::Update(FNiagaraWorldManager* WorldMan) const FNiagaraSystemScalabilitySettings& ScalabilitySettings = System->GetScalabilitySettings(); SignificanceSortedIndices.Add(i); - bNeedSortedSignificanceCull = ScalabilitySettings.bCullMaxInstanceCount && ScalabilitySettings.MaxInstances > 0; + bNeedSortedSignificanceCull |= ScalabilitySettings.bCullMaxInstanceCount && ScalabilitySettings.MaxInstances > 0; WorldMan->CalculateScalabilityState(System, ScalabilitySettings, EffectType, Component, false, CompState); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScript.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScript.cpp index 1ff34a03e326..af0d2a42ef88 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScript.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScript.cpp @@ -181,6 +181,7 @@ bool FNiagaraVMExecutableDataId::operator==(const FNiagaraVMExecutableDataId& Re BaseScriptCompileHash != ReferenceSet.BaseScriptCompileHash || #endif bUsesRapidIterationParams != ReferenceSet.bUsesRapidIterationParams || + bUseShaderPermutations != ReferenceSet.bUseShaderPermutations || bInterpolatedSpawn != ReferenceSet.bInterpolatedSpawn || bRequiresPersistentIDs != ReferenceSet.bRequiresPersistentIDs ) { @@ -251,7 +252,6 @@ void FNiagaraVMExecutableDataId::AppendKeyString(FString& KeyString, const FStri KeyString += TEXT("[AdditionalDefines]") + Delimiter; } - if (bUsesRapidIterationParams) { KeyString += TEXT("USESRI") + Delimiter; @@ -261,6 +261,15 @@ void FNiagaraVMExecutableDataId::AppendKeyString(FString& KeyString, const FStri KeyString += TEXT("NORI") + Delimiter; } + if (bUseShaderPermutations) + { + KeyString += TEXT("USEPERM_"); + } + else + { + KeyString += TEXT("NOPERM_"); + } + for (int32 Idx = 0; Idx < AdditionalDefines.Num(); Idx++) { KeyString += AdditionalDefines[Idx]; @@ -296,6 +305,7 @@ UNiagaraScript::UNiagaraScript(const FObjectInitializer& ObjectInitializer) #if WITH_EDITORONLY_DATA , UsageIndex_DEPRECATED(0) , ModuleUsageBitmask( (1 << (int32)ENiagaraScriptUsage::ParticleSpawnScript) | (1 << (int32)ENiagaraScriptUsage::ParticleSpawnScriptInterpolated) | (1 << (int32)ENiagaraScriptUsage::ParticleUpdateScript) | (1 << (int32)ENiagaraScriptUsage::ParticleEventScript) | (1 << (int32)ENiagaraScriptUsage::ParticleSimulationStageScript)) + , LibraryVisibility(ENiagaraScriptLibraryVisibility::Unexposed) , NumericOutputTypeSelectionMode(ENiagaraNumericOutputTypeSelectionMode::Largest) #endif { @@ -349,6 +359,7 @@ void UNiagaraScript::ComputeVMCompilationId(FNiagaraVMExecutableDataId& Id) cons Id = FNiagaraVMExecutableDataId(); Id.bUsesRapidIterationParams = true; + Id.bUseShaderPermutations = true; Id.bInterpolatedSpawn = false; Id.bRequiresPersistentIDs = false; @@ -365,6 +376,10 @@ void UNiagaraScript::ComputeVMCompilationId(FNiagaraVMExecutableDataId& Id) cons { Id.bUsesRapidIterationParams = false; } + if (!EmitterOwner->bUseShaderPermutations) + { + Id.bUseShaderPermutations = false; + } if (EmitterOwner->bCompressAttributes) { Id.AdditionalDefines.Add(TEXT("CompressAttributes")); @@ -403,12 +418,29 @@ void UNiagaraScript::ComputeVMCompilationId(FNiagaraVMExecutableDataId& Id) cons { Id.AdditionalDefines.Add(TEXT("TrimAttributes")); + TArray PreserveAttributes; + // preserve the attributes that have been defined on the emitter directly for (const FString& Attribute : Emitter->AttributesToPreserve) { const FString PreserveDefine = TEXT("PreserveAttribute=") + Attribute; - Id.AdditionalDefines.Add(PreserveDefine); + PreserveAttributes.AddUnique(PreserveDefine); } + + // Now preserve the attributes that have been defined on the renderers in use + for (UNiagaraRendererProperties* RendererProperty : Emitter->GetRenderers()) + { + for (const FNiagaraVariable& BoundAttribute : RendererProperty->GetBoundAttributes()) + { + const FString PreserveDefine = TEXT("PreserveAttribute=") + BoundAttribute.GetName().ToString(); + PreserveAttributes.AddUnique(PreserveDefine); + } + } + + // We sort the keys so that it doesn't matter what order they were defined in. + PreserveAttributes.Sort([](const FString& A, const FString& B) -> bool { return A < B; }); + + Id.AdditionalDefines.Append(PreserveAttributes); } } @@ -451,7 +483,7 @@ void UNiagaraScript::ComputeVMCompilationId(FNiagaraVMExecutableDataId& Id) cons for (UNiagaraSimulationStageBase* Base : Emitter->GetSimulationStages()) { // bool AppendCompileHash(FNiagaraCompileHashVisitor* InVisitor) const; - if (Base) + if (Base && Base->bEnabled) { Base->AppendCompileHash(&Visitor); } @@ -485,6 +517,10 @@ void UNiagaraScript::ComputeVMCompilationId(FNiagaraVMExecutableDataId& Id) cons { Id.bUsesRapidIterationParams = false; } + if (!System->bUseShaderPermutations) + { + Id.bUseShaderPermutations = false; + } if (System->bCompressAttributes) { Id.AdditionalDefines.Add(TEXT("CompressAttributes")); @@ -1056,7 +1092,6 @@ void UNiagaraScript::PostLoad() } } - bool bNeedsRecompile = false; const int32 NiagaraVer = GetLinkerCustomVersion(FNiagaraCustomVersion::GUID); #if WITH_EDITORONLY_DATA @@ -1140,9 +1175,10 @@ void UNiagaraScript::PostLoad() InvalidateCompileResults(RebuildReason); } - if (NiagaraVer < FNiagaraCustomVersion::AddLibraryAssetProperty) + // Convert visibility of old assets + if (NiagaraVer < FNiagaraCustomVersion::AddLibraryAssetProperty || (NiagaraVer < FNiagaraCustomVersion::AddLibraryVisibilityProperty && bExposeToLibrary_DEPRECATED)) { - bExposeToLibrary = true; + LibraryVisibility = ENiagaraScriptLibraryVisibility::Library; } } #endif @@ -1827,6 +1863,27 @@ void UNiagaraScript::GetAssetRegistryTags(TArray& OutTags) co #endif } +void UNiagaraScript::BeginDestroy() +{ + Super::BeginDestroy(); + + if (!HasAnyFlags(RF_ClassDefaultObject) && ScriptResource) + { + ScriptResource->QueueForRelease(ReleasedByRT); + } + else + { + ReleasedByRT = true; + } +} + +bool UNiagaraScript::IsReadyForFinishDestroy() +{ + const bool bIsReady = Super::IsReadyForFinishDestroy(); + + return bIsReady && ReleasedByRT; +} + bool UNiagaraScript::IsEditorOnly() const { #if WITH_EDITOR @@ -1960,7 +2017,7 @@ void UNiagaraScript::CacheResourceShadersForCooking(EShaderPlatform ShaderPlatfo NewResource->SetScript(this, TargetFeatureLevel, ShaderPlatform, CachedScriptVMId.CompilerVersionID, CachedScriptVMId.AdditionalDefines, CachedScriptVMId.BaseScriptCompileHash, CachedScriptVMId.ReferencedCompileHashes, - CachedScriptVMId.bUsesRapidIterationParams, GetFriendlyName()); + CachedScriptVMId.bUsesRapidIterationParams, CachedScriptVMId.bUseShaderPermutations, GetFriendlyName()); ResourceToCache = NewResource; check(ResourceToCache); @@ -2029,7 +2086,7 @@ void UNiagaraScript::CacheResourceShadersForRendering(bool bRegenerateId, bool b ScriptResource->SetScript(this, CacheFeatureLevel, ShaderPlatform, CachedScriptVMId.CompilerVersionID, CachedScriptVMId.AdditionalDefines, CachedScriptVMId.BaseScriptCompileHash, CachedScriptVMId.ReferencedCompileHashes, - CachedScriptVMId.bUsesRapidIterationParams, GetFriendlyName()); + CachedScriptVMId.bUsesRapidIterationParams, CachedScriptVMId.bUseShaderPermutations, GetFriendlyName()); if (FNiagaraUtilities::SupportsGPUParticles(ShaderPlatform)) { @@ -2216,8 +2273,7 @@ NIAGARA_API bool UNiagaraScript::IsScriptCompilationPending(bool bGPUScript) con { if (ScriptResource.IsValid()) { - FNiagaraShaderRef Shader = ScriptResource->GetShaderGameThread(); - if (Shader.IsValid()) + if (ScriptResource->IsShaderMapComplete()) { return false; } @@ -2238,8 +2294,7 @@ NIAGARA_API bool UNiagaraScript::DidScriptCompilationSucceed(bool bGPUScript) co { if (ScriptResource.IsValid()) { - FNiagaraShaderRef Shader = ScriptResource->GetShaderGameThread(); - if (Shader.IsValid()) + if (ScriptResource->IsShaderMapComplete()) { return true; } @@ -2436,6 +2491,17 @@ TArray UNiagaraScript::GetSupportedUsageContextsForBitmask( } return Supported; } + +bool UNiagaraScript::IsSupportedUsageContextForBitmask(int32 InModuleUsageBitmask, ENiagaraScriptUsage InUsageContext) +{ + int32 TargetBit = (InModuleUsageBitmask >> (int32)InUsageContext) & 1; + if (TargetBit == 1) + { + return true; + } + return false; +} + #endif bool UNiagaraScript::CanBeRunOnGpu()const diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptExecutionContext.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptExecutionContext.cpp index 7dd5e6882795..9c8e15b610e2 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptExecutionContext.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptExecutionContext.cpp @@ -19,7 +19,7 @@ DECLARE_CYCLE_STAT(TEXT("Rebind DInterface Func Table"), STAT_NiagaraRebindDataI //Internal constants - only needed for non-GPU sim -uint32 FNiagaraScriptExecutionContext::TickCounter = 0; +uint32 FNiagaraScriptExecutionContextBase::TickCounter = 0; static int32 GbExecVMScripts = 1; static FAutoConsoleVariableRef CVarNiagaraExecVMScripts( @@ -29,17 +29,17 @@ static FAutoConsoleVariableRef CVarNiagaraExecVMScripts( ECVF_Default ); -FNiagaraScriptExecutionContext::FNiagaraScriptExecutionContext() +FNiagaraScriptExecutionContextBase::FNiagaraScriptExecutionContextBase() : Script(nullptr) { } -FNiagaraScriptExecutionContext::~FNiagaraScriptExecutionContext() +FNiagaraScriptExecutionContextBase::~FNiagaraScriptExecutionContextBase() { } -bool FNiagaraScriptExecutionContext::Init(UNiagaraScript* InScript, ENiagaraSimTarget InTarget) +bool FNiagaraScriptExecutionContextBase::Init(UNiagaraScript* InScript, ENiagaraSimTarget InTarget) { Script = InScript; @@ -47,16 +47,142 @@ bool FNiagaraScriptExecutionContext::Init(UNiagaraScript* InScript, ENiagaraSimT HasInterpolationParameters = Script && Script->GetComputedVMCompilationId().HasInterpolatedParameters(); + return true; +} + +void FNiagaraScriptExecutionContextBase::BindData(int32 Index, FNiagaraDataSet& DataSet, int32 StartInstance, bool bUpdateInstanceCounts) +{ + FNiagaraDataBuffer* Input = DataSet.GetCurrentData(); + FNiagaraDataBuffer* Output = DataSet.GetDestinationData(); + + DataSetInfo.SetNum(FMath::Max(DataSetInfo.Num(), Index + 1)); + DataSetInfo[Index].Init(&DataSet, Input, Output, StartInstance, bUpdateInstanceCounts); + + //Would be nice to roll this and DataSetInfo into one but currently the VM being in it's own Engine module prevents this. Possibly should move the VM into Niagara itself. + TArrayView InputRegisters = Input ? Input->GetRegisterTable() : TArrayView(); + TArrayView OutputRegisters = Output ? Output->GetRegisterTable() : TArrayView(); + + DataSetMetaTable.SetNum(FMath::Max(DataSetMetaTable.Num(), Index + 1)); + DataSetMetaTable[Index].Init(InputRegisters, OutputRegisters, StartInstance, + Output ? &Output->GetIDTable() : nullptr, &DataSet.GetFreeIDTable(), &DataSet.GetNumFreeIDs(), &DataSet.GetMaxUsedID(), DataSet.GetIDAcquireTag(), &DataSet.GetSpawnedIDsTable()); + + if (InputRegisters.Num() > 0) + { + static_assert(sizeof(DataSetMetaTable[Index].InputRegisterTypeOffsets) == sizeof(FNiagaraDataBuffer::RegisterTypeOffsetType), "ArraySizes do not match"); + memcpy(DataSetMetaTable[Index].InputRegisterTypeOffsets, Input->GetRegisterTypeOffsets(), sizeof(FNiagaraDataBuffer::RegisterTypeOffsetType)); + } + + if (OutputRegisters.Num() > 0) + { + static_assert(sizeof(DataSetMetaTable[Index].OutputRegisterTypeOffsets) == sizeof(FNiagaraDataBuffer::RegisterTypeOffsetType), "ArraySizes do not match"); + memcpy(DataSetMetaTable[Index].OutputRegisterTypeOffsets, Output->GetRegisterTypeOffsets(), sizeof(FNiagaraDataBuffer::RegisterTypeOffsetType)); + } +} + +void FNiagaraScriptExecutionContextBase::BindData(int32 Index, FNiagaraDataBuffer* Input, int32 StartInstance, bool bUpdateInstanceCounts) +{ + check(Input && Input->GetOwner()); + DataSetInfo.SetNum(FMath::Max(DataSetInfo.Num(), Index + 1)); + FNiagaraDataSet* DataSet = Input->GetOwner(); + DataSetInfo[Index].Init(DataSet, Input, nullptr, StartInstance, bUpdateInstanceCounts); + + TArrayView InputRegisters = Input->GetRegisterTable(); + + DataSetMetaTable.SetNum(FMath::Max(DataSetMetaTable.Num(), Index + 1)); + DataSetMetaTable[Index].Init(InputRegisters, TArrayView(), StartInstance, nullptr, nullptr, &DataSet->GetNumFreeIDs(), &DataSet->GetMaxUsedID(), DataSet->GetIDAcquireTag(), &DataSet->GetSpawnedIDsTable()); + + if (InputRegisters.Num() > 0) + { + static_assert(sizeof(DataSetMetaTable[Index].InputRegisterTypeOffsets) == sizeof(FNiagaraDataBuffer::RegisterTypeOffsetType), "ArraySizes do not match"); + memcpy(DataSetMetaTable[Index].InputRegisterTypeOffsets, Input->GetRegisterTypeOffsets(), sizeof(FNiagaraDataBuffer::RegisterTypeOffsetType)); + } +} + +bool FNiagaraScriptExecutionContextBase::Execute(uint32 NumInstances, const FScriptExecutionConstantBufferTable& ConstantBufferTable) +{ + if (NumInstances == 0) + { + DataSetInfo.Reset(); + return true; + } + + ++TickCounter;//Should this be per execution? + + if (GbExecVMScripts != 0) + { + const FNiagaraVMExecutableData& ExecData = Script->GetVMExecutableData(); + VectorVM::Exec( + ExecData.ByteCode.GetData(), + ExecData.OptimizedByteCode.Num() > 0 ? ExecData.OptimizedByteCode.GetData() : nullptr, + ExecData.NumTempRegisters, + ConstantBufferTable.Buffers.Num(), + ConstantBufferTable.Buffers.GetData(), + ConstantBufferTable.BufferSizes.GetData(), + DataSetMetaTable, + FunctionTable.GetData(), + UserPtrTable.GetData(), + NumInstances +#if STATS + , Script->GetStatScopeIDs() +#elif ENABLE_STATNAMEDEVENTS + , Script->GetStatNamedEvents() +#endif + ); + } + + // Tell the datasets we wrote how many instances were actually written. + for (int Idx = 0; Idx < DataSetInfo.Num(); Idx++) + { + FNiagaraDataSetExecutionInfo& Info = DataSetInfo[Idx]; + +#if NIAGARA_NAN_CHECKING + Info.DataSet->CheckForNaNs(); +#endif + + if (Info.bUpdateInstanceCount) + { + Info.Output->SetNumInstances(Info.StartInstance + DataSetMetaTable[Idx].DataSetAccessIndex + 1); + } + } + + //Can maybe do without resetting here. Just doing it for tidiness. + for (int32 DataSetIdx = 0; DataSetIdx < DataSetInfo.Num(); ++DataSetIdx) + { + DataSetInfo[DataSetIdx].Reset(); + DataSetMetaTable[DataSetIdx].Reset(); + } + return true;//TODO: Error cases? } +bool FNiagaraScriptExecutionContextBase::CanExecute()const +{ + return Script && Script->GetVMExecutableData().IsValid() && Script->GetVMExecutableData().ByteCode.Num() > 0; +} + +TArrayView FNiagaraScriptExecutionContextBase::GetScriptLiterals() const +{ +#if WITH_EDITORONLY_DATA + return Parameters.GetScriptLiterals(); +#else + return MakeArrayView(Script->GetVMExecutableData().ScriptLiterals); +#endif +} + +////////////////////////////////////////////////////////////////////////// + +void FNiagaraScriptExecutionContextBase::DirtyDataInterfaces() +{ + Parameters.MarkInterfacesDirty(); +} + bool FNiagaraScriptExecutionContext::Tick(FNiagaraSystemInstance* ParentSystemInstance, ENiagaraSimTarget SimTarget) { //Bind data interfaces if needed. if (Parameters.GetInterfacesDirty()) { SCOPE_CYCLE_COUNTER(STAT_NiagaraScriptExecContextTick); - if (Script && Script->IsReadyToRun(ENiagaraSimTarget::CPUSim))//TODO: Remove. Script can only be null for system instances that currently don't have their script exec context set up correctly. + if (Script && Script->IsReadyToRun(ENiagaraSimTarget::CPUSim) && SimTarget == ENiagaraSimTarget::CPUSim)//TODO: Remove. Script can only be null for system instances that currently don't have their script exec context set up correctly. { const FNiagaraVMExecutableData& ScriptExecutableData = Script->GetVMExecutableData(); const TArray& DataInterfaces = GetDataInterfaces(); @@ -77,7 +203,7 @@ bool FNiagaraScriptExecutionContext::Tick(FNiagaraSystemInstance* ParentSystemIn //Fill the instance data table. if (ParentSystemInstance) { - DataInterfaceInstDataTable.SetNumZeroed(ScriptExecutableData.NumUserPtrs, false); + UserPtrTable.SetNumZeroed(ScriptExecutableData.NumUserPtrs, false); for (int32 i = 0; i < DataInterfaces.Num(); i++) { UNiagaraDataInterface* Interface = DataInterfaces[i]; @@ -86,7 +212,7 @@ bool FNiagaraScriptExecutionContext::Tick(FNiagaraSystemInstance* ParentSystemIn if (UserPtrIdx != INDEX_NONE) { void* InstData = ParentSystemInstance->FindDataInterfaceInstanceData(Interface); - DataInterfaceInstDataTable[UserPtrIdx] = InstData; + UserPtrTable[UserPtrIdx] = InstData; } } } @@ -140,7 +266,7 @@ bool FNiagaraScriptExecutionContext::Tick(FNiagaraSystemInstance* ParentSystemIn } } - void* InstData = ScriptInfo.UserPtrIdx == INDEX_NONE ? nullptr : DataInterfaceInstDataTable[ScriptInfo.UserPtrIdx]; + void* InstData = ScriptInfo.UserPtrIdx == INDEX_NONE ? nullptr : UserPtrTable[ScriptInfo.UserPtrIdx]; FVMExternalFunction& LocalFunction = LocalFunctionTable.AddDefaulted_GetRef(); LocalFunctionTableIndices.Add(FunctionIt); @@ -187,7 +313,7 @@ bool FNiagaraScriptExecutionContext::Tick(FNiagaraSystemInstance* ParentSystemIn return true; } -void FNiagaraScriptExecutionContext::PostTick() +void FNiagaraScriptExecutionContextBase::PostTick() { //If we're for interpolated spawn, copy over the previous frame's parameters into the Prev parameters. if (HasInterpolationParameters) @@ -196,129 +322,218 @@ void FNiagaraScriptExecutionContext::PostTick() } } -void FNiagaraScriptExecutionContext::BindData(int32 Index, FNiagaraDataSet& DataSet, int32 StartInstance, bool bUpdateInstanceCounts) +////////////////////////////////////////////////////////////////////////// + +void FNiagaraSystemScriptExecutionContext::PerInstanceFunctionHook(FVectorVMContext& Context, int32 PerInstFunctionIndex, int32 UserPtrIndex) { - FNiagaraDataBuffer* Input = DataSet.GetCurrentData(); - FNiagaraDataBuffer* Output = DataSet.GetDestinationData(); + check(SystemInstances); + + //This is a bit of a hack. We grab the base offset into the instance data from the primary dataset. + //TODO: Find a cleaner way to do this. + int32 InstanceOffset = Context.GetDataSetMeta(0).InstanceOffset; - DataSetInfo.SetNum(FMath::Max(DataSetInfo.Num(), Index + 1)); - DataSetInfo[Index].Init(&DataSet, Input, Output, StartInstance, bUpdateInstanceCounts); + //Cache context state. + int32 CachedContextStartInstance = Context.StartInstance; + int32 CachedContextNumInstances = Context.NumInstances; + uint8 const* CachedCodeLocation = Context.Code; - //Would be nice to roll this and DataSetInfo into one but currently the VM being in it's own Engine module prevents this. Possibly should move the VM into Niagara itself. - TArrayView InputRegisters = Input ? Input->GetRegisterTable() : TArrayView(); - TArrayView OutputRegisters = Output ? Output->GetRegisterTable() : TArrayView(); + //Hack context so we can run the DI calls one by one. + Context.NumInstances = 1; - DataSetMetaTable.SetNum(FMath::Max(DataSetMetaTable.Num(), Index + 1)); - DataSetMetaTable[Index].Init(InputRegisters, OutputRegisters, StartInstance, - Output ? &Output->GetIDTable() : nullptr, &DataSet.GetFreeIDTable(), &DataSet.GetNumFreeIDs(), &DataSet.GetMaxUsedID(), DataSet.GetIDAcquireTag(), &DataSet.GetSpawnedIDsTable()); - - if (InputRegisters.Num() > 0) + for (int32 i = 0; i < CachedContextNumInstances; ++i) { - static_assert(sizeof(DataSetMetaTable[Index].InputRegisterTypeOffsets) == sizeof(FNiagaraDataBuffer::RegisterTypeOffsetType), "ArraySizes do not match"); - memcpy(DataSetMetaTable[Index].InputRegisterTypeOffsets, Input->GetRegisterTypeOffsets(), sizeof(FNiagaraDataBuffer::RegisterTypeOffsetType)); - } + //Reset the code each iteration. + Context.Code = CachedCodeLocation; + //Offset buffer I/O to the correct instance's data. + Context.ExternalFunctionInstanceOffset = i; - if (OutputRegisters.Num() > 0) - { - static_assert(sizeof(DataSetMetaTable[Index].OutputRegisterTypeOffsets) == sizeof(FNiagaraDataBuffer::RegisterTypeOffsetType), "ArraySizes do not match"); - memcpy(DataSetMetaTable[Index].OutputRegisterTypeOffsets, Output->GetRegisterTypeOffsets(), sizeof(FNiagaraDataBuffer::RegisterTypeOffsetType)); - } -} - -void FNiagaraScriptExecutionContext::BindData(int32 Index, FNiagaraDataBuffer* Input, int32 StartInstance, bool bUpdateInstanceCounts) -{ - check(Input && Input->GetOwner()); - DataSetInfo.SetNum(FMath::Max(DataSetInfo.Num(), Index + 1)); - FNiagaraDataSet* DataSet = Input->GetOwner(); - DataSetInfo[Index].Init(DataSet, Input, nullptr, StartInstance, bUpdateInstanceCounts); - - TArrayView InputRegisters = Input->GetRegisterTable(); - - DataSetMetaTable.SetNum(FMath::Max(DataSetMetaTable.Num(), Index + 1)); - DataSetMetaTable[Index].Init(InputRegisters, TArrayView(), StartInstance, nullptr, nullptr, &DataSet->GetNumFreeIDs(), &DataSet->GetMaxUsedID(), DataSet->GetIDAcquireTag(), &DataSet->GetSpawnedIDsTable()); - - if (InputRegisters.Num() > 0) - { - static_assert(sizeof(DataSetMetaTable[Index].InputRegisterTypeOffsets) == sizeof(FNiagaraDataBuffer::RegisterTypeOffsetType), "ArraySizes do not match"); - memcpy(DataSetMetaTable[Index].InputRegisterTypeOffsets, Input->GetRegisterTypeOffsets(), sizeof(FNiagaraDataBuffer::RegisterTypeOffsetType)); - } -} - -bool FNiagaraScriptExecutionContext::Execute(uint32 NumInstances, const FScriptExecutionConstantBufferTable& ConstantBufferTable) -{ - if (NumInstances == 0) - { - DataSetInfo.Reset(); - return true; - } - - ++TickCounter;//Should this be per execution? - - if (GbExecVMScripts != 0) - { - const FNiagaraVMExecutableData& ExecData = Script->GetVMExecutableData(); - VectorVM::Exec( - ExecData.ByteCode.GetData(), - ExecData.OptimizedByteCode.Num() > 0 ? ExecData.OptimizedByteCode.GetData() : nullptr, - ExecData.NumTempRegisters, - ConstantBufferTable.Buffers.Num(), - ConstantBufferTable.Buffers.GetData(), - ConstantBufferTable.BufferSizes.GetData(), - DataSetMetaTable, - FunctionTable.GetData(), - DataInterfaceInstDataTable.GetData(), - NumInstances -#if STATS - , Script->GetStatScopeIDs() -#elif ENABLE_STATNAMEDEVENTS - , Script->GetStatNamedEvents() -#endif - ); - } - - // Tell the datasets we wrote how many instances were actually written. - for (int Idx = 0; Idx < DataSetInfo.Num(); Idx++) - { - FNiagaraDataSetExecutionInfo& Info = DataSetInfo[Idx]; - -#if NIAGARA_NAN_CHECKING - Info.DataSet->CheckForNaNs(); -#endif - - if (Info.bUpdateInstanceCount) + int32 InstanceIndex = InstanceOffset + CachedContextStartInstance + i; + FNiagaraSystemInstance* Instance = (*SystemInstances)[InstanceIndex]; + const FNiagaraPerInstanceDIFuncInfo& FuncInfo = Instance->GetPerInstanceDIFunction(ScriptType, PerInstFunctionIndex); + + //TODO: We can embed the instance data inside the function lambda. No need for the user ptr table at all. + //Do this way for now to reduce overall complexity of the initial change. Doing this needs extensive boiler plate changes to most DI classes and a script recompile. + if(UserPtrIndex != INDEX_NONE) { - Info.Output->SetNumInstances(Info.StartInstance + DataSetMetaTable[Idx].DataSetAccessIndex + 1); + Context.UserPtrTable[UserPtrIndex] = FuncInfo.InstData; + } + + Context.StartInstance = InstanceIndex; + + //TODO: In future for DIs where more perf is needed here we could split the DI func into an args gen and a execution. + //The this path could gen args from the bytecode once and just run the execution func per instance. + //I wonder if we could auto generate the args gen in a template func and just pass them into the DI for perf and reduced end user/author complexity. + FuncInfo.Function.Execute(Context); + } + + //Restore the context state. + Context.ExternalFunctionInstanceOffset = 0; + Context.StartInstance = CachedContextStartInstance; + Context.NumInstances = CachedContextNumInstances; +} + +bool FNiagaraSystemScriptExecutionContext::Init(UNiagaraScript* InScript, ENiagaraSimTarget InTarget) +{ + return FNiagaraScriptExecutionContextBase::Init(InScript, InTarget); +} + +bool FNiagaraSystemScriptExecutionContext::Tick(class FNiagaraSystemInstance* Instance, ENiagaraSimTarget SimTarget) +{ + //Bind data interfaces if needed. + if (Parameters.GetInterfacesDirty()) + { + SCOPE_CYCLE_COUNTER(STAT_NiagaraScriptExecContextTick); + if (Script && Script->IsReadyToRun(ENiagaraSimTarget::CPUSim))//TODO: Remove. Script can only be null for system instances that currently don't have their script exec context set up correctly. + { + const FNiagaraVMExecutableData& ScriptExecutableData = Script->GetVMExecutableData(); + const TArray& DataInterfaces = GetDataInterfaces(); + + const int32 FunctionCount = ScriptExecutableData.CalledVMExternalFunctions.Num(); + FunctionTable.Reset(); + FunctionTable.SetNum(FunctionCount); + ExtFunctionInfo.AddDefaulted(FunctionCount); + + const FNiagaraScriptExecutionParameterStore* ScriptParameterStore = Script->GetExecutionReadyParameterStore(ENiagaraSimTarget::CPUSim); + check(ScriptParameterStore != nullptr); + const auto& ScriptDataInterfaces = ScriptParameterStore->GetDataInterfaces(); + int32 NumPerInstanceFunctions = 0; + for (int32 FunctionIndex = 0; FunctionIndex < FunctionCount; ++FunctionIndex) + { + const FVMExternalFunctionBindingInfo& BindingInfo = ScriptExecutableData.CalledVMExternalFunctions[FunctionIndex]; + + FExternalFuncInfo& FuncInfo = ExtFunctionInfo[FunctionIndex]; + + // First check to see if we can pull from the fast path library.. + if (UNiagaraFunctionLibrary::GetVectorVMFastPathExternalFunction(BindingInfo, FuncInfo.Function) && FuncInfo.Function.IsBound()) + { + continue; + } + + //TODO: Remove use of userptr table here and just embed the instance data in the function lambda. + UserPtrTable.SetNumZeroed(ScriptExecutableData.NumUserPtrs, false); + + //Next check DI functions. + for (int32 i = 0; i < ScriptExecutableData.DataInterfaceInfo.Num(); i++) + { + const FNiagaraScriptDataInterfaceCompileInfo& ScriptDIInfo = ScriptExecutableData.DataInterfaceInfo[i]; + UNiagaraDataInterface* ScriptInterface = ScriptDataInterfaces[i]; + UNiagaraDataInterface* ExternalInterface = GetDataInterfaces()[i]; + + if (ScriptDIInfo.Name == BindingInfo.OwnerName) + { + //Currently we must assume that any User DI is overridden but maybe we can be less conservative with this in future. + if (ScriptDIInfo.NeedsPerInstanceBinding()) + { + //This DI needs a binding per instance so we just bind to the external function hook which will call the correct binding for each instance. + auto PerInstFunctionHookLambda = [ExecContext = this, NumPerInstanceFunctions, UserPtrIndex = ScriptDIInfo.UserPtrIdx](FVectorVMContext& Context) + { + ExecContext->PerInstanceFunctionHook(Context, NumPerInstanceFunctions, UserPtrIndex); + }; + + ++NumPerInstanceFunctions; + FuncInfo.Function = FVMExternalFunction::CreateLambda(PerInstFunctionHookLambda); + } + else + { + // first check to see if we should just use the one from the script + if (ScriptExecutableData.CalledVMExternalFunctionBindings.IsValidIndex(FunctionIndex) + && ScriptInterface + && ExternalInterface == ScriptDataInterfaces[i]) + { + const FVMExternalFunction& ScriptFuncBind = ScriptExecutableData.CalledVMExternalFunctionBindings[FunctionIndex]; + if (ScriptFuncBind.IsBound()) + { + FuncInfo.Function = ScriptFuncBind; + check(ScriptDIInfo.UserPtrIdx == INDEX_NONE); + break; + } + } + + //If we don't need a call per instance we can just bind directly to the DI function call; + check(ExternalInterface); + ExternalInterface->GetVMExternalFunction(BindingInfo, nullptr, FuncInfo.Function); + } + break; + } + } + + for (int32 FunctionIt = 0; FunctionIt < FunctionCount; ++FunctionIt) + { + FunctionTable[FunctionIt] = &ExtFunctionInfo[FunctionIt].Function; + } + + if (FuncInfo.Function.IsBound() == false) + { + UE_LOG(LogNiagara, Warning, TEXT("Error building data interface function table for system script!")); + FunctionTable.Empty(); + return false; + } + } } } - //Can maybe do without resetting here. Just doing it for tidiness. - for (int32 DataSetIdx = 0; DataSetIdx < DataSetInfo.Num(); ++DataSetIdx) + Parameters.Tick(); + + return true; +} + +bool FNiagaraSystemScriptExecutionContext::GeneratePerInstanceDIFunctionTable(FNiagaraSystemInstance* Inst, TArray& OutFunctions) +{ + const FNiagaraScriptExecutionParameterStore* ScriptParameterStore = Script->GetExecutionReadyParameterStore(ENiagaraSimTarget::CPUSim); + const auto& ScriptDataInterfaces = ScriptParameterStore->GetDataInterfaces(); + const FNiagaraVMExecutableData& ScriptExecutableData = Script->GetVMExecutableData(); + + for (int32 FunctionIndex = 0; FunctionIndex < ScriptExecutableData.CalledVMExternalFunctions.Num(); ++FunctionIndex) { - DataSetInfo[DataSetIdx].Reset(); - DataSetMetaTable[DataSetIdx].Reset(); + const FVMExternalFunctionBindingInfo& BindingInfo = ScriptExecutableData.CalledVMExternalFunctions[FunctionIndex]; + + for (int32 i = 0; i < ScriptExecutableData.DataInterfaceInfo.Num(); i++) + { + const FNiagaraScriptDataInterfaceCompileInfo& ScriptDIInfo = ScriptExecutableData.DataInterfaceInfo[i]; + //UNiagaraDataInterface* ScriptInterface = ScriptDataInterfaces[i]; + UNiagaraDataInterface* ExternalInterface = GetDataInterfaces()[i]; + + if (ScriptDIInfo.Name == BindingInfo.OwnerName && ScriptDIInfo.NeedsPerInstanceBinding()) + { + UNiagaraDataInterface* DIToBind = nullptr; + FNiagaraPerInstanceDIFuncInfo& NewFuncInfo = OutFunctions.AddDefaulted_GetRef(); + void* InstData = nullptr; + + if (const int32* DIIndex = Inst->GetInstanceParameters().FindParameterOffset(FNiagaraVariable(ScriptDIInfo.Type, ScriptDIInfo.Name))) + { + //If this is a User DI we bind to the user DI and find instance data with it. + if (UNiagaraDataInterface* UserInterface = Inst->GetInstanceParameters().GetDataInterface(*DIIndex)) + { + DIToBind = UserInterface; + InstData = Inst->FindDataInterfaceInstanceData(UserInterface); + } + } + else + { + //Otherwise we use the script DI and search for instance data with that. + DIToBind = ExternalInterface; + InstData = Inst->FindDataInterfaceInstanceData(ExternalInterface); + } + + if (DIToBind) + { + check(ExternalInterface->PerInstanceDataSize() == 0 || InstData); + DIToBind->GetVMExternalFunction(BindingInfo, InstData, NewFuncInfo.Function); + NewFuncInfo.InstData = InstData; + } + + if (NewFuncInfo.Function.IsBound() == false) + { + return false; + } + break; + } + } } + return true; +}; - return true;//TODO: Error cases? -} - -void FNiagaraScriptExecutionContext::DirtyDataInterfaces() -{ - Parameters.MarkInterfacesDirty(); -} - -bool FNiagaraScriptExecutionContext::CanExecute()const -{ - return Script && Script->GetVMExecutableData().IsValid() && Script->GetVMExecutableData().ByteCode.Num() > 0; -} - -TArrayView FNiagaraScriptExecutionContext::GetScriptLiterals() const -{ -#if WITH_EDITORONLY_DATA - return Parameters.GetScriptLiterals(); -#else - return MakeArrayView(Script->GetVMExecutableData().ScriptLiterals); -#endif -} +////////////////////////////////////////////////////////////////////////// void FNiagaraGPUSystemTick::Init(FNiagaraSystemInstance* InSystemInstance) { @@ -581,7 +796,6 @@ FNiagaraComputeExecutionContext::FNiagaraComputeExecutionContext() : MainDataSet(nullptr) , GPUScript(nullptr) , GPUScript_RT(nullptr) - , DataToRender(nullptr) , TranslucentDataToRender(nullptr) { ExternalCBufferLayout = new FNiagaraRHIUniformBufferLayout(TEXT("Niagara GPU External CBuffer")); @@ -617,6 +831,29 @@ void FNiagaraComputeExecutionContext::Reset(NiagaraEmitterInstanceBatcher* Batch ); } +void FNiagaraComputeExecutionContext::BakeVariableNamesForIterationLookup() +{ + // We need to store the name of each DI source variable here so that we can look it up later when looking for the + // iteration interface. + TArray Params; + CombinedParamStore.GetParameters(Params); + for (FNiagaraVariable& Var : Params) + { + if (!Var.IsDataInterface()) + continue; + + UNiagaraDataInterface* DI = CombinedParamStore.GetDataInterface(Var); + if (DI) + { + FNiagaraDataInterfaceProxy* Proxy = DI->GetProxy(); + if (Proxy) + { + Proxy->SourceDIName = Var.GetName(); + } + } + } +} + void FNiagaraComputeExecutionContext::InitParams(UNiagaraScript* InGPUComputeScript, ENiagaraSimTarget InSimTarget, const uint32 InDefaultSimulationStageIndex, const int32 InMaxUpdateIterations, const TSet InSpawnStages) { GPUScript = InGPUComputeScript; @@ -634,7 +871,6 @@ void FNiagaraComputeExecutionContext::InitParams(UNiagaraScript* InGPUComputeScr FNiagaraVMExecutableData& VMData = InGPUComputeScript->GetVMExecutableData(); if (VMData.IsValid() && VMData.SimulationStageMetaData.Num() > 0) { - SimStageInfo = VMData.SimulationStageMetaData; int32 FoundMaxUpdateIterations = SimStageInfo[SimStageInfo.Num() - 1].MaxStage; @@ -713,31 +949,14 @@ void FNiagaraComputeExecutionContext::InitParams(UNiagaraScript* InGPUComputeScr } } - // We need to store the name of each DI source variable here so that we can look it up later when looking for the - // iteration interface. - TArray Params; - CombinedParamStore.GetParameters(Params); - for (FNiagaraVariable& Var : Params) - { - if (!Var.IsDataInterface()) - continue; - - UNiagaraDataInterface* DI = CombinedParamStore.GetDataInterface(Var); - if (DI) - { - FNiagaraDataInterfaceProxy* Proxy = DI->GetProxy(); - if (Proxy) - { - Proxy->SourceDIName = Var.GetName(); - } - } - } + } } #if DO_CHECK - FNiagaraShaderRef Shader = InGPUComputeScript->GetRenderThreadScript()->GetShaderGameThread(); + // DI Parameters are the same between all shader permutations so we can just get the first one + FNiagaraShaderRef Shader = InGPUComputeScript->GetRenderThreadScript()->GetShaderGameThread(0); if (Shader.IsValid()) { DIClassNames.Empty(Shader->GetDIParameters().Num()); @@ -915,21 +1134,35 @@ void FNiagaraComputeExecutionContext::SetDataToRender(FNiagaraDataBuffer* InData DataToRender = InDataToRender; - if (DataToRender) { DataToRender->AddReadRef(); } - // Clear out translucent data to render as we should be equal now + // This call the DataToRender should be equal to the TranslucentDataToRender so we can release the read ref if (TranslucentDataToRender) { - ensure(DataToRender == TranslucentDataToRender); + ensure((DataToRender == nullptr) || (DataToRender == TranslucentDataToRender)); TranslucentDataToRender->ReleaseReadRef(); TranslucentDataToRender = nullptr; } } +void FNiagaraComputeExecutionContext::SetTranslucentDataToRender(FNiagaraDataBuffer* InTranslucentDataToRender) +{ + if (TranslucentDataToRender) + { + TranslucentDataToRender->ReleaseReadRef(); + } + + TranslucentDataToRender = InTranslucentDataToRender; + + if (TranslucentDataToRender) + { + TranslucentDataToRender->AddReadRef(); + } +} + bool FNiagaraComputeInstanceData::IsOutputStage(FNiagaraDataInterfaceProxy* DIProxy, uint32 CurrentStage) const { if (bUsesOldShaderStages) @@ -986,18 +1219,3 @@ FNiagaraDataInterfaceProxy* FNiagaraComputeInstanceData::FindIterationInterface( } return nullptr; } - -void FNiagaraComputeExecutionContext::SetTranslucentDataToRender(FNiagaraDataBuffer* InDataToRender) -{ - if (TranslucentDataToRender) - { - TranslucentDataToRender->ReleaseReadRef(); - } - - TranslucentDataToRender = InDataToRender; - - if (TranslucentDataToRender) - { - TranslucentDataToRender->AddReadRef(); - } -} diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSettings.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSettings.cpp index a59b5142cf30..a764db1ea402 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSettings.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSettings.cpp @@ -33,7 +33,7 @@ void UNiagaraSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyCha { if (PropertyChangedEvent.Property != nullptr) { - SettingsChangedDelegate.Broadcast(PropertyChangedEvent.Property->GetName(), this); + SettingsChangedDelegate.Broadcast(PropertyChangedEvent.Property->GetFName(), this); } DefaultEffectTypePtr = Cast(DefaultEffectType.TryLoad()); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSimulationStageBase.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSimulationStageBase.cpp index 24c21a87bddd..90ad3d15fdb9 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSimulationStageBase.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSimulationStageBase.cpp @@ -5,21 +5,69 @@ #include "NiagaraSystem.h" #include "NiagaraScriptSourceBase.h" +bool UNiagaraSimulationStageBase::AppendCompileHash(FNiagaraCompileHashVisitor* InVisitor) const +{ +#if WITH_EDITORONLY_DATA + const int32 Index = InVisitor->Values.AddDefaulted(); + InVisitor->Values[Index].Object = FString::Printf(TEXT("Class: \"%s\" Name: \"%s\""), *GetClass()->GetName(), *GetName()); +#endif + InVisitor->UpdatePOD(TEXT("Enabled"), bEnabled ? 1 : 0); + return true; +} + +#if WITH_EDITOR +void UNiagaraSimulationStageBase::SetEnabled(bool bInEnabled) +{ + if (bEnabled != bInEnabled) + { + bEnabled = bInEnabled; + RequestRecompile(); + } +} + +void UNiagaraSimulationStageBase::RequestRecompile() +{ + UNiagaraEmitter* Emitter = Cast< UNiagaraEmitter>(GetOuter()); + if (Emitter) + { + UNiagaraScriptSourceBase* GraphSource = Emitter->UpdateScriptProps.Script->GetSource(); + if (GraphSource != nullptr) + { + GraphSource->MarkNotSynchronized(TEXT("SimulationStage changed.")); + } + + UNiagaraSystem::RequestCompileForEmitter(Emitter); + } +} + +void UNiagaraSimulationStageBase::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) +{ + FName PropertyName; + if (PropertyChangedEvent.Property) + { + PropertyName = PropertyChangedEvent.Property->GetFName(); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSimulationStageBase, bEnabled)) + { + RequestRecompile(); + } +} +#endif bool UNiagaraSimulationStageGeneric::AppendCompileHash(FNiagaraCompileHashVisitor* InVisitor) const { -#if WITH_EDITORONLY_DATA - int32 Index = InVisitor->Values.AddDefaulted(); - InVisitor->Values[Index].Object = FString::Printf(TEXT("Class: \"%s\" Name: \"%s\""), *GetClass()->GetName(), *GetName()); -#endif + Super::AppendCompileHash(InVisitor); InVisitor->UpdatePOD(TEXT("Iterations"), Iterations); InVisitor->UpdatePOD(TEXT("IterationSource"), (int32)IterationSource); InVisitor->UpdatePOD(TEXT("bSpawnOnly"), bSpawnOnly ? 1 : 0); + InVisitor->UpdatePOD(TEXT("bPartialParticleUpdate"), bPartialParticleUpdate ? 1 : 0); InVisitor->UpdateString(TEXT("DataInterface"), DataInterface.BoundVariable.GetName().ToString()); InVisitor->UpdateString(TEXT("SimulationStageName"), SimulationStageName.ToString()); return true; } + #if WITH_EDITOR void UNiagaraSimulationStageGeneric::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { @@ -31,41 +79,35 @@ void UNiagaraSimulationStageGeneric::PostEditChangeProperty(struct FPropertyChan PropertyName = PropertyChangedEvent.Property->GetFName(); } - UNiagaraEmitter* Emitter = Cast< UNiagaraEmitter>(GetOuter()); - bool bNeedsRecompile = false; - if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSimulationStageGeneric, Iterations) && Emitter) + if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSimulationStageGeneric, Iterations)) { bNeedsRecompile = true; } - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSimulationStageGeneric, IterationSource) && Emitter) + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSimulationStageGeneric, IterationSource)) { bNeedsRecompile = true; } - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSimulationStageGeneric, bSpawnOnly) && Emitter) + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSimulationStageGeneric, bSpawnOnly)) { bNeedsRecompile = true; } - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSimulationStageGeneric, DataInterface) && Emitter) + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSimulationStageGeneric, bPartialParticleUpdate)) { bNeedsRecompile = true; } - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSimulationStageGeneric, SimulationStageName) && Emitter) + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSimulationStageGeneric, DataInterface)) + { + bNeedsRecompile = true; + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSimulationStageGeneric, SimulationStageName)) { bNeedsRecompile = true; } -#if WITH_EDITORONLY_DATA if (bNeedsRecompile) { - UNiagaraScriptSourceBase* GraphSource = Emitter->UpdateScriptProps.Script->GetSource(); - if (GraphSource != nullptr) - { - GraphSource->MarkNotSynchronized(TEXT("SimulationStageGeneric changed.")); - } - - UNiagaraSystem::RequestCompileForEmitter(Emitter); + RequestRecompile(); } -#endif } #endif \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSpriteRendererProperties.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSpriteRendererProperties.cpp index 2fe1e8c8c73b..5775b30a0213 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSpriteRendererProperties.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSpriteRendererProperties.cpp @@ -6,13 +6,14 @@ #include "NiagaraConstants.h" #include "NiagaraRendererSprites.h" #include "NiagaraBoundsCalculatorHelper.h" +#include "NiagaraCustomVersion.h" #include "Modules/ModuleManager.h" + #if WITH_EDITOR #include "DerivedDataCacheInterface.h" #include "Widgets/Images/SImage.h" #include "Styling/SlateIconFinder.h" #include "Widgets/SWidget.h" -#include "Styling/SlateBrush.h" #include "AssetThumbnail.h" #include "Widgets/Text/STextBlock.h" #endif @@ -45,6 +46,7 @@ UNiagaraSpriteRendererProperties::UNiagaraSpriteRendererProperties() , bSubImageBlend(false) , bRemoveHMDRollInVR(false) , bSortOnlyWhenTranslucent(true) + , bGpuLowLatencyTranslucency(true) , MinFacingCameraBlendDistance(0.0f) , MaxFacingCameraBlendDistance(0.0f) #if WITH_EDITORONLY_DATA @@ -75,10 +77,10 @@ UNiagaraSpriteRendererProperties::UNiagaraSpriteRendererProperties() AttributeBindings.Add(&NormalizedAgeBinding); } -FNiagaraRenderer* UNiagaraSpriteRendererProperties::CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter) +FNiagaraRenderer* UNiagaraSpriteRendererProperties::CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) { FNiagaraRenderer* NewRenderer = new FNiagaraRendererSprites(FeatureLevel, this, Emitter); - NewRenderer->Initialize(this, Emitter); + NewRenderer->Initialize(this, Emitter, InComponent); return NewRenderer; } @@ -118,6 +120,7 @@ void UNiagaraSpriteRendererProperties::PostLoad() } CacheDerivedData(); } + PostLoadBindings(SourceMode); #endif // WITH_EDITORONLY_DATA } @@ -179,7 +182,7 @@ void UNiagaraSpriteRendererProperties::InitCDOPropertiesAfterModuleStartup() void UNiagaraSpriteRendererProperties::InitBindings() { - if (PositionBinding.BoundVariable.GetName() == NAME_None) + if (PositionBinding.GetParamMapBindableVariable().GetName() == NAME_None) { PositionBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_POSITION); ColorBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_COLOR); @@ -205,48 +208,123 @@ void UNiagaraSpriteRendererProperties::InitBindings() void UNiagaraSpriteRendererProperties::CacheFromCompiledData(const FNiagaraDataSetCompiledData* CompiledData) { + UpdateSourceModeDerivates(SourceMode); + RendererLayoutWithCustomSort.Initialize(ENiagaraSpriteVFLayout::Num); - RendererLayoutWithCustomSort.SetVariable(CompiledData, PositionBinding.DataSetVariable, ENiagaraSpriteVFLayout::Position); - RendererLayoutWithCustomSort.SetVariable(CompiledData, VelocityBinding.DataSetVariable, ENiagaraSpriteVFLayout::Velocity); - RendererLayoutWithCustomSort.SetVariable(CompiledData, ColorBinding.DataSetVariable, ENiagaraSpriteVFLayout::Color); - RendererLayoutWithCustomSort.SetVariable(CompiledData, SpriteRotationBinding.DataSetVariable, ENiagaraSpriteVFLayout::Rotation); - RendererLayoutWithCustomSort.SetVariable(CompiledData, SpriteSizeBinding.DataSetVariable, ENiagaraSpriteVFLayout::Size); - RendererLayoutWithCustomSort.SetVariable(CompiledData, SpriteFacingBinding.DataSetVariable, ENiagaraSpriteVFLayout::Facing); - RendererLayoutWithCustomSort.SetVariable(CompiledData, SpriteAlignmentBinding.DataSetVariable, ENiagaraSpriteVFLayout::Alignment); - RendererLayoutWithCustomSort.SetVariable(CompiledData, SubImageIndexBinding.DataSetVariable, ENiagaraSpriteVFLayout::SubImage); - RendererLayoutWithCustomSort.SetVariable(CompiledData, CameraOffsetBinding.DataSetVariable, ENiagaraSpriteVFLayout::CameraOffset); - RendererLayoutWithCustomSort.SetVariable(CompiledData, UVScaleBinding.DataSetVariable, ENiagaraSpriteVFLayout::UVScale); - RendererLayoutWithCustomSort.SetVariable(CompiledData, NormalizedAgeBinding.DataSetVariable, ENiagaraSpriteVFLayout::NormalizedAge); - RendererLayoutWithCustomSort.SetVariable(CompiledData, MaterialRandomBinding.DataSetVariable, ENiagaraSpriteVFLayout::MaterialRandom); - RendererLayoutWithCustomSort.SetVariable(CompiledData, CustomSortingBinding.DataSetVariable, ENiagaraSpriteVFLayout::CustomSorting); - MaterialParamValidMask = RendererLayoutWithCustomSort.SetVariable(CompiledData, DynamicMaterialBinding.DataSetVariable, ENiagaraSpriteVFLayout::MaterialParam0) ? 0x1 : 0; - MaterialParamValidMask |= RendererLayoutWithCustomSort.SetVariable(CompiledData, DynamicMaterial1Binding.DataSetVariable, ENiagaraSpriteVFLayout::MaterialParam1) ? 0x2 : 0; - MaterialParamValidMask |= RendererLayoutWithCustomSort.SetVariable(CompiledData, DynamicMaterial2Binding.DataSetVariable, ENiagaraSpriteVFLayout::MaterialParam2) ? 0x4 : 0; - MaterialParamValidMask |= RendererLayoutWithCustomSort.SetVariable(CompiledData, DynamicMaterial3Binding.DataSetVariable, ENiagaraSpriteVFLayout::MaterialParam3) ? 0x8 : 0; + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, PositionBinding, ENiagaraSpriteVFLayout::Position); + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, VelocityBinding, ENiagaraSpriteVFLayout::Velocity); + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, ColorBinding, ENiagaraSpriteVFLayout::Color); + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, SpriteRotationBinding, ENiagaraSpriteVFLayout::Rotation); + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, SpriteSizeBinding, ENiagaraSpriteVFLayout::Size); + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, SpriteFacingBinding, ENiagaraSpriteVFLayout::Facing); + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, SpriteAlignmentBinding, ENiagaraSpriteVFLayout::Alignment); + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, SubImageIndexBinding, ENiagaraSpriteVFLayout::SubImage); + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, CameraOffsetBinding, ENiagaraSpriteVFLayout::CameraOffset); + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, UVScaleBinding, ENiagaraSpriteVFLayout::UVScale); + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, NormalizedAgeBinding, ENiagaraSpriteVFLayout::NormalizedAge); + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, MaterialRandomBinding, ENiagaraSpriteVFLayout::MaterialRandom); + RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, CustomSortingBinding, ENiagaraSpriteVFLayout::CustomSorting); + MaterialParamValidMask = RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, DynamicMaterialBinding, ENiagaraSpriteVFLayout::MaterialParam0) ? 0x1 : 0; + MaterialParamValidMask |= RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, DynamicMaterial1Binding, ENiagaraSpriteVFLayout::MaterialParam1) ? 0x2 : 0; + MaterialParamValidMask |= RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, DynamicMaterial2Binding, ENiagaraSpriteVFLayout::MaterialParam2) ? 0x4 : 0; + MaterialParamValidMask |= RendererLayoutWithCustomSort.SetVariableFromBinding(CompiledData, DynamicMaterial3Binding, ENiagaraSpriteVFLayout::MaterialParam3) ? 0x8 : 0; RendererLayoutWithCustomSort.Finalize(); RendererLayoutWithoutCustomSort.Initialize(ENiagaraSpriteVFLayout::Num); - RendererLayoutWithoutCustomSort.SetVariable(CompiledData, PositionBinding.DataSetVariable, ENiagaraSpriteVFLayout::Position); - RendererLayoutWithoutCustomSort.SetVariable(CompiledData, VelocityBinding.DataSetVariable, ENiagaraSpriteVFLayout::Velocity); - RendererLayoutWithoutCustomSort.SetVariable(CompiledData, ColorBinding.DataSetVariable, ENiagaraSpriteVFLayout::Color); - RendererLayoutWithoutCustomSort.SetVariable(CompiledData, SpriteRotationBinding.DataSetVariable, ENiagaraSpriteVFLayout::Rotation); - RendererLayoutWithoutCustomSort.SetVariable(CompiledData, SpriteSizeBinding.DataSetVariable, ENiagaraSpriteVFLayout::Size); - RendererLayoutWithoutCustomSort.SetVariable(CompiledData, SpriteFacingBinding.DataSetVariable, ENiagaraSpriteVFLayout::Facing); - RendererLayoutWithoutCustomSort.SetVariable(CompiledData, SpriteAlignmentBinding.DataSetVariable, ENiagaraSpriteVFLayout::Alignment); - RendererLayoutWithoutCustomSort.SetVariable(CompiledData, SubImageIndexBinding.DataSetVariable, ENiagaraSpriteVFLayout::SubImage); - RendererLayoutWithoutCustomSort.SetVariable(CompiledData, CameraOffsetBinding.DataSetVariable, ENiagaraSpriteVFLayout::CameraOffset); - RendererLayoutWithoutCustomSort.SetVariable(CompiledData, UVScaleBinding.DataSetVariable, ENiagaraSpriteVFLayout::UVScale); - RendererLayoutWithoutCustomSort.SetVariable(CompiledData, NormalizedAgeBinding.DataSetVariable, ENiagaraSpriteVFLayout::NormalizedAge); - RendererLayoutWithoutCustomSort.SetVariable(CompiledData, MaterialRandomBinding.DataSetVariable, ENiagaraSpriteVFLayout::MaterialRandom); - MaterialParamValidMask = RendererLayoutWithoutCustomSort.SetVariable(CompiledData, DynamicMaterialBinding.DataSetVariable, ENiagaraSpriteVFLayout::MaterialParam0) ? 0x1 : 0; - MaterialParamValidMask |= RendererLayoutWithoutCustomSort.SetVariable(CompiledData, DynamicMaterial1Binding.DataSetVariable, ENiagaraSpriteVFLayout::MaterialParam1) ? 0x2 : 0; - MaterialParamValidMask |= RendererLayoutWithoutCustomSort.SetVariable(CompiledData, DynamicMaterial2Binding.DataSetVariable, ENiagaraSpriteVFLayout::MaterialParam2) ? 0x4 : 0; - MaterialParamValidMask |= RendererLayoutWithoutCustomSort.SetVariable(CompiledData, DynamicMaterial3Binding.DataSetVariable, ENiagaraSpriteVFLayout::MaterialParam3) ? 0x8 : 0; + RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, PositionBinding, ENiagaraSpriteVFLayout::Position); + RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, VelocityBinding, ENiagaraSpriteVFLayout::Velocity); + RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, ColorBinding, ENiagaraSpriteVFLayout::Color); + RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, SpriteRotationBinding, ENiagaraSpriteVFLayout::Rotation); + RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, SpriteSizeBinding, ENiagaraSpriteVFLayout::Size); + RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, SpriteFacingBinding, ENiagaraSpriteVFLayout::Facing); + RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, SpriteAlignmentBinding, ENiagaraSpriteVFLayout::Alignment); + RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, SubImageIndexBinding, ENiagaraSpriteVFLayout::SubImage); + RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, CameraOffsetBinding, ENiagaraSpriteVFLayout::CameraOffset); + RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, UVScaleBinding, ENiagaraSpriteVFLayout::UVScale); + RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, NormalizedAgeBinding, ENiagaraSpriteVFLayout::NormalizedAge); + RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, MaterialRandomBinding, ENiagaraSpriteVFLayout::MaterialRandom); + MaterialParamValidMask = RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, DynamicMaterialBinding, ENiagaraSpriteVFLayout::MaterialParam0) ? 0x1 : 0; + MaterialParamValidMask |= RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, DynamicMaterial1Binding, ENiagaraSpriteVFLayout::MaterialParam1) ? 0x2 : 0; + MaterialParamValidMask |= RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, DynamicMaterial2Binding, ENiagaraSpriteVFLayout::MaterialParam2) ? 0x4 : 0; + MaterialParamValidMask |= RendererLayoutWithoutCustomSort.SetVariableFromBinding(CompiledData, DynamicMaterial3Binding, ENiagaraSpriteVFLayout::MaterialParam3) ? 0x8 : 0; RendererLayoutWithoutCustomSort.Finalize(); + } + +bool UNiagaraSpriteRendererProperties::PopulateRequiredBindings(FNiagaraParameterStore& InParameterStore) +{ + bool bAnyAdded = false; + + for (const FNiagaraVariableAttributeBinding* Binding : AttributeBindings) + { + if (Binding && Binding->CanBindToHostParameterMap()) + { + InParameterStore.AddParameter(Binding->GetParamMapBindableVariable(), false); + bAnyAdded = true; + } + } + + for (FNiagaraMaterialAttributeBinding& MaterialParamBinding : MaterialParameterBindings) + { + InParameterStore.AddParameter(MaterialParamBinding.GetParamMapBindableVariable(), false); + bAnyAdded = true; + } + + return bAnyAdded; +} + +#if WITH_EDITOR +void UNiagaraSpriteRendererProperties::RenameEmitter(const FName& InOldName, const UNiagaraEmitter* InRenamedEmitter) +{ + Modify(); + + for (const FNiagaraVariableAttributeBinding* Binding : AttributeBindings) + { + if (Binding) + { + // This is a little ugly, but otherwise GetBindingsArray needs a const/non-const version. + const_cast(Binding)->CacheValues(InRenamedEmitter, SourceMode); + } + } + + for (FNiagaraMaterialAttributeBinding& MaterialParamBinding : MaterialParameterBindings) + { + // TODO rename emitter vars + MaterialParamBinding.CacheValues(InRenamedEmitter); + } +} +#endif + #if WITH_EDITORONLY_DATA +bool UNiagaraSpriteRendererProperties::IsSupportedVariableForBinding(const FNiagaraVariableBase& InSourceForBinding, const FName& InTargetBindingName) const +{ + if ((SourceMode == ENiagaraRendererSourceDataMode::Particles && InSourceForBinding.IsInNameSpace(FNiagaraConstants::ParticleAttributeNamespace)) || + InSourceForBinding.IsInNameSpace(FNiagaraConstants::UserNamespace) || + InSourceForBinding.IsInNameSpace(FNiagaraConstants::SystemNamespace) || + InSourceForBinding.IsInNameSpace(FNiagaraConstants::EmitterNamespace)) + { + return true; + } + return false; +} + + +//#if WITH_EDITOR +//void UNiagaraSpriteRendererProperties::PostEditUndo() +//{ +// Super::PostEditUndo(); +// UpdateSourceModeDerivates(SourceMode); +//} +// +//void UNiagaraSpriteRendererProperties::PostEditUndo(TSharedPtr TransactionAnnotation) +//{ +// Super::PostEditUndo(TransactionAnnotation); +// UpdateSourceModeDerivates(SourceMode); +//} +//#endif + void UNiagaraSpriteRendererProperties::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { SubImageSize.X = FMath::Max(SubImageSize.X, 1.f); @@ -270,11 +348,25 @@ void UNiagaraSpriteRendererProperties::PostEditChangeProperty(struct FPropertyCh CacheDerivedData(); } } - + + // If changing the source mode, we may need to update many of our values. + if (PropertyChangedEvent.GetPropertyName() == TEXT("SourceMode")) + { + UpdateSourceModeDerivates(SourceMode, true); + } + else if (FStructProperty* StructProp = CastField(PropertyChangedEvent.Property)) + { + if (StructProp->Struct == FNiagaraVariableAttributeBinding::StaticStruct()) + { + UpdateSourceModeDerivates(SourceMode, true); + } + } + Super::PostEditChangeProperty(PropertyChangedEvent); } + const TArray& UNiagaraSpriteRendererProperties::GetOptionalAttributes() { static TArray Attrs; @@ -334,7 +426,7 @@ void UNiagaraSpriteRendererProperties::GetRendererFeedback(const UNiagaraEmitter { if (bUseMaterialCutoutTexture || CutoutTexture) { - if (InEmitter->SpawnScriptProps.Script->GetVMExecutableData().Attributes.Contains(UVScaleBinding.DataSetVariable)) + if (UVScaleBinding.DoesBindingExistOnSource()) { OutInfo.Add(LOCTEXT("SpriteRendererUVScaleWithCutout", "Cutouts will not be sized dynamically with UVScale variable. If scaling above 1.0, geometry may clip.")); } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystem.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystem.cpp index 811e85bf6ee5..d0c742b3f67e 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystem.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystem.cpp @@ -62,12 +62,21 @@ static FAutoConsoleVariableRef CVarLogDDCStatusForSystems( ECVF_Default ); +static float GNiagaraScalabiltiyMinumumMaxDistance = 1.0f; +static FAutoConsoleVariableRef CVarNiagaraScalabiltiyMinumumMaxDistance( + TEXT("fx.Niagara.Scalability.MinMaxDistance"), + GNiagaraScalabiltiyMinumumMaxDistance, + TEXT("Minimum value for Niagara's Max distance value. Primariy to prevent divide by zero issues and ensure a sensible distance value for sorted significance culling."), + ECVF_Default +); + ////////////////////////////////////////////////////////////////////////// UNiagaraSystem::UNiagaraSystem(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) #if WITH_EDITORONLY_DATA , bBakeOutRapidIterationOnCook(true) +, bUseShaderPermutations(true) , bTrimAttributes(false) , bTrimAttributesOnCook(true) #endif @@ -124,21 +133,6 @@ void UNiagaraSystem::PreSave(const class ITargetPlatform * TargetPlatform) #endif } -bool UNiagaraSystem::NeedsLoadForTargetPlatform(const ITargetPlatform* TargetPlatform)const -{ - bool bHasAnyEnabledEmitters = false; - for (const FNiagaraEmitterHandle& EmitterHandle : GetEmitterHandles()) - { - if (EmitterHandle.GetIsEnabled() && EmitterHandle.GetInstance()->Platforms.IsEnabledForPlatform(TargetPlatform->IniPlatformName())) - { - bHasAnyEnabledEmitters = true; - break; - } - } - - return bHasAnyEnabledEmitters; -} - #if WITH_EDITOR void UNiagaraSystem::BeginCacheForCookedPlatformData(const ITargetPlatform *TargetPlatform) { @@ -1150,7 +1144,8 @@ void UNiagaraSystem::UpdateDITickFlags() { for (FNiagaraScriptDataInterfaceCompileInfo& Info : Script->GetVMExecutableData().DataInterfaceInfo) { - if (Info.GetDefaultDataInterface()->HasPostSimulateTick()) + UNiagaraDataInterface* DefaultDataInterface = Info.GetDefaultDataInterface(); + if (DefaultDataInterface && DefaultDataInterface->HasPostSimulateTick()) { bHasDIsWithPostSimulateTick |= true; } @@ -1221,6 +1216,24 @@ bool UNiagaraSystem::IsValidInternal() const return true; } + +bool UNiagaraSystem::CanObtainEmitterAttribute(const FNiagaraVariableBase& InVarWithUniqueNameNamespace) const +{ + if (SystemSpawnScript) + return SystemSpawnScript->GetVMExecutableData().Attributes.Contains(InVarWithUniqueNameNamespace); + return false; +} +bool UNiagaraSystem::CanObtainSystemAttribute(const FNiagaraVariableBase& InVar) const +{ + if (SystemSpawnScript) + return SystemSpawnScript->GetVMExecutableData().Attributes.Contains(InVar); + return false; +} +bool UNiagaraSystem::CanObtainUserVariable(const FNiagaraVariableBase& InVar) const +{ + return ExposedParameters.IndexOf(InVar) != INDEX_NONE; +} + #if WITH_EDITORONLY_DATA FNiagaraEmitterHandle UNiagaraSystem::AddEmitterHandle(UNiagaraEmitter& InEmitter, FName EmitterName) @@ -2140,6 +2153,8 @@ void UNiagaraSystem::ResolveScalabilitySettings() break;//These overrides *should* be for orthogonal platform sets so we can exit after we've found a match. } } + + CurrentScalabilitySettings.MaxDistance = FMath::Max(GNiagaraScalabiltiyMinumumMaxDistance, CurrentScalabilitySettings.MaxDistance); } void UNiagaraSystem::OnQualityLevelChanged() @@ -2154,10 +2169,19 @@ void UNiagaraSystem::OnQualityLevelChanged() } } - FNiagaraSystemUpdateContext UpdateCtx; - UpdateCtx.SetDestroyOnAdd(true); - UpdateCtx.SetOnlyActive(true); - UpdateCtx.Add(this, true); + // Update components + { + FNiagaraSystemUpdateContext UpdateCtx; + UpdateCtx.SetDestroyOnAdd(true); + UpdateCtx.SetOnlyActive(true); + UpdateCtx.Add(this, true); + } + + // Re-prime the component pool + if (PoolPrimeSize > 0 && MaxPoolSize > 0) + { + FNiagaraWorldManager::PrimePoolForAllWorlds(this); + } } const FString& UNiagaraSystem::GetCrashReporterTag()const diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemInstance.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemInstance.cpp index 447440107575..71acb5954221 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemInstance.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemInstance.cpp @@ -15,7 +15,6 @@ #include "GameFramework/PlayerController.h" #include "NiagaraCrashReporterHandler.h" #include "Async/Async.h" -#include "Algo/RemoveIf.h" DECLARE_CYCLE_STAT(TEXT("System Activate [GT]"), STAT_NiagaraSystemActivate, STATGROUP_Niagara); @@ -95,13 +94,20 @@ static FAutoConsoleVariableRef CVarNiagaraAllowDeferredReset( ECVF_Default ); -FNiagaraSystemInstance::FNiagaraSystemInstance(UNiagaraComponent* InComponent) +FNiagaraSystemInstance::FNiagaraSystemInstance(UWorld& InWorld, UNiagaraSystem& InAsset, FNiagaraUserRedirectionParameterStore* InOverrideParameters, + USceneComponent* InAttachComponent, ENiagaraTickBehavior InTickBehavior, bool bInPooled) : SystemInstanceIndex(INDEX_NONE) - , Component(InComponent) + , World(&InWorld) + , Asset(&InAsset) + , OverrideParameters(InOverrideParameters) + , AttachComponent(InAttachComponent) , PrereqComponent(nullptr) - , TickBehavior(InComponent ? InComponent->GetTickBehavior() : ENiagaraTickBehavior::UsePrereqs) + , TickBehavior(InTickBehavior) , Age(0.0f) + , LastRenderTime(0.0f) , TickCount(0) + , LODDistance(0.0f) + , MaxLODDistance(FLT_MAX) , CurrentFrameIndex(1) , ParametersValid(false) , bSolo(false) @@ -109,11 +115,11 @@ FNiagaraSystemInstance::FNiagaraSystemInstance(UNiagaraComponent* InComponent) , bPendingSpawn(false) , bPaused(false) , bDataInterfacesHaveTickPrereqs(false) - , bIsTransformDirty(true) , bNeedsFinalize(false) , bDataInterfacesInitialized(false) , bAlreadyBound(false) , bLODDistanceIsValid(false) + , bPooled(bInPooled) , bAsyncWorkInProgress(false) , CachedDeltaSeconds(0.0f) , RequestedExecutionState(ENiagaraExecutionState::Complete) @@ -124,29 +130,25 @@ FNiagaraSystemInstance::FNiagaraSystemInstance(UNiagaraComponent* InComponent) ID = IDCounter.IncrementExchange(); LocalBounds = FBox(FVector::ZeroVector, FVector::ZeroVector); - InstanceParameters.SetOwner(Component); - - LODDistance = 0.0f; - - if (Component) + if (InAttachComponent) { - UWorld* World = Component->GetWorld(); - if (World && World->Scene) + InstanceParameters.SetOwner(InAttachComponent); + } + + if (World->Scene) + { + FFXSystemInterface* FXSystemInterface = World->Scene->GetFXSystem(); + if (FXSystemInterface) { - FFXSystemInterface* FXSystemInterface = World->Scene->GetFXSystem(); - if (FXSystemInterface) - { - Batcher = static_cast(FXSystemInterface->GetInterface(NiagaraEmitterInstanceBatcher::Name)); - } - FeatureLevel = World->FeatureLevel; + Batcher = static_cast(FXSystemInterface->GetInterface(NiagaraEmitterInstanceBatcher::Name)); } + FeatureLevel = World->FeatureLevel; + } - // In some cases the system may have already stated that you should ignore dependencies and tick as early as possible. - ENiagaraTickBehavior SystemTickBehavior = TickBehavior; - if (!Component->GetAsset()->bRequireCurrentFrameData) - { - TickBehavior = ENiagaraTickBehavior::ForceTickFirst; - } + // In some cases the system may have already stated that you should ignore dependencies and tick as early as possible. + if (!InAsset.bRequireCurrentFrameData) + { + TickBehavior = ENiagaraTickBehavior::ForceTickFirst; } } @@ -214,10 +216,6 @@ void FNiagaraSystemInstance::Init(bool bInForceSolo) { // We warn if async is not complete here as we should never wait WaitForAsyncTickAndFinalize(true); - if (!ensureMsgf(Component != nullptr, TEXT("SystemInstance Component is nullptr during Init"))) - { - return; - } bForceSolo = bInForceSolo; ActualExecutionState = ENiagaraExecutionState::Inactive; @@ -353,12 +351,14 @@ bool FNiagaraSystemInstance::RequestCapture(const FGuid& RequestId) for (const FNiagaraEmitterHandle& Handle : GetSystem()->GetEmitterHandles()) { TArray Scripts; - if (Handle.GetInstance()) + if (Handle.GetInstance() && Handle.GetIsEnabled()) { Handle.GetInstance()->GetScripts(Scripts, false); for (UNiagaraScript* Script : Scripts) { + if (Script->IsGPUScript(Script->Usage) && Handle.GetInstance()->SimTarget == ENiagaraSimTarget::CPUSim) + continue; TSharedPtr DebugInfoPtr = MakeShared(Handle.GetIdName(), Script->GetUsage(), Script->GetUsageId()); DebugInfoPtr->bWritten = false; @@ -479,7 +479,7 @@ void FNiagaraSystemInstance::SetSolo(bool bInSolo) if (bInSolo) { TSharedPtr NewSoloSim = MakeShared(); - NewSoloSim->Init(System, Component->GetWorld(), true, TG_MAX); + NewSoloSim->Init(System, World, true, TG_MAX); NewSoloSim->TransferInstance(SystemSimulation.Get(), this); @@ -504,7 +504,15 @@ void FNiagaraSystemInstance::SetSolo(bool bInSolo) void FNiagaraSystemInstance::UpdatePrereqs() { - PrereqComponent = Component != nullptr ? Component->GetAttachParent() : nullptr; + PrereqComponent = AttachComponent.Get(); + + // This is to maintain legacy behavior (and perf benefit) of ticking in PrePhysics with unattached UNiagaraComponents that have no DI prereqs + // NOTE: This means that the system likely ticks with frame-behind transform if the component is moved, but likely doesn't manifest as an issue with local-space emitters + // TODO: Is there a better way to detect being able to tick early for these perf wins by default, even when not using a NiagaraComponent? + if (UNiagaraComponent* NiagaraComponent = Cast(PrereqComponent)) + { + PrereqComponent = NiagaraComponent->GetAttachParent(); + } } void FNiagaraSystemInstance::Activate(EResetMode InResetMode) @@ -514,7 +522,7 @@ void FNiagaraSystemInstance::Activate(EResetMode InResetMode) UNiagaraSystem* System = GetSystem(); if (System && System->IsValid() && IsReadyToRun()) { - if (GNiagaraAllowDeferredReset && (bAsyncWorkInProgress || bNeedsFinalize)) + if (GNiagaraAllowDeferredReset && (bAsyncWorkInProgress || bNeedsFinalize) && SystemInstanceIndex != INDEX_NONE) { DeferredResetMode = InResetMode; } @@ -524,10 +532,7 @@ void FNiagaraSystemInstance::Activate(EResetMode InResetMode) WaitForAsyncTickAndFinalize(); DeferredResetMode = EResetMode::None; - if (Component != nullptr) - { - Reset(InResetMode); - } + Reset(InResetMode); } } else @@ -559,9 +564,10 @@ void FNiagaraSystemInstance::Deactivate(bool bImmediate) } } -bool FNiagaraSystemInstance::AllocateSystemInstance(class UNiagaraComponent* InComponent, TUniquePtr< FNiagaraSystemInstance >& OutSystemInstanceAllocation) +bool FNiagaraSystemInstance::AllocateSystemInstance(TUniquePtr& OutSystemInstanceAllocation, UWorld& InWorld, UNiagaraSystem& InAsset, + FNiagaraUserRedirectionParameterStore* InOverrideParameters, USceneComponent* InAttachComponent, ENiagaraTickBehavior InTickBehavior, bool bInPooled) { - OutSystemInstanceAllocation = MakeUnique(InComponent); + OutSystemInstanceAllocation = MakeUnique(InWorld, InAsset, InOverrideParameters, InAttachComponent, InTickBehavior, bInPooled); return true; } @@ -595,7 +601,10 @@ bool FNiagaraSystemInstance::DeallocateSystemInstance(TUniquePtr< FNiagaraSystem FNiagaraWorldManager* WorldManager = SystemInstanceAllocation->GetWorldManager(); check(WorldManager != nullptr); - SystemInstanceAllocation->Component = nullptr; + // Make sure we abandon any external interface at this point + SystemInstanceAllocation->OverrideParameters = nullptr; + SystemInstanceAllocation->PrereqComponent = nullptr; + SystemInstanceAllocation->OnPostTickDelegate.Unbind(); WorldManager->DestroySystemInstance(SystemInstanceAllocation); check(SystemInstanceAllocation == nullptr); @@ -637,7 +646,7 @@ void FNiagaraSystemInstance::Complete() DestroyDataInterfaceInstanceData(); - if (!Component || Component->PoolingMethod == ENCPoolMethod::None) + if (!bPooled) { UnbindParameters(true); } @@ -652,17 +661,12 @@ void FNiagaraSystemInstance::Complete() #if WITH_EDITOR OnCompleteDelegate.Broadcast(this); #endif - - if (Component) - { - // Note: This call may destroy this instance of FNiagaraSystemInstance, so don't use bNotifyOnCompletion after it! - Component->OnSystemComplete(); - } } } -void FNiagaraSystemInstance::OnPooledReuse() +void FNiagaraSystemInstance::OnPooledReuse(UWorld& NewWorld) { + World = &NewWorld; for (auto&& Emitter : Emitters) { Emitter->OnPooledReuse(); @@ -715,12 +719,8 @@ void FNiagaraSystemInstance::Reset(FNiagaraSystemInstance::EResetMode Mode) // Wait for any async operations, can complete the system WaitForAsyncTickAndFinalize(); - if (Component == nullptr) - { - return; - } - Component->SetLastRenderTime(Component->GetWorld()->GetTimeSeconds()); + LastRenderTime = World->GetTimeSeconds(); SetPaused(false); @@ -808,13 +808,6 @@ void FNiagaraSystemInstance::Reset(FNiagaraSystemInstance::EResetMode Mode) TickCount = 0; } } - - if (Component) - { - // This system may not tick again immediately so we mark the render state dirty here so that - // the renderers will be reset this frame. - Component->MarkRenderDynamicDataDirty(); - } } else { @@ -841,20 +834,20 @@ void FNiagaraSystemInstance::ResetInternal(bool bResetSimulations) // Note: We do not need to update our bounds here as they are still valid UNiagaraSystem* System = GetSystem(); - if (System == nullptr || Component == nullptr || IsDisabled()) + if (System == nullptr || IsDisabled()) { return; } #if WITH_EDITOR - if (Component->GetWorld() != nullptr && Component->GetWorld()->WorldType == EWorldType::Editor) + check(World); + if (OverrideParameters && World->WorldType == EWorldType::Editor) { - Component->GetOverrideParameters().Tick(); + OverrideParameters->Tick(); } #endif bool bAllReadyToRun = IsReadyToRun(); - if (!bAllReadyToRun) { return; @@ -863,7 +856,7 @@ void FNiagaraSystemInstance::ResetInternal(bool bResetSimulations) if (!System->IsValid()) { SetRequestedExecutionState(ENiagaraExecutionState::Disabled); - UE_LOG(LogNiagara, Warning, TEXT("Failed to activate Niagara System due to invalid asset! System(%s) Component(%s)"), *System->GetName(), *Component->GetFullName()); + UE_LOG(LogNiagara, Warning, TEXT("Failed to activate Niagara System due to invalid asset! System(%s) Component(%s)"), *System->GetName(), *GetFullNameSafe(AttachComponent.Get())); return; } @@ -902,7 +895,7 @@ void FNiagaraSystemInstance::AdvanceSimulation(int32 TickCountToSimulate, float { //Cannot do multiple tick off the game thread here without additional work. So we pass in null for the completion event which will force GT execution. //If this becomes a perf problem I can add a new path for the tick code to handle multiple ticks. - ComponentTick(TickDeltaSeconds, nullptr); + ManualTick(TickDeltaSeconds, nullptr); } SetSolo(bWasSolo); } @@ -929,27 +922,32 @@ bool FNiagaraSystemInstance::IsReadyToRun() const return bAllReadyToRun; } -bool DoSystemDataInterfacesRequireSolo(const UNiagaraSystem& System, const UNiagaraComponent& Component) +bool DoSystemDataInterfacesRequireSolo(const UNiagaraSystem& System, const FNiagaraUserRedirectionParameterStore* OverrideParameters) { - if (System.HasSystemScriptDIsWithPerInstanceData()) + if (FNiagaraSystemSimulation::UseLegacySystemSimulationContexts()) { - return true; - } - - const TArray& UserDINamesReadInSystemScripts = System.GetUserDINamesReadInSystemScripts(); - if (UserDINamesReadInSystemScripts.Num() > 0) - { - TArray OverrideParameterVariables; - Component.GetOverrideParameters().GetParameters(OverrideParameterVariables); - for (const FNiagaraVariable& OverrideParameterVariable : OverrideParameterVariables) + if (System.HasSystemScriptDIsWithPerInstanceData()) { - if (OverrideParameterVariable.IsDataInterface() && UserDINamesReadInSystemScripts.Contains(OverrideParameterVariable.GetName())) + return true; + } + + const TArray& UserDINamesReadInSystemScripts = System.GetUserDINamesReadInSystemScripts(); + if (OverrideParameters != nullptr && UserDINamesReadInSystemScripts.Num() > 0) + { + TArray OverrideParameterVariables; + OverrideParameters->GetParameters(OverrideParameterVariables); + for (const FNiagaraVariable& OverrideParameterVariable : OverrideParameterVariables) { - return true; + if (OverrideParameterVariable.IsDataInterface() && UserDINamesReadInSystemScripts.Contains(OverrideParameterVariable.GetName())) + { + if (UserDINamesReadInSystemScripts.Contains(OverrideParameterVariable.GetName())) + { + return true; + } + } } } } - return false; } @@ -969,15 +967,12 @@ void FNiagaraSystemInstance::ReInitInternal() Age = 0; TickCount = 0; - bIsTransformDirty = true; - TimeSinceLastForceUpdateTransform = 0.0f; LocalBounds = FBox(FVector::ZeroVector, FVector::ZeroVector); CachedDeltaSeconds = 0.0f; - bAlreadyBound = false; UNiagaraSystem* System = GetSystem(); - if (System == nullptr || Component == nullptr) + if (System == nullptr) { return; } @@ -987,7 +982,6 @@ void FNiagaraSystemInstance::ReInitInternal() ActualExecutionState = ENiagaraExecutionState::Inactive; bool bAllReadyToRun = IsReadyToRun(); - if (!bAllReadyToRun) { return; @@ -996,18 +990,18 @@ void FNiagaraSystemInstance::ReInitInternal() if (!System->IsValid()) { SetRequestedExecutionState(ENiagaraExecutionState::Disabled); - UE_LOG(LogNiagara, Warning, TEXT("Failed to activate Niagara System due to invalid asset! System(%s) Component(%s)"), *System->GetName(), *Component->GetFullName()); + UE_LOG(LogNiagara, Warning, TEXT("Failed to activate Niagara System due to invalid asset! System(%s) Component(%s)"), *System->GetName(), *GetFullNameSafe(AttachComponent.Get())); return; } /** Do we need to run in solo mode? */ - bSolo = bForceSolo || DoSystemDataInterfacesRequireSolo(*System, *Component); + bSolo = bForceSolo || DoSystemDataInterfacesRequireSolo(*System, OverrideParameters); if (bSolo) { if (!SystemSimulation.IsValid()) { SystemSimulation = MakeShared(); - SystemSimulation->Init(System, Component->GetWorld(), true, TG_MAX); + SystemSimulation->Init(System, World, true, TG_MAX); } } else @@ -1030,9 +1024,6 @@ void FNiagaraSystemInstance::ReInitInternal() TickInstanceParameters_GameThread(0.01f); TickInstanceParameters_Concurrent(); - //Invalidate the component render state so we recreate the scene proxy and the renderers. - Component->MarkRenderStateDirty(); - #if WITH_EDITOR //UE_LOG(LogNiagara, Log, TEXT("OnResetInternal %p"), this); OnResetDelegate.Broadcast(); @@ -1118,23 +1109,21 @@ void FNiagaraSystemInstance::Cleanup() void FNiagaraSystemInstance::BindParameters() { - if (!Component) + if (OverrideParameters != nullptr) { - return; - } + if (!bAlreadyBound) + { + // NOTE: We don't rebind if it's already bound to improve reset times. + OverrideParameters->Bind(&InstanceParameters); + } - if (!bAlreadyBound) - { - // NOTE: We don't rebind if it's already bound to improve reset times. - Component->GetOverrideParameters().Bind(&InstanceParameters); - } - - if (SystemSimulation->GetIsSolo()) - { - // If this simulation is solo than we can bind the instance parameters to the system simulation contexts so that - // the system and emitter scripts use the per-instance data interfaces. - Component->GetOverrideParameters().Bind(&SystemSimulation->GetSpawnExecutionContext().Parameters); - Component->GetOverrideParameters().Bind(&SystemSimulation->GetUpdateExecutionContext().Parameters); + if (SystemSimulation->GetIsSolo() && FNiagaraSystemSimulation::UseLegacySystemSimulationContexts()) + { + // If this simulation is solo than we can bind the instance parameters to the system simulation contexts so that + // the system and emitter scripts use the per-instance data interfaces. + OverrideParameters->Bind(&SystemSimulation->GetSpawnExecutionContext()->Parameters); + OverrideParameters->Bind(&SystemSimulation->GetUpdateExecutionContext()->Parameters); + } } for (TSharedRef Simulation : Emitters) @@ -1147,21 +1136,18 @@ void FNiagaraSystemInstance::BindParameters() void FNiagaraSystemInstance::UnbindParameters(bool bFromComplete) { - if (Component && !bFromComplete) + if (OverrideParameters != nullptr) { - // NOTE: We don't unbind this on complete to improve reset times. - Component->GetOverrideParameters().Unbind(&InstanceParameters); - } - - if (SystemSimulation.IsValid()) - { - if (SystemSimulation->GetIsSolo()) + if (!bFromComplete) { - if (Component) - { - Component->GetOverrideParameters().Unbind(&SystemSimulation->GetSpawnExecutionContext().Parameters); - Component->GetOverrideParameters().Unbind(&SystemSimulation->GetUpdateExecutionContext().Parameters); - } + // NOTE: We don't unbind this on complete to improve reset times. + OverrideParameters->Unbind(&InstanceParameters); + } + + if (SystemSimulation.IsValid() && SystemSimulation->GetIsSolo()) + { + OverrideParameters->Unbind(&SystemSimulation->GetSpawnExecutionContext()->Parameters); + OverrideParameters->Unbind(&SystemSimulation->GetUpdateExecutionContext()->Parameters); } } @@ -1174,7 +1160,8 @@ void FNiagaraSystemInstance::UnbindParameters(bool bFromComplete) FNiagaraWorldManager* FNiagaraSystemInstance::GetWorldManager()const { - return Component ? FNiagaraWorldManager::Get(Component->GetWorld()) : nullptr; + check(World); + return FNiagaraWorldManager::Get(World); } bool FNiagaraSystemInstance::RequiresDistanceFieldData() const @@ -1256,9 +1243,8 @@ void FNiagaraSystemInstance::InitDataInterfaces() { bDataInterfacesHaveTickPrereqs = false; - // If either the System or the component is invalid, it is possible that our cached data interfaces - // are now bogus and could point to invalid memory. Only the UNiagaraComponent or UNiagaraSystem - // can hold onto GC references to the DataInterfaces. + // If the System is invalid, it is possible that our cached data interfaces are now bogus and could point to invalid memory. + // Only the UNiagaraComponent or UNiagaraSystem can hold onto GC references to the DataInterfaces. if (GetSystem() == nullptr || IsDisabled()) { return; @@ -1267,16 +1253,17 @@ void FNiagaraSystemInstance::InitDataInterfaces() // Wait for any async operations, can complete the system WaitForAsyncTickAndFinalize(true); - if (Component == nullptr) + if (OverrideParameters != nullptr) { - return; + OverrideParameters->Tick(); } - Component->GetOverrideParameters().Tick(); - //-TODO: Validate that any queued ticks have been executed DestroyDataInterfaceInstanceData(); + PerInstanceDIFunctions[(int32)ENiagaraSystemSimulationScript::Spawn].Reset(); + PerInstanceDIFunctions[(int32)ENiagaraSystemSimulationScript::Update].Reset(); + GPUDataInterfaceInstanceDataSize = 0; //Now the interfaces in the simulations are all correct, we can build the per instance data table. @@ -1316,13 +1303,18 @@ void FNiagaraSystemInstance::InitDataInterfaces() CalcInstDataSize(InstanceParameters.GetDataInterfaces());//This probably should be a proper exec context. - if (SystemSimulation->GetIsSolo()) + if (SystemSimulation->GetIsSolo() && FNiagaraSystemSimulation::UseLegacySystemSimulationContexts()) { - CalcInstDataSize(SystemSimulation->GetSpawnExecutionContext().GetDataInterfaces()); - SystemSimulation->GetSpawnExecutionContext().DirtyDataInterfaces(); + CalcInstDataSize(SystemSimulation->GetSpawnExecutionContext()->GetDataInterfaces()); + SystemSimulation->GetSpawnExecutionContext()->DirtyDataInterfaces(); - CalcInstDataSize(SystemSimulation->GetUpdateExecutionContext().GetDataInterfaces()); - SystemSimulation->GetUpdateExecutionContext().DirtyDataInterfaces(); + CalcInstDataSize(SystemSimulation->GetUpdateExecutionContext()->GetDataInterfaces()); + SystemSimulation->GetUpdateExecutionContext()->DirtyDataInterfaces(); + } + else + { + CalcInstDataSize(SystemSimulation->GetSpawnExecutionContext()->GetDataInterfaces()); + CalcInstDataSize(SystemSimulation->GetUpdateExecutionContext()->GetDataInterfaces()); } //Iterate over interfaces to get size for table and clear their interface bindings. @@ -1380,10 +1372,8 @@ void FNiagaraSystemInstance::InitDataInterfaces() bDataInterfacesInitialized &= bResult; if (!bResult) { - UE_LOG(LogNiagara, Error, TEXT("Error initializing data interface \"%s\" for system. %u | %s"), *Interface->GetPathName(), Component, *Component->GetAsset()->GetName()); + UE_LOG(LogNiagara, Error, TEXT("Error initializing data interface \"%s\" for system. %s"), *Interface->GetPathName(), Asset.IsValid() ? *Asset->GetName() : TEXT("nullptr")); } - - } else { @@ -1395,14 +1385,33 @@ void FNiagaraSystemInstance::InitDataInterfaces() if (!bDataInterfacesInitialized && (!IsComplete() && !IsPendingSpawn())) { //Some error initializing the data interfaces so disable until we're explicitly reinitialized. - UE_LOG(LogNiagara, Error, TEXT("Error initializing data interfaces. Completing system. %u | %s"), Component, *Component->GetAsset()->GetName()); + UE_LOG(LogNiagara, Error, TEXT("Error initializing data interfaces. Completing system. %s"), Asset.IsValid() ? *Asset->GetName() : TEXT("nullptr")); Complete(); + return; + } + + //We have valid DI instance data so now generate the table of function calls. + //When using the new exec contexts, each system instance builds it's own tables of DI function bindings for DI calls that require it. + //i.e. User DIs or those with per instance data that are called from system scripts. + if (FNiagaraSystemSimulation::UseLegacySystemSimulationContexts() == false) + { + bool bSuccess = true; + bSuccess &= SystemSimulation->GetSpawnExecutionContext()->GeneratePerInstanceDIFunctionTable(this, PerInstanceDIFunctions[(int32)ENiagaraSystemSimulationScript::Spawn]); + bSuccess &= SystemSimulation->GetUpdateExecutionContext()->GeneratePerInstanceDIFunctionTable(this, PerInstanceDIFunctions[(int32)ENiagaraSystemSimulationScript::Update]); + + if (!bSuccess) + { + //Some error initializing the per instance function tables. + UE_LOG(LogNiagara, Error, TEXT("Error initializing data interfaces. Completing system. %s"), Asset.IsValid() ? *Asset->GetName() : TEXT("nullptr")); + Complete(); + return; + } } } void FNiagaraSystemInstance::TickDataInterfaces(float DeltaSeconds, bool bPostSimulate) { - if (!GetSystem() || !Component || IsDisabled()) + if (!GetSystem() || IsDisabled()) { return; } @@ -1441,14 +1450,6 @@ void FNiagaraSystemInstance::TickDataInterfaces(float DeltaSeconds, bool bPostSi float FNiagaraSystemInstance::GetLODDistance() { - check(Component); -#if WITH_EDITOR - if (Component->bEnablePreviewLODDistance) - { - return Component->PreviewLODDistance; - } -#endif - //In most cases this will have been set externally by the scalability manager. if (bLODDistanceIsValid) { @@ -1458,14 +1459,13 @@ float FNiagaraSystemInstance::GetLODDistance() constexpr float DefaultLODDistance = 0.0f; FNiagaraWorldManager* WorldManager = GetWorldManager(); - if ( WorldManager == nullptr ) + if (WorldManager == nullptr) { return DefaultLODDistance; } - UWorld* World = Component->GetWorld(); check(World); - const FVector EffectLocation = Component->GetComponentLocation(); + const FVector EffectLocation = WorldTransform.GetLocation(); LODDistance = DefaultLODDistance; // If we are inside the WorldManager tick we will use the cache player view locations as we can be ticked on different threads @@ -1569,7 +1569,14 @@ ETickingGroup FNiagaraSystemInstance::CalculateTickGroup() const break; case ENiagaraTickBehavior::UseComponentTickGroup: - NewTickGroup = FMath::Clamp((ETickingGroup)Component->PrimaryComponentTick.TickGroup, NiagaraFirstTickGroup, NiagaraLastTickGroup); + if (USceneComponent* Component = AttachComponent.Get()) + { + NewTickGroup = FMath::Clamp((ETickingGroup)Component->PrimaryComponentTick.TickGroup, NiagaraFirstTickGroup, NiagaraLastTickGroup); + } + else + { + NewTickGroup = NiagaraFirstTickGroup; + } break; case ENiagaraTickBehavior::ForceTickFirst: @@ -1589,22 +1596,26 @@ ETickingGroup FNiagaraSystemInstance::CalculateTickGroup() const void FNiagaraSystemInstance::SetTickBehavior(ENiagaraTickBehavior NewTickBehavior) { - TickBehavior = NewTickBehavior; + UNiagaraSystem* System = GetSystem(); + if (!System || System->bRequireCurrentFrameData) + { + TickBehavior = NewTickBehavior; + } + else + { + // Tick as soon as possible + TickBehavior = ENiagaraTickBehavior::ForceTickFirst; + } } void FNiagaraSystemInstance::TickInstanceParameters_GameThread(float DeltaSeconds) { - static const auto EffectsQualityLevelCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("sg.EffectsQuality")); - - if (!Component) + // If we're associated with a scene component, update our cached transform (otherwise, assume it was previously set externally) + if (AttachComponent.IsValid()) { - return; + WorldTransform = AttachComponent->GetComponentToWorld(); } - - const int EffectsQualityLevel = EffectsQualityLevelCVar->GetInt(); - - const FTransform& ComponentTransform = Component->GetComponentTransform(); - const bool TransformMatches = GatheredInstanceParameters.ComponentTrans.Equals(ComponentTransform); + const bool TransformMatches = GatheredInstanceParameters.ComponentTrans.Equals(WorldTransform); if (TransformMatches) { // we want to update the transforms one more time than the buffer count because even if the transform buffers didn't change, @@ -1613,7 +1624,7 @@ void FNiagaraSystemInstance::TickInstanceParameters_GameThread(float DeltaSecond } else { - GatheredInstanceParameters.ComponentTrans = ComponentTransform; + GatheredInstanceParameters.ComponentTrans = WorldTransform; GatheredInstanceParameters.TransformMatchCount = 0; } @@ -1622,16 +1633,9 @@ void FNiagaraSystemInstance::TickInstanceParameters_GameThread(float DeltaSecond GatheredInstanceParameters.NumAlive = 0; //Bias the LastRenderTime slightly to account for any delay as it's written by the RT. - if (const UWorld* World = Component->GetWorld()) - { - GatheredInstanceParameters.TimeSeconds = World->TimeSeconds; - GatheredInstanceParameters.RealTimeSeconds = World->RealTimeSeconds; - } - else - { - GatheredInstanceParameters.TimeSeconds = Age; - GatheredInstanceParameters.RealTimeSeconds = Age; - } + check(World); + GatheredInstanceParameters.TimeSeconds = World->TimeSeconds; + GatheredInstanceParameters.RealTimeSeconds = World->RealTimeSeconds; // flip our buffered parameters FlipParameterBuffers(); @@ -1659,12 +1663,15 @@ void FNiagaraSystemInstance::TickInstanceParameters_GameThread(float DeltaSecond FNiagaraSystemParameters& CurrentSystemParameters = SystemParameters[ParameterIndex]; CurrentSystemParameters.EngineSystemAge = Age; CurrentSystemParameters.EngineTickCount = TickCount; - CurrentSystemParameters.EngineTimeSinceRendered = FMath::Max(0.0f, GatheredInstanceParameters.TimeSeconds - Component->GetLastRenderTime() - GLastRenderTimeSafetyBias); + CurrentSystemParameters.EngineTimeSinceRendered = FMath::Max(0.0f, GatheredInstanceParameters.TimeSeconds - LastRenderTime - GLastRenderTimeSafetyBias); CurrentSystemParameters.EngineExecutionState = static_cast(RequestedExecutionState); CurrentSystemParameters.EngineLodDistance = GetLODDistance(); CurrentSystemParameters.EngineLodDistanceFraction = CurrentSystemParameters.EngineLodDistance / MaxLODDistance; - Component->GetOverrideParameters().Tick(); + if (OverrideParameters) + { + OverrideParameters->Tick(); + } } void FNiagaraSystemInstance::TickInstanceParameters_Concurrent() @@ -1790,10 +1797,6 @@ bool FNiagaraSystemInstance::UsesCollection(const UNiagaraParameterCollection* C void FNiagaraSystemInstance::InitEmitters() { SCOPE_CYCLE_COUNTER(STAT_NiagaraSystemInitEmitters); - if (Component) - { - Component->MarkRenderStateDirty(); - } bHasGPUEmitters = false; @@ -1841,7 +1844,7 @@ void FNiagaraSystemInstance::InitEmitters() ResetParameters(); } -void FNiagaraSystemInstance::ComponentTick(float DeltaSeconds, const FGraphEventRef& MyCompletionGraphEvent) +void FNiagaraSystemInstance::ManualTick(float DeltaSeconds, const FGraphEventRef& MyCompletionGraphEvent) { SCOPE_CYCLE_COUNTER(STAT_NiagaraOverview_GT); SCOPE_CYCLE_COUNTER(STAT_NiagaraSystemInst_ComponentTickGT); @@ -1857,7 +1860,6 @@ void FNiagaraSystemInstance::ComponentTick(float DeltaSeconds, const FGraphEvent check(SystemSim.IsValid()); check(IsInGameThread()); check(bSolo); - check(Component); SystemSim->Tick_GameThread(DeltaSeconds, MyCompletionGraphEvent); @@ -1886,7 +1888,7 @@ void FNiagaraSystemInstance::WaitForAsyncTickDoNotFinalize(bool bEnsureComplete) if ( bDoWarning && (FPlatformTime::Cycles64() > WarnCycles) ) { bDoWarning = false; - UE_LOG(LogNiagara, Warning, TEXT("Niagara Effect has stalled GT for %g seconds and is not complete, this may result in a deadlock. Component(%s) System(%s)"), WarnSeconds, *GetFullNameSafe(Component), *GetFullNameSafe(GetSystem())); + UE_LOG(LogNiagara, Warning, TEXT("Niagara Effect has stalled GT for %g seconds and is not complete, this may result in a deadlock. Component(%s) System(%s)"), WarnSeconds, *GetFullNameSafe(AttachComponent.Get()), *GetFullNameSafe(GetSystem())); } } @@ -1894,7 +1896,7 @@ void FNiagaraSystemInstance::WaitForAsyncTickDoNotFinalize(bool bEnsureComplete) if (StallTimeMS > GWaitForAsyncStallWarnThresholdMS) { //-TODO: This should be put back to a warning once EngineTests no longer cause it show up. The reason it's triggered is because we pause in latent actions right after a TG running Niagara sims. - UE_LOG(LogNiagara, Log, TEXT("Niagara Effect stalled GT for %g ms. Component(%s) System(%s)"), StallTimeMS, *GetFullNameSafe(Component), *GetFullNameSafe(GetSystem())); + UE_LOG(LogNiagara, Log, TEXT("Niagara Effect stalled GT for %g ms. Component(%s) System(%s)"), StallTimeMS, *GetFullNameSafe(AttachComponent.Get()), *GetFullNameSafe(GetSystem())); } } @@ -1979,7 +1981,7 @@ void FNiagaraSystemInstance::Tick_Concurrent(bool bEnqueueGPUTickIfNeeded) GPUParamIncludeInterpolation = false; UNiagaraSystem* System = GetSystem(); - if (IsComplete() || System == nullptr || Component == nullptr || CachedDeltaSeconds < SMALL_NUMBER) + if (IsComplete() || System == nullptr || CachedDeltaSeconds < SMALL_NUMBER) { bAsyncWorkInProgress = false; return; @@ -2066,20 +2068,14 @@ void FNiagaraSystemInstance::Tick_Concurrent(bool bEnqueueGPUTickIfNeeded) else { FBox NewLocalBounds(EForceInit::ForceInit); - for ( const auto& Emitter : Emitters ) + for (const auto& Emitter : Emitters) { NewLocalBounds += Emitter->GetBounds(); } - if ( NewLocalBounds.IsValid ) + if (NewLocalBounds.IsValid) { - TimeSinceLastForceUpdateTransform += CachedDeltaSeconds; - if ((TimeSinceLastForceUpdateTransform > Component->MaxTimeBeforeForceUpdateTransform) || !LocalBounds.IsInsideOrOn(NewLocalBounds.Min) || !LocalBounds.IsInsideOrOn(NewLocalBounds.Max)) - { - bIsTransformDirty = true; - LocalBounds = NewLocalBounds.ExpandBy(NewLocalBounds.GetExtent() * GNiagaraBoundsExpandByPercent); - TimeSinceLastForceUpdateTransform = 0.0f; - } + LocalBounds = NewLocalBounds.ExpandBy(NewLocalBounds.GetExtent() * GNiagaraBoundsExpandByPercent); } else { @@ -2106,6 +2102,8 @@ void FNiagaraSystemInstance::ProcessComponentRendererTasks() { return; } + + USceneComponent* Component = AttachComponent.Get(); if (!Component) { // we can't attach the components anywhere, so just discard them @@ -2278,26 +2276,16 @@ bool FNiagaraSystemInstance::FinalizeTick_GameThread(bool bEnqueueGPUTickIfNeede //Post tick our interfaces. TickDataInterfaces(CachedDeltaSeconds, true); - if (Component) + ProcessComponentRendererTasks(); + + //Enqueue a GPU tick for this sim if we have to do this from the GameThread. + //If we're batching our tick passing we may still need to enqueue here if not called from the regular finalize task. The caller will tell us with bEnqueueGPUTickIfNeeded. + FNiagaraSystemSimulation* Sim = SystemSimulation.Get(); + check(Sim); + ENiagaraGPUTickHandlingMode Mode = Sim->GetGPUTickHandlingMode(); + if (Mode == ENiagaraGPUTickHandlingMode::GameThread || (Mode == ENiagaraGPUTickHandlingMode::GameThreadBatched && bEnqueueGPUTickIfNeeded)) { - if ( bIsTransformDirty ) - { - bIsTransformDirty = false; - Component->UpdateComponentToWorld(); - } - Component->MarkRenderDynamicDataDirty(); - - ProcessComponentRendererTasks(); - - //Enqueue a GPU tick for this sim if we have to do this from the GameThread. - //If we're batching our tick passing we may still need to enqueue here if not called from the regular finalize task. The caller will tell us with bEnqueueGPUTickIfNeeded. - FNiagaraSystemSimulation* Sim = SystemSimulation.Get(); - check(Sim); - ENiagaraGPUTickHandlingMode Mode = Sim->GetGPUTickHandlingMode(); - if (Mode == ENiagaraGPUTickHandlingMode::GameThread || (Mode == ENiagaraGPUTickHandlingMode::GameThreadBatched && bEnqueueGPUTickIfNeeded)) - { - GenerateAndSubmitGPUTick(); - } + GenerateAndSubmitGPUTick(); } } @@ -2309,6 +2297,11 @@ bool FNiagaraSystemInstance::FinalizeTick_GameThread(bool bEnqueueGPUTickIfNeede Reset(ResetMode); } + if (OnPostTickDelegate.IsBound()) + { + OnPostTickDelegate.Execute(); + } + return true; } @@ -2360,17 +2353,14 @@ void FNiagaraSystemInstance::InitGPUTick(FNiagaraGPUSystemTick& OutTick) #if WITH_EDITOR void FNiagaraSystemInstance::RaiseNeedsUIResync() { - AsyncTask( - ENamedThreads::GameThread, - [WeakComponent = TWeakObjectPtr(Component)]() mutable - { - UNiagaraComponent* NiagaraComponent = WeakComponent.Get(); - if (NiagaraComponent != nullptr ) - { - NiagaraComponent->OnSynchronizedWithAssetParameters().Broadcast(); - } - } - ); + bNeedsUIResync = true; +} + +bool FNiagaraSystemInstance::HandleNeedsUIResync() +{ + bool bRet = bNeedsUIResync; + bNeedsUIResync = false; + return bRet; } #endif @@ -2426,18 +2416,6 @@ TSharedPtr FNiagaraSystemInstance: return nullptr; } -UNiagaraSystem* FNiagaraSystemInstance::GetSystem()const -{ - if (Component) - { - return Component->GetAsset(); - } - else - { - return nullptr; - } -} - TConstArrayView FNiagaraSystemInstance::GetEmitterExecutionOrder() const { if (SystemSimulation != nullptr) @@ -2489,12 +2467,13 @@ const FString& FNiagaraSystemInstance::GetCrashReporterTag()const { if(CrashReporterTag.IsEmpty()) { - UNiagaraSystem* Sys = Component ? Component->GetAsset() : nullptr; - USceneComponent* AttachParent = Component ? Component->GetAttachParent() : nullptr; + UNiagaraSystem* Sys = GetSystem(); + UNiagaraComponent* Component = Cast(AttachComponent.Get()); + USceneComponent* AttachParent = Component ? Component->GetAttachParent() : AttachComponent.Get(); - const FString& CompName = Component ? Component->GetFullName() : TEXT("nullptr"); - const FString& AssetName = Sys ? Sys->GetFullName() : TEXT("nullptr"); - const FString& AttachName = AttachParent ? AttachParent->GetFullName() : TEXT("nullptr"); + const FString& CompName = GetFullNameSafe(Component); + const FString& AssetName = GetFullNameSafe(Sys); + const FString& AttachName = GetFullNameSafe(AttachParent); CrashReporterTag = FString::Printf(TEXT("SystemInstance | System: %s | bSolo: %s | Component: %s | AttachedTo: %s |"), *AssetName, IsSolo() ? TEXT("true") : TEXT("false"), *CompName, *AttachName); } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemSimulation.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemSimulation.cpp index 7194a0e77034..5ad39a4d4339 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemSimulation.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemSimulation.cpp @@ -523,8 +523,20 @@ bool FNiagaraSystemSimulation::Init(UNiagaraSystem* InSystem, UWorld* InWorld, b { //SCOPE_CYCLE_COUNTER(STAT_NiagaraSystemSim_Init_ExecContexts); - SpawnExecContext.Init(SpawnScript, ENiagaraSimTarget::CPUSim); - UpdateExecContext.Init(UpdateScript, ENiagaraSimTarget::CPUSim); + if (UseLegacySystemSimulationContexts()) + { + SpawnExecContext = MakeUnique(); + UpdateExecContext = MakeUnique(); + bCanExecute &= SpawnExecContext->Init(SpawnScript, ENiagaraSimTarget::CPUSim); + bCanExecute &= UpdateExecContext->Init(UpdateScript, ENiagaraSimTarget::CPUSim); + } + else + { + SpawnExecContext = MakeUnique(ENiagaraSystemSimulationScript::Spawn); + UpdateExecContext = MakeUnique(ENiagaraSystemSimulationScript::Update); + bCanExecute &= SpawnExecContext->Init(SpawnScript, ENiagaraSimTarget::CPUSim); + bCanExecute &= UpdateExecContext->Init(UpdateScript, ENiagaraSimTarget::CPUSim); + } } { @@ -533,11 +545,11 @@ bool FNiagaraSystemSimulation::Init(UNiagaraSystem* InSystem, UWorld* InWorld, b //Bind parameter collections. for (UNiagaraParameterCollection* Collection : SpawnScript->GetCachedParameterCollectionReferences()) { - GetParameterCollectionInstance(Collection)->GetParameterStore().Bind(&SpawnExecContext.Parameters); + GetParameterCollectionInstance(Collection)->GetParameterStore().Bind(&SpawnExecContext->Parameters); } for (UNiagaraParameterCollection* Collection : UpdateScript->GetCachedParameterCollectionReferences()) { - GetParameterCollectionInstance(Collection)->GetParameterStore().Bind(&UpdateExecContext.Parameters); + GetParameterCollectionInstance(Collection)->GetParameterStore().Bind(&UpdateExecContext->Parameters); } TArray> Scripts; @@ -545,31 +557,31 @@ bool FNiagaraSystemSimulation::Init(UNiagaraSystem* InSystem, UWorld* InWorld, b Scripts.Add(UpdateScript); FNiagaraUtilities::CollectScriptDataInterfaceParameters(*System, Scripts, ScriptDefinedDataInterfaceParameters); - ScriptDefinedDataInterfaceParameters.Bind(&SpawnExecContext.Parameters); - ScriptDefinedDataInterfaceParameters.Bind(&UpdateExecContext.Parameters); + ScriptDefinedDataInterfaceParameters.Bind(&SpawnExecContext->Parameters); + ScriptDefinedDataInterfaceParameters.Bind(&UpdateExecContext->Parameters); - SpawnScript->RapidIterationParameters.Bind(&SpawnExecContext.Parameters); - UpdateScript->RapidIterationParameters.Bind(&UpdateExecContext.Parameters); + SpawnScript->RapidIterationParameters.Bind(&SpawnExecContext->Parameters); + UpdateScript->RapidIterationParameters.Bind(&UpdateExecContext->Parameters); // If this simulation is not solo than we have bind the source system parameters to the system simulation contexts so that // the system and emitter scripts use the default shared data interfaces. - if (!bIsSolo) + if (UseLegacySystemSimulationContexts() && !bIsSolo) { FNiagaraUserRedirectionParameterStore& ExposedParameters = System->GetExposedParameters(); - ExposedParameters.Bind(&SpawnExecContext.Parameters); - ExposedParameters.Bind(&UpdateExecContext.Parameters); + ExposedParameters.Bind(&SpawnExecContext->Parameters); + ExposedParameters.Bind(&UpdateExecContext->Parameters); } } { //SCOPE_CYCLE_COUNTER(STAT_NiagaraSystemSim_Init_DirectBindings); - SpawnNumSystemInstancesParam.Init(SpawnExecContext.Parameters, SYS_PARAM_ENGINE_NUM_SYSTEM_INSTANCES); - UpdateNumSystemInstancesParam.Init(UpdateExecContext.Parameters, SYS_PARAM_ENGINE_NUM_SYSTEM_INSTANCES); - SpawnGlobalSpawnCountScaleParam.Init(SpawnExecContext.Parameters, SYS_PARAM_ENGINE_GLOBAL_SPAWN_COUNT_SCALE); - UpdateGlobalSpawnCountScaleParam.Init(UpdateExecContext.Parameters, SYS_PARAM_ENGINE_GLOBAL_SPAWN_COUNT_SCALE); - SpawnGlobalSystemCountScaleParam.Init(SpawnExecContext.Parameters, SYS_PARAM_ENGINE_GLOBAL_SYSTEM_COUNT_SCALE); - UpdateGlobalSystemCountScaleParam.Init(UpdateExecContext.Parameters, SYS_PARAM_ENGINE_GLOBAL_SYSTEM_COUNT_SCALE); + SpawnNumSystemInstancesParam.Init(SpawnExecContext->Parameters, SYS_PARAM_ENGINE_NUM_SYSTEM_INSTANCES); + UpdateNumSystemInstancesParam.Init(UpdateExecContext->Parameters, SYS_PARAM_ENGINE_NUM_SYSTEM_INSTANCES); + SpawnGlobalSpawnCountScaleParam.Init(SpawnExecContext->Parameters, SYS_PARAM_ENGINE_GLOBAL_SPAWN_COUNT_SCALE); + UpdateGlobalSpawnCountScaleParam.Init(UpdateExecContext->Parameters, SYS_PARAM_ENGINE_GLOBAL_SPAWN_COUNT_SCALE); + SpawnGlobalSystemCountScaleParam.Init(SpawnExecContext->Parameters, SYS_PARAM_ENGINE_GLOBAL_SYSTEM_COUNT_SCALE); + UpdateGlobalSystemCountScaleParam.Init(UpdateExecContext->Parameters, SYS_PARAM_ENGINE_GLOBAL_SYSTEM_COUNT_SCALE); } } @@ -585,35 +597,21 @@ void FNiagaraSystemSimulation::Destroy() { FNiagaraSystemInstance* Inst = SystemInstances.Last(); check(Inst); - if (ensure(Inst->GetComponent()))//Currently we have no cases whre there shouldn't be a component but maybe in future. - { - Inst->GetComponent()->DeactivateImmediate(); - } - else - { - Inst->Deactivate(true); - } + Inst->Deactivate(true); } while (PendingSystemInstances.Num()) { FNiagaraSystemInstance* Inst = PendingSystemInstances.Last(); check(Inst); - if (ensure(Inst->GetComponent()))//Currently we have no cases whre there shouldn't be a component but maybe in future. - { - Inst->GetComponent()->DeactivateImmediate(); - } - else - { - Inst->Deactivate(true); - } + Inst->Deactivate(true); } SystemInstances.Empty(); PendingSystemInstances.Empty(); FNiagaraWorldManager* WorldMan = FNiagaraWorldManager::Get(World); check(WorldMan); - SpawnExecContext.Parameters.UnbindFromSourceStores(); - UpdateExecContext.Parameters.UnbindFromSourceStores(); + SpawnExecContext->Parameters.UnbindFromSourceStores(); + UpdateExecContext->Parameters.UnbindFromSourceStores(); } UNiagaraParameterCollectionInstance* FNiagaraSystemSimulation::GetParameterCollectionInstance(UNiagaraParameterCollection* Collection) @@ -695,10 +693,10 @@ void FNiagaraSystemSimulation::DumpInstance(const FNiagaraSystemInstance* Inst)c UE_LOG(LogNiagara, Log, TEXT("== %s (%d) ========"), *Inst->GetSystem()->GetFullName(), Inst->SystemInstanceIndex); UE_LOG(LogNiagara, Log, TEXT(".................Spawn.................")); - SpawnExecContext.Parameters.DumpParameters(false); + SpawnExecContext->Parameters.DumpParameters(false); SpawnInstanceParameterDataSet.Dump(Inst->SystemInstanceIndex, 1, TEXT("Spawn Instance Parameters")); UE_LOG(LogNiagara, Log, TEXT(".................Update.................")); - UpdateExecContext.Parameters.DumpParameters(false); + UpdateExecContext->Parameters.DumpParameters(false); UpdateInstanceParameterDataSet.Dump(Inst->SystemInstanceIndex, 1, TEXT("Update Instance Parameters")); UE_LOG(LogNiagara, Log, TEXT("................. System Instance .................")); MainDataSet.Dump(Inst->SystemInstanceIndex, 1, TEXT("System Data")); @@ -811,7 +809,7 @@ void FNiagaraSystemSimulation::FlushTickBatch(FNiagaraSystemSimulationTickContex /** First phase of system sim tick. Must run on GameThread. */ void FNiagaraSystemSimulation::Tick_GameThread(float DeltaSeconds, const FGraphEventRef& MyCompletionGraphEvent) { - if (PendingSystemInstances.Num() == 0 && SystemInstances.Num() == 0) + if ((PendingSystemInstances.Num() == 0 && SystemInstances.Num() == 0) || !bCanExecute) { return; } @@ -1096,7 +1094,7 @@ void FNiagaraSystemSimulation::UpdateTickGroups_GameThread() void FNiagaraSystemSimulation::Spawn_GameThread(float DeltaSeconds, bool bPostActorTick) { // Early out, nothing to do - if (PendingSystemInstances.Num() == 0) + if (PendingSystemInstances.Num() == 0 || !bCanExecute) { return; } @@ -1461,7 +1459,7 @@ void FNiagaraSystemSimulation::SpawnSystemInstances(FNiagaraSystemSimulationTick Context.DataSet.GetDestinationDataChecked().SetNumInstances(NumInstances); // Run Spawn - if (SpawnExecContext.Tick(SoloSystemInstance, ENiagaraSimTarget::CPUSim) == false) + if (SpawnExecContext->Tick(SoloSystemInstance, ENiagaraSimTarget::CPUSim) == false) { for (FNiagaraSystemInstance* SystemInst : Context.Instances) { @@ -1471,13 +1469,14 @@ void FNiagaraSystemSimulation::SpawnSystemInstances(FNiagaraSystemSimulationTick return; } - SpawnExecContext.BindData(0, Context.DataSet, OrigNum, false); - SpawnExecContext.BindData(1, SpawnInstanceParameterDataSet, OrigNum, false); + SpawnExecContext->BindSystemInstances(Context.Instances); + SpawnExecContext->BindData(0, Context.DataSet, OrigNum, false); + SpawnExecContext->BindData(1, SpawnInstanceParameterDataSet, OrigNum, false); FScriptExecutionConstantBufferTable SpawnConstantBufferTable; BuildConstantBufferTable(Context.Instances[0]->GetGlobalParameters(), SpawnExecContext, SpawnConstantBufferTable); - SpawnExecContext.Execute(SpawnNum, SpawnConstantBufferTable); + SpawnExecContext->Execute(SpawnNum, SpawnConstantBufferTable); if (GbDumpSystemData || Context.System->bDumpDebugSystemInfo) { @@ -1495,7 +1494,7 @@ void FNiagaraSystemSimulation::SpawnSystemInstances(FNiagaraSystemSimulationTick if (DebugInfo) { Context.DataSet.CopyTo(DebugInfo->Frame, OrigNum, SpawnNum); - DebugInfo->Parameters = UpdateExecContext.Parameters; + DebugInfo->Parameters = UpdateExecContext->Parameters; DebugInfo->bWritten = true; } } @@ -1521,7 +1520,7 @@ void FNiagaraSystemSimulation::UpdateSystemInstances(FNiagaraSystemSimulationTic DestinationData.SetNumInstances(NumInstances); // Tick UpdateExecContext, this can fail to bind VM functions if this happens we become invalid so mark all instances as disabled - if (UpdateExecContext.Tick(Context.Instances[0], ENiagaraSimTarget::CPUSim) == false) + if (UpdateExecContext->Tick(Context.Instances[0], ENiagaraSimTarget::CPUSim) == false) { for (FNiagaraSystemInstance* SystemInst : Context.Instances) { @@ -1534,13 +1533,14 @@ void FNiagaraSystemSimulation::UpdateSystemInstances(FNiagaraSystemSimulationTic // Run update. if (OrigNum > 0) { - UpdateExecContext.BindData(0, Context.DataSet, 0, false); - UpdateExecContext.BindData(1, UpdateInstanceParameterDataSet, 0, false); + UpdateExecContext->BindSystemInstances(Context.Instances); + UpdateExecContext->BindData(0, Context.DataSet, 0, false); + UpdateExecContext->BindData(1, UpdateInstanceParameterDataSet, 0, false); FScriptExecutionConstantBufferTable UpdateConstantBufferTable; BuildConstantBufferTable(Context.Instances[0]->GetGlobalParameters(), UpdateExecContext, UpdateConstantBufferTable); - UpdateExecContext.Execute(OrigNum, UpdateConstantBufferTable); + UpdateExecContext->Execute(OrigNum, UpdateConstantBufferTable); } if (GbDumpSystemData || Context.System->bDumpDebugSystemInfo) @@ -1555,9 +1555,9 @@ void FNiagaraSystemSimulation::UpdateSystemInstances(FNiagaraSystemSimulationTic //Ideally this should be compiled directly into the script similarly to interpolated particle spawning. if ( (SpawnNum > 0) && GbSystemUpdateOnSpawn) { - - UpdateExecContext.BindData(0, Context.DataSet, OrigNum, false); - UpdateExecContext.BindData(1, UpdateInstanceParameterDataSet, OrigNum, false); + UpdateExecContext->BindSystemInstances(Context.Instances); + UpdateExecContext->BindData(0, Context.DataSet, OrigNum, false); + UpdateExecContext->BindData(1, UpdateInstanceParameterDataSet, OrigNum, false); FNiagaraGlobalParameters UpdateOnSpawnParameters(Context.Instances[0]->GetGlobalParameters()); UpdateOnSpawnParameters.EngineDeltaTime = 0.0001f; @@ -1566,7 +1566,7 @@ void FNiagaraSystemSimulation::UpdateSystemInstances(FNiagaraSystemSimulationTic FScriptExecutionConstantBufferTable UpdateConstantBufferTable; BuildConstantBufferTable(UpdateOnSpawnParameters, UpdateExecContext, UpdateConstantBufferTable); - UpdateExecContext.Execute(SpawnNum, UpdateConstantBufferTable); + UpdateExecContext->Execute(SpawnNum, UpdateConstantBufferTable); if (GbDumpSystemData || Context.System->bDumpDebugSystemInfo) { @@ -1585,7 +1585,7 @@ void FNiagaraSystemSimulation::UpdateSystemInstances(FNiagaraSystemSimulationTic if (DebugInfo) { Context.DataSet.CopyTo(DebugInfo->Frame); - DebugInfo->Parameters = UpdateExecContext.Parameters; + DebugInfo->Parameters = UpdateExecContext->Parameters; DebugInfo->bWritten = true; } } @@ -1679,6 +1679,9 @@ void FNiagaraSystemSimulation::TransferSystemSimResults(FNiagaraSystemSimulation UE_LOG(LogNiagara, Log, TEXT("Skipping DataSetToEmitterEventParameters because EventIdx is out-of-bounds. %d of %d"), EventIdx, DataSetToEmitterEventParameters[EmitterIdx].Num()); } } + + DataSetToEmitterRendererParameters[EmitterIdx].DataSetToParameterStore(EmitterInst.GetRendererBoundVariables(), Context.DataSet, SystemIndex); + } } } @@ -1970,6 +1973,7 @@ void FNiagaraSystemSimulation::InitParameterDataSetBindings(FNiagaraSystemInstan DataSetToEmitterUpdateParameters.SetNum(EmitterCount); DataSetToEmitterEventParameters.SetNum(EmitterCount); DataSetToEmitterGPUParameters.SetNum(EmitterCount); + DataSetToEmitterRendererParameters.SetNum(EmitterCount); for (int32 EmitterIdx = 0; EmitterIdx < EmitterCount; ++EmitterIdx) { @@ -1991,6 +1995,8 @@ void FNiagaraSystemSimulation::InitParameterDataSetBindings(FNiagaraSystemInstan DataSetToEmitterGPUParameters[EmitterIdx].Init(MainDataSet, GPUContext->CombinedParamStore); } + DataSetToEmitterRendererParameters[EmitterIdx].Init(MainDataSet, EmitterInst.GetRendererBoundVariables()); + TArrayView EventContexts = EmitterInst.GetEventExecutionContexts(); const int32 EventCount = EventContexts.Num(); DataSetToEmitterEventParameters[EmitterIdx].SetNum(EventCount); @@ -2077,16 +2083,16 @@ void FNiagaraConstantBufferToDataSetBinding::ApplyOffsets( void FNiagaraSystemSimulation::BuildConstantBufferTable( const FNiagaraGlobalParameters& GlobalParameters, - FNiagaraScriptExecutionContext& ExecContext, + TUniquePtr& ExecContext, FScriptExecutionConstantBufferTable& ConstantBufferTable) const { - const auto ScriptLiterals = ExecContext.GetScriptLiterals(); + const auto ScriptLiterals = ExecContext->GetScriptLiterals(); - check(!ExecContext.HasInterpolationParameters); + check(!ExecContext->HasInterpolationParameters); - const auto& ExternalParameterData = ExecContext.Parameters.GetParameterDataArray(); + const auto& ExternalParameterData = ExecContext->Parameters.GetParameterDataArray(); uint8* ExternalParameterBuffer = const_cast(ExternalParameterData.GetData()); - const uint32 ExternalParameterSize = ExecContext.Parameters.GetExternalParameterSize(); + const uint32 ExternalParameterSize = ExecContext->Parameters.GetExternalParameterSize(); ConstantBufferTable.Reset(3); ConstantBufferTable.AddTypedBuffer(GlobalParameters); @@ -2115,3 +2121,52 @@ ENiagaraGPUTickHandlingMode FNiagaraSystemSimulation::GetGPUTickHandlingMode()co return ENiagaraGPUTickHandlingMode::None; } + +static int32 GbNiagaraUseLegacySystemSimContexts = 0; +static FAutoConsoleVariableRef CVarNiagaraUseLevgacySystemSimContexts( + TEXT("fx.Niagara.UseLegacySystemSimContexts"), + GbNiagaraUseLegacySystemSimContexts, + TEXT("If > 0, Niagara will use legacy system simulation contexts which would force the whole simulation solo if there were per instance DI calls in the system scripts. \n"), + FConsoleVariableDelegate::CreateStatic(&FNiagaraSystemSimulation::OnChanged_UseLegacySystemSimulationContexts), + ECVF_Default +); + +bool FNiagaraSystemSimulation::bUseLegacyExecContexts = GbNiagaraUseLegacySystemSimContexts != 0; +bool FNiagaraSystemSimulation::UseLegacySystemSimulationContexts() +{ + return bUseLegacyExecContexts; +} + +void FNiagaraSystemSimulation::OnChanged_UseLegacySystemSimulationContexts(IConsoleVariable* CVar) +{ + bool bNewValue = GbNiagaraUseLegacySystemSimContexts != 0; + if( bUseLegacyExecContexts != bNewValue) + { + //To change at runtime we have to reinit all systems so they have the correct per instance DI bindings. + FNiagaraSystemUpdateContext UpdateContext; + UpdateContext.SetDestroyOnAdd(true); + UpdateContext.SetOnlyActive(true); + UpdateContext.AddAll(true); + + //Just to be sure there's no lingering state, clear out the pools. + //TODO: Moveinto the update context itself? + FNiagaraWorldManager::ForAllWorldManagers( + [](FNiagaraWorldManager& WorldMan) + { + WorldMan.GetComponentPool()->Cleanup(true); + } + ); + + //Reactivate any FX that were active. + bUseLegacyExecContexts = bNewValue; + UpdateContext.CommitUpdate(); + + //Re-prime the pools. + FNiagaraWorldManager::ForAllWorldManagers( + [](FNiagaraWorldManager& WorldMan) + { + WorldMan.PrimePoolForAllSystems(); + } + ); + } +} \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraUserRedirectionParameterStore.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraUserRedirectionParameterStore.cpp index 9e1c0bbf4ff5..8e84f4019a93 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraUserRedirectionParameterStore.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraUserRedirectionParameterStore.cpp @@ -65,14 +65,20 @@ void FNiagaraUserRedirectionParameterStore::RecreateRedirections() } } -FNiagaraVariableBase FNiagaraUserRedirectionParameterStore::FindRedirection(const FNiagaraVariableBase& InVar) const +bool FNiagaraUserRedirectionParameterStore::RedirectUserVariable(FNiagaraVariableBase& UserVar) const { - if (const FNiagaraVariable* RedirectedKey = UserParameterRedirects.Find(InVar)) + if (const FNiagaraVariable* RedirectedKey = UserParameterRedirects.Find(UserVar)) { - return *RedirectedKey; + UserVar = FNiagaraVariableBase(*RedirectedKey); + return true; } - return InVar; + if (IsUserParameter(UserVar)) + { + return true; + } + + return false; } bool FNiagaraUserRedirectionParameterStore::AddParameter(const FNiagaraVariable& Param, bool bInitialize /*= true*/, bool bTriggerRebind /*= true*/, int32* OutOffset /*= nullptr*/) diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraWorldManager.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraWorldManager.cpp index a13fe2098934..97b0ef8a6eee 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraWorldManager.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraWorldManager.cpp @@ -53,7 +53,7 @@ static FAutoConsoleVariableRef CVarNiagaraUsePostActorMark( static int GNiagaraSpawnPerTickGroup = 1; static FAutoConsoleVariableRef CVarNiagaraSpawnPerTickGroup( TEXT("fx.Niagara.WorldManager.SpawnPerTickGroup"), - GNiagaraUsePostActorMark, + GNiagaraSpawnPerTickGroup, TEXT("Will attempt to spawn new systems earlier (default enabled)."), ECVF_Default ); @@ -398,6 +398,7 @@ void FNiagaraWorldManager::DestroySystemSimulation(UNiagaraSystem* System) SystemSimulations[TG].Remove(System); } } + ComponentPool->RemoveComponentsBySystem(System); } void FNiagaraWorldManager::DestroySystemInstance(TUniquePtr& InPtr) @@ -594,24 +595,40 @@ void FNiagaraWorldManager::PostActorTick(float DeltaSeconds) // - Instances that were spawned and we need to ensure the async tick is complete if (SimulationsWithPostActorWork.Num() > 0) { - for (int32 i = 0; i < SimulationsWithPostActorWork.Num(); ++i) + for (int32 i=0; i < SimulationsWithPostActorWork.Num(); ++i ) { - const auto& System = SimulationsWithPostActorWork[i]; - if (System->GetSystem()->IsValid()) + if (!SimulationsWithPostActorWork[i]->IsValid()) { - System->WaitForSystemTickComplete(); - System->UpdateTickGroups_GameThread(); + SimulationsWithPostActorWork.RemoveAtSwap(i, 1, false); + --i; + } + else + { + SimulationsWithPostActorWork[i]->WaitForSystemTickComplete(); } } - for (int32 i = 0; i < SimulationsWithPostActorWork.Num(); ++i) + for (int32 i=0; i < SimulationsWithPostActorWork.Num(); ++i) { - const auto& System = SimulationsWithPostActorWork[i]; - if (System->GetSystem()->IsValid()) + if (!SimulationsWithPostActorWork[i]->IsValid()) { - System->Spawn_GameThread(DeltaSeconds, true); + SimulationsWithPostActorWork.RemoveAtSwap(i, 1, false); + --i; + } + else + { + SimulationsWithPostActorWork[i]->UpdateTickGroups_GameThread(); } } + + for (const auto& Simulation : SimulationsWithPostActorWork) + { + if (Simulation->IsValid()) + { + Simulation->Spawn_GameThread(DeltaSeconds, true); + } + } + SimulationsWithPostActorWork.Reset(); } } @@ -620,6 +637,18 @@ void FNiagaraWorldManager::PostActorTick(float DeltaSeconds) SimulationsWithPostActorWork.Reset(); // Resolve tick groups for pending spawn instances + for (int TG = 0; TG < NiagaraNumTickGroups; ++TG) + { + for (TPair>& SystemSim : SystemSimulations[TG]) + { + FNiagaraSystemSimulation* Sim = &SystemSim.Value.Get(); + if (Sim->IsValid()) + { + Sim->WaitForSystemTickComplete(); + } + } + } + for (int TG=0; TG < NiagaraNumTickGroups; ++TG) { for (TPair>& SystemSim : SystemSimulations[TG]) @@ -632,7 +661,6 @@ void FNiagaraWorldManager::PostActorTick(float DeltaSeconds) } } - // Execute spawn game thread for (int TG = 0; TG < NiagaraNumTickGroups; ++TG) { for (TPair>& SystemSim : SystemSimulations[TG]) @@ -776,7 +804,7 @@ void FNiagaraWorldManager::Tick(ETickingGroup TickGroup, float DeltaSeconds, ELe for (int32 i = 0; i < SimulationsWithPostActorWork.Num(); ++i) { const auto& Simulation = SimulationsWithPostActorWork[i]; - if (Simulation->GetSystem()->IsValid() && (Simulation->GetTickGroup() < TickGroup)) + if (Simulation->IsValid() && (Simulation->GetTickGroup() < TickGroup)) { Simulation->Spawn_GameThread(DeltaSeconds, false); } @@ -983,7 +1011,13 @@ bool FNiagaraWorldManager::CanPreCull(UNiagaraEffectType* EffectType) void FNiagaraWorldManager::SortedSignificanceCull(UNiagaraEffectType* EffectType, const FNiagaraSystemScalabilitySettings& ScalabilitySettings, float Significance, int32 Index, FNiagaraScalabilityState& OutState) { //Cull all but the N most significance FX. - bool bCull = ScalabilitySettings.bCullMaxInstanceCount && Index >= ScalabilitySettings.MaxInstances; + bool bCull = false; + + if(GEnableNiagaraInstanceCountCulling) + { + bCull = ScalabilitySettings.bCullMaxInstanceCount && Index >= ScalabilitySettings.MaxInstances; + } + OutState.bCulled |= bCull; #if DEBUG_SCALABILITY_STATE OutState.bCulledByInstanceCount = bCull; @@ -1067,12 +1101,19 @@ float FNiagaraWorldManager::DistanceSignificance(UNiagaraEffectType* EffectType, return 1.0f - (LODDistance / ScalabilitySettings.MaxDistance); } + else + { + //We still need a sensible significance value for sorted instance count culling. + //100 here purely to make 1m LODDistance = 1.0 significance. + return 100.0f / LODDistance; + } + return 1.0f; } float FNiagaraWorldManager::DistanceSignificance(UNiagaraEffectType* EffectType, const FNiagaraSystemScalabilitySettings& ScalabilitySettings, FVector Location) { - if (ScalabilitySettings.bCullByDistance && bCachedPlayerViewLocationsValid) + if (bCachedPlayerViewLocationsValid) { float ClosestDistSq = FLT_MAX; for (FVector ViewLocation : CachedPlayerViewLocations) @@ -1081,13 +1122,23 @@ float FNiagaraWorldManager::DistanceSignificance(UNiagaraEffectType* EffectType, } float ClosestDist = FMath::Sqrt(ClosestDistSq); - if (ClosestDist >= ScalabilitySettings.MaxDistance) + if (ScalabilitySettings.bCullByDistance) { - return 0.0f; - } + if (ClosestDist >= ScalabilitySettings.MaxDistance) + { + return 0.0f; + } - return ClosestDist / ScalabilitySettings.MaxDistance; + return ClosestDist / ScalabilitySettings.MaxDistance; + } + else + { + //We still need a sensible significance value for sorted instance count culling. + //100 here purely to make 1m ClosestDist = 1.0 significance. + return 100.0f / ClosestDist; + } } + return 1.0f; } @@ -1107,7 +1158,7 @@ void FNiagaraWorldManager::PrimePoolForAllWorlds(UNiagaraSystem* System) void FNiagaraWorldManager::PrimePoolForAllSystems() { - if (GNigaraAllowPrimedPools) + if (GNigaraAllowPrimedPools && World && World->IsGameWorld()) { //Prime the pool for all currently loaded systems. for (TObjectIterator It; It; ++It) @@ -1122,7 +1173,7 @@ void FNiagaraWorldManager::PrimePoolForAllSystems() void FNiagaraWorldManager::PrimePool(UNiagaraSystem* System) { - if (GNigaraAllowPrimedPools) + if (GNigaraAllowPrimedPools && World && World->IsGameWorld()) { ComponentPool->PrimePool(System, World); } @@ -1157,5 +1208,4 @@ FAutoConsoleCommandWithWorld GDumpNiagaraScalabilityData( WorldMan->DumpScalabilityState(); })); - #endif diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraCommon.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraCommon.h index 6c6db1b755b5..c5da706025ee 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraCommon.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraCommon.h @@ -254,6 +254,9 @@ struct NIAGARA_API FNiagaraFunctionSignature FName OwnerName; UPROPERTY() uint32 bRequiresContext : 1; + /** Does this function need an exec pin for control flow because it has internal side effects that be seen by the script VM and could therefore be optimized out? If so, set to true. Default is false. */ + UPROPERTY() + uint32 bRequiresExecPin : 1; /** True if this is the signature for a "member" function of a data interface. If this is true, the first input is the owner. */ UPROPERTY() uint32 bMemberFunction : 1; @@ -282,6 +285,10 @@ struct NIAGARA_API FNiagaraFunctionSignature UPROPERTY() uint32 bWriteFunction : 1; + /** Bitmask for which scripts are supported for this function. Use UNiagaraScript::MakeSupportedUsageContextBitmask to make the bitmask. */ + UPROPERTY(meta = (Bitmask, BitmaskEnum = ENiagaraScriptUsage)) + int32 ModuleUsageBitmask; + /** Function specifiers verified at bind time. */ UPROPERTY() TMap FunctionSpecifiers; @@ -294,11 +301,13 @@ struct NIAGARA_API FNiagaraFunctionSignature FNiagaraFunctionSignature() : bRequiresContext(false) + , bRequiresExecPin(false) , bMemberFunction(false) , bExperimental(false) , bSupportsCPU(true) , bSupportsGPU(true) , bWriteFunction(false) + , ModuleUsageBitmask(0) { } @@ -307,11 +316,13 @@ struct NIAGARA_API FNiagaraFunctionSignature , Inputs(InInputs) , Outputs(InOutputs) , bRequiresContext(bInRequiresContext) + , bRequiresExecPin(false) , bMemberFunction(bInMemberFunction) , bExperimental(false) , bSupportsCPU(true) , bSupportsGPU(true) , bWriteFunction(false) + , ModuleUsageBitmask(0) { } @@ -321,11 +332,13 @@ struct NIAGARA_API FNiagaraFunctionSignature , Inputs(InInputs) , Outputs(InOutputs) , bRequiresContext(bInRequiresContext) + , bRequiresExecPin(false) , bMemberFunction(bInMemberFunction) , bExperimental(false) , bSupportsCPU(true) , bSupportsGPU(true) , bWriteFunction(false) + , ModuleUsageBitmask(0) , FunctionSpecifiers(InFunctionSpecifiers) { @@ -357,6 +370,7 @@ struct NIAGARA_API FNiagaraFunctionSignature bMatches &= Inputs == Other.Inputs; bMatches &= Outputs == Other.Outputs; bMatches &= bRequiresContext == Other.bRequiresContext; + bMatches &= bRequiresExecPin == Other.bRequiresExecPin; bMatches &= bMemberFunction == Other.bMemberFunction; bMatches &= OwnerName == Other.OwnerName; return bMatches; @@ -462,6 +476,10 @@ public: /** Note that this is the CDO for this type of data interface, as we often cannot guarantee that the same instance of the data interface we compiled with is the one the user ultimately executes. Only call this on the game thread.*/ UNiagaraDataInterface* GetDefaultDataInterface() const; + + bool NeedsPerInstanceBinding() const; + + bool MatchesClass(const UClass* InClass) const; }; USTRUCT() @@ -671,6 +689,27 @@ enum class ENiagaraScriptUsage : uint8 SystemUpdateScript, }; +/** Defines common bit masks for script usage */ +namespace ENiagaraScriptUsageMask +{ + enum Type + { + System = + (1 << int32(ENiagaraScriptUsage::SystemSpawnScript)) | + (1 << int32(ENiagaraScriptUsage::SystemUpdateScript)), + Emitter = + (1 << int32(ENiagaraScriptUsage::EmitterSpawnScript)) | + (1 << int32(ENiagaraScriptUsage::EmitterUpdateScript)), + Particle = + (1 << int32(ENiagaraScriptUsage::ParticleSpawnScript)) | + (1 << int32(ENiagaraScriptUsage::ParticleSpawnScriptInterpolated)) | + (1 << int32(ENiagaraScriptUsage::ParticleUpdateScript)) | + (1 << int32(ENiagaraScriptUsage::ParticleEventScript)) | + (1 << int32(ENiagaraScriptUsage::ParticleSimulationStageScript)) | + (1 << int32(ENiagaraScriptUsage::ParticleGPUComputeScript)), + }; +} + /** Defines different usages for a niagara script. */ UENUM() enum class ENiagaraCompileUsageStaticSwitch : uint8 @@ -687,6 +726,18 @@ enum class ENiagaraCompileUsageStaticSwitch : uint8 Default, }; +/** Defines different execution contexts for a niagara script. */ +UENUM() +enum class ENiagaraScriptContextStaticSwitch : uint8 +{ + /** The script is called in a system context. */ + System, + /** The script is called in a emitter context. */ + Emitter, + /** The script is called in a particle context. */ + Particle, +}; + UENUM() enum class ENiagaraScriptGroup : uint8 { @@ -704,6 +755,17 @@ enum class ENiagaraIterationSource : uint8 DataInterface }; +UENUM() +enum ENiagaraBindingSource +{ + ImplicitFromSource = 0, + ExplicitParticles, + ExplicitEmitter, + ExplicitSystem, + ExplicitUser, + MaxBindingSource +}; + /** Defines all you need to know about a variable.*/ USTRUCT() @@ -723,30 +785,106 @@ struct FNiagaraVariableInfo UNiagaraDataInterface* DataInterface; }; +/** This enum decides how a renderer will attempt to process the incoming data from the stack.*/ +UENUM() +enum class ENiagaraRendererSourceDataMode : uint8 +{ + /** The renderer will draw particle data, but can potentially pull in data from the Emitter/User/or System namespaces when drawing each Particle.*/ + Particles = 0, + /** The renderer will draw only one element per Emitter. It can only pull in data from Emitter/User/or System namespaces when drawing the single element. */ + Emitter +}; + USTRUCT() struct FNiagaraVariableAttributeBinding { GENERATED_USTRUCT_BODY(); - FNiagaraVariableAttributeBinding() {} - FNiagaraVariableAttributeBinding(const FNiagaraVariable& InVar, const FNiagaraVariable& InAttrVar) : BoundVariable(InVar), DataSetVariable(InAttrVar), DefaultValueIfNonExistent(InAttrVar) + FNiagaraVariableAttributeBinding() : BindingSourceMode(ENiagaraBindingSource::ImplicitFromSource), bBindingExistsOnSource(false), bIsCachedParticleValue(true) {} + + bool NIAGARA_API IsParticleBinding() const { return bIsCachedParticleValue; } + bool NIAGARA_API DoesBindingExistOnSource() const { return bBindingExistsOnSource; } + bool NIAGARA_API CanBindToHostParameterMap() const { return bBindingExistsOnSource && !bIsCachedParticleValue; } + void NIAGARA_API SetValue(const FName& InValue, const UNiagaraEmitter* InEmitter, ENiagaraRendererSourceDataMode InSourceMode); + void NIAGARA_API CacheValues(const UNiagaraEmitter* InEmitter, ENiagaraRendererSourceDataMode InSourceMode); + +#if WITH_EDITORONLY_DATA + NIAGARA_API const FName& GetName(ENiagaraRendererSourceDataMode InSourceMode) const; + NIAGARA_API FString GetDefaultValueString() const; +#endif + NIAGARA_API const FNiagaraVariableBase& GetParamMapBindableVariable() const { return ParamMapVariable; } + NIAGARA_API const FNiagaraVariableBase& GetDataSetBindableVariable() const { return DataSetVariable; } + NIAGARA_API const FNiagaraTypeDefinition& GetType() const { return DataSetVariable.GetType(); } + + NIAGARA_API bool IsValid() const { return DataSetVariable.IsValid();} + + template + T GetDefaultValue() const { - check(InVar.GetType() == InAttrVar.GetType()); - } - FNiagaraVariableAttributeBinding(const FNiagaraVariable& InVar, const FNiagaraVariable& InAttrVar, const FNiagaraVariable& InNonExistentValue) : BoundVariable(InVar), DataSetVariable(InAttrVar), DefaultValueIfNonExistent(InNonExistentValue) - { - check(InVar.GetType() == InAttrVar.GetType() && InNonExistentValue.GetType() == InAttrVar.GetType()); + return RootVariable.GetValue(); } + void NIAGARA_API Setup(const FNiagaraVariableBase& InRootVar, const FNiagaraVariableBase& InDataSetVar, const FNiagaraVariable& InDefaultValue, ENiagaraRendererSourceDataMode InSourceMode = ENiagaraRendererSourceDataMode::Particles); + void NIAGARA_API PostLoad(ENiagaraRendererSourceDataMode InSourceMode); + void NIAGARA_API Dump() const; + + void NIAGARA_API ResetToDefault(const FNiagaraVariableAttributeBinding& InOther, const UNiagaraEmitter* InEmitter, ENiagaraRendererSourceDataMode InSourceMode); + bool NIAGARA_API MatchesDefault(const FNiagaraVariableAttributeBinding& InOther, ENiagaraRendererSourceDataMode InSourceMode) const; +protected: + /** The fully expressed namespace for the variable. If an emitter namespace, this will include the Emitter's unique name.*/ + UPROPERTY() + FNiagaraVariableBase ParamMapVariable; + + /** The version of the namespace to be found in an attribute table lookup. I.e. without Particles or Emitter.*/ + UPROPERTY() + FNiagaraVariable DataSetVariable; + + /** The namespace and default value explicitly set by the user. If meant to be derived from the source mode, it will be without a namespace.*/ + UPROPERTY() + FNiagaraVariable RootVariable; + +#if WITH_EDITORONLY_DATA + /** Old variable brought in from previous setup. Generally ignored other than postload work.*/ UPROPERTY() FNiagaraVariable BoundVariable; UPROPERTY() - FNiagaraVariable DataSetVariable; + FName CachedDisplayName; +#endif + + /** Captures the state of the namespace when the variable is set. Allows us to make later decisions about how to reconstititue the namespace.*/ + UPROPERTY() + TEnumAsByte BindingSourceMode; + + /** Determine if this varible is accessible by the associated emitter passed into CacheValues.*/ + UPROPERTY() + uint32 bBindingExistsOnSource : 1; + + /** When CacheValues is called, was this a particle attribute?*/ + UPROPERTY() + uint32 bIsCachedParticleValue : 1; +}; + +USTRUCT() +struct FNiagaraMaterialAttributeBinding +{ + GENERATED_USTRUCT_BODY(); + + UPROPERTY(EditAnywhere, Category = "Variable") + FName MaterialParameterName; + + UPROPERTY(EditAnywhere, Category = "Variable") + FNiagaraVariableBase NiagaraVariable; UPROPERTY() - FNiagaraVariable DefaultValueIfNonExistent; + FNiagaraVariableBase ResolvedNiagaraVariable; + + UPROPERTY(EditAnywhere, Category = "Variable") + FNiagaraVariableBase NiagaraChildVariable; + + void NIAGARA_API CacheValues(const UNiagaraEmitter* InEmitter); + const FNiagaraVariableBase& GetParamMapBindableVariable() const; }; USTRUCT() diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponent.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponent.h index 3e0664c7b1e3..bb1595923f57 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponent.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponent.h @@ -29,6 +29,21 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNiagaraSystemFinished, class UNia #define WITH_NIAGARA_COMPONENT_PREVIEW_DATA (!UE_BUILD_SHIPPING) +USTRUCT() +struct FNiagaraMaterialOverride +{ + GENERATED_USTRUCT_BODY(); + + UPROPERTY() + class UMaterialInterface* Material; + + UPROPERTY() + uint32 MaterialSubIndex; + + UPROPERTY() + UNiagaraRendererProperties* EmitterRendererProperty; +}; + /** * UNiagaraComponent is the primitive component for a Niagara System. * @see ANiagaraActor @@ -122,6 +137,7 @@ private: UPROPERTY() uint32 bRenderingEnabled : 1; + //~ Begin UActorComponent Interface. protected: virtual void OnRegister() override; @@ -131,6 +147,8 @@ protected: virtual void SendRenderDynamicData_Concurrent() override; virtual void BeginDestroy() override; //virtual void OnAttachmentChanged() override; + + void UpdateEmitterMaterials(); public: /** * True if we should automatically attach to AutoAttachParent when activated, and detach from our parent when completed. @@ -156,6 +174,10 @@ public: UPROPERTY() float MaxTimeBeforeForceUpdateTransform; + + UPROPERTY(transient, duplicatetransient) + TArray EmitterMaterials; + /** How to handle pooling for this component instance. */ ENCPoolMethod PoolingMethod; @@ -181,6 +203,8 @@ public: void RegisterWithScalabilityManager(); void UnregisterWithScalabilityManager(); + void PostSystemTick_GameThread(); + public: virtual void SetComponentTickEnabled(bool bEnabled) override; @@ -549,6 +573,10 @@ public: FORCEINLINE bool IsRegisteredWithScalabilityManager()const { return ScalabilityManagerHandle != INDEX_NONE; } FORCEINLINE int32 GetScalabilityManagerHandle()const { return ScalabilityManagerHandle; } + + FORCEINLINE void BeginUpdateContextReset(){ bDuringUpdateContextReset = true; } + FORCEINLINE void EndUpdateContextReset(){ bDuringUpdateContextReset = false; } + private: /** Did we try and activate but fail due to the asset being not yet ready. Keep looping.*/ uint32 bAwaitingActivationDueToNotReady : 1; @@ -567,6 +595,9 @@ private: /** Flag to mark us as currently changing auto attachment as part of Activate/Deactivate so we don't reset in the OnAttachmentChanged() callback. */ //uint32 bIsChangingAutoAttachment : 1; + /** True if we're currently inside an update context reset. This will prevent us from doing some completion work such as releaseing to the pool or auto destroy etc during a reset. */ + uint32 bDuringUpdateContextReset : 1; + /** Restore relative transform from auto attachment and optionally detach from parent (regardless of whether it was an auto attachment). */ void CancelAutoAttachment(bool bDetachFromParent); @@ -579,6 +610,9 @@ private: FDelegateHandle AssetExposedParametersChangedHandle; int32 ScalabilityManagerHandle; + + float ForceUpdateTransformTime; + FBox CurrLocalBounds; }; #if WITH_NIAGARA_COMPONENT_PREVIEW_DATA diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponentPool.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponentPool.h index edcf85391cdf..9b709273c8d2 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponentPool.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponentPool.h @@ -91,7 +91,7 @@ struct FNCPool public: FNCPool(); - void Cleanup(); + void Cleanup(bool bFreeOnly); /** Gets a component from the pool ready for use. */ UNiagaraComponent* Acquire(UWorld* World, UNiagaraSystem* Template, ENCPoolMethod PoolingMethod, bool bForceNew=false); @@ -124,7 +124,10 @@ public: ~UNiagaraComponentPool(); - void Cleanup(); + void Cleanup(bool bFreeOnly=false); + + /** Clear all free entires of the specified system. */ + void ClearPool(UNiagaraSystem* System); void PrimePool(UNiagaraSystem* Template, UWorld* World); UNiagaraComponent* CreateWorldParticleSystem(UNiagaraSystem* Template, UWorld* World, ENCPoolMethod PoolingMethod); @@ -138,6 +141,9 @@ public: /** Notification that the component is being destroyed but has relevance to the component pool. */ void PooledComponentDestroyed(UNiagaraComponent* Component); + /** Remove any components that are using this system. */ + void RemoveComponentsBySystem(UNiagaraSystem* System); + /** Dumps the current state of the pool to the log. */ void Dump(); }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponentRendererProperties.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponentRendererProperties.h index abe5eb17c72e..8458f8566dec 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponentRendererProperties.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponentRendererProperties.h @@ -32,6 +32,9 @@ struct FNiagaraComponentPropertyBinding UPROPERTY() FName MetadataSetterName; + UPROPERTY(Transient) + FNiagaraVariable WritableValue; + UFunction* SetterFunction = nullptr; }; @@ -44,6 +47,7 @@ public: UNiagaraComponentRendererProperties(); //UObject Interface + virtual void PostLoad() override; virtual void PostInitProperties() override; virtual void PostDuplicate(bool bDuplicateForPIE) override; //UObject Interface END @@ -51,7 +55,7 @@ public: static void InitCDOPropertiesAfterModuleStartup(); //~ UNiagaraRendererProperties interface - virtual FNiagaraRenderer* CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter) override; + virtual FNiagaraRenderer* CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) override; virtual class FNiagaraBoundsCalculator* CreateBoundsCalculator() override { return nullptr; } virtual bool IsSimTargetSupported(ENiagaraSimTarget InSimTarget) const override { return (InSimTarget == ENiagaraSimTarget::CPUSim); }; virtual void GetUsedMaterials(const FNiagaraEmitterInstance* InEmitter, TArray& OutMaterials) const override {}; @@ -105,6 +109,11 @@ public: static FNiagaraTypeDefinition GetFColorDef(); static FNiagaraTypeDefinition GetFRotatorDef(); + virtual void CacheFromCompiledData(const FNiagaraDataSetCompiledData* CompiledData) override; + +protected: + virtual void UpdateSourceModeDerivates(ENiagaraRendererSourceDataMode InSourceMode, bool bFromPropertyEdit = false) override; + private: static TArray> ComponentRendererPropertiesToDeferredInit; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponentSettings.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponentSettings.h index e38e66e89e0f..61b553e90a14 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponentSettings.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponentSettings.h @@ -6,20 +6,47 @@ #include "NiagaraComponentSettings.generated.h" +USTRUCT(meta = (DisplayName = "Emitter Name Settings Reference")) +struct FNiagaraEmitterNameSettingsRef +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY(EditAnywhere, Category = Parameters) + FName SystemName; + + UPROPERTY(EditAnywhere, Category = Parameters) + FString EmitterName; + + FORCEINLINE bool operator==(const FNiagaraEmitterNameSettingsRef& Other)const + { + return SystemName == Other.SystemName && EmitterName == Other.EmitterName; + } + + FORCEINLINE bool operator!=(const FNiagaraEmitterNameSettingsRef& Other)const + { + return !(*this == Other); + } +}; + +FORCEINLINE uint32 GetTypeHash(const FNiagaraEmitterNameSettingsRef& Var) +{ + return HashCombine(GetTypeHash(Var.SystemName), GetTypeHash(Var.EmitterName)); +} + UCLASS(config=Game, defaultconfig) class NIAGARA_API UNiagaraComponentSettings : public UObject { GENERATED_UCLASS_BODY() public: - FORCEINLINE static bool ShouldSupressActivation(const UNiagaraSystem* System) + FORCEINLINE static bool ShouldSuppressActivation(const UNiagaraSystem* System) { check(System != nullptr); - if ( bAllowSupressActivation ) + if ( bAllowSuppressActivation ) { if (const UNiagaraComponentSettings* ComponentSettings = GetDefault()) { - if (ComponentSettings->SupressActivationList.Contains(System->GetFName())) + if (ComponentSettings->SuppressActivationList.Contains(System->GetFName())) { return true; } @@ -45,7 +72,7 @@ public: } UPROPERTY(config) - TSet SupressActivationList; + TSet SuppressActivationList; UPROPERTY(config) TSet ForceAutoPooolingList; @@ -53,6 +80,15 @@ public: //UPROPERTY(config) //TSet ForceLatencyList; - static int32 bAllowSupressActivation; + static int32 bAllowSuppressActivation; static int32 bAllowForceAutoPooling; + + + /** + Config file to tweak individual emitters being disabled. Syntax is as follows for the config file: + [/Script/Niagara.NiagaraComponentSettings] + SuppressEmitterList=((SystemName="BasicSpriteSystem",EmitterName="BasicSprite001")) + */ + UPROPERTY(config) + TSet SuppressEmitterList; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraLightRendererProperties.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraLightRendererProperties.h index 0808caa65a3a..17adebea439f 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraLightRendererProperties.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraLightRendererProperties.h @@ -21,13 +21,14 @@ public: UNiagaraLightRendererProperties(); //UObject Interface + virtual void PostLoad() override; virtual void PostInitProperties() override; //UObject Interface END static void InitCDOPropertiesAfterModuleStartup(); //~ UNiagaraRendererProperties interface - virtual FNiagaraRenderer* CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter) override; + virtual FNiagaraRenderer* CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) override; virtual class FNiagaraBoundsCalculator* CreateBoundsCalculator() override { return nullptr; } virtual void GetUsedMaterials(const FNiagaraEmitterInstance* InEmitter, TArray& OutMaterials) const override; virtual bool IsSimTargetSupported(ENiagaraSimTarget InSimTarget) const override { return (InSimTarget == ENiagaraSimTarget::CPUSim); }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraMeshRendererProperties.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraMeshRendererProperties.h index 85fd9a9c22cd..703b848bc6bb 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraMeshRendererProperties.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraMeshRendererProperties.h @@ -97,6 +97,7 @@ public: UNiagaraMeshRendererProperties(); //UObject Interface + virtual void PostLoad() override; virtual void PostInitProperties() override; virtual void Serialize(FArchive& Ar) override; #if WITH_EDITORONLY_DATA @@ -109,11 +110,10 @@ public: static void InitCDOPropertiesAfterModuleStartup(); //~ UNiagaraRendererProperties interface - virtual FNiagaraRenderer* CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter) override; + virtual FNiagaraRenderer* CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) override; virtual class FNiagaraBoundsCalculator* CreateBoundsCalculator() override; virtual void GetUsedMaterials(const FNiagaraEmitterInstance* InEmitter, TArray& OutMaterials) const override; virtual bool IsSimTargetSupported(ENiagaraSimTarget InSimTarget) const override { return true; }; - virtual void PostLoad(); #if WITH_EDITORONLY_DATA virtual bool IsMaterialValidForRenderer(UMaterial* Material, FText& InvalidMessage) override; @@ -123,6 +123,7 @@ public: virtual void GetRendererTooltipWidgets(const FNiagaraEmitterInstance* InEmitter, TArray>& OutWidgets, TSharedPtr InThumbnailPool) const override; virtual void GetRendererFeedback(const UNiagaraEmitter* InEmitter, TArray& OutErrors, TArray& OutWarnings, TArray& OutInfo) const override; void OnMeshChanged(); + void OnMeshPostBuild(UStaticMesh*); void CheckMaterialUsage(); #endif // WITH_EDITORONLY_DATA virtual void CacheFromCompiledData(const FNiagaraDataSetCompiledData* CompiledData) override; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraModule.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraModule.h index 8ebca24805e0..b651799bc981 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraModule.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraModule.h @@ -126,6 +126,7 @@ public: FORCEINLINE static const FNiagaraVariable& GetVar_Emitter_SpawnInterval() { return Emitter_SpawnInterval; } FORCEINLINE static const FNiagaraVariable& GetVar_Emitter_SimulationTarget() { return Emitter_SimulationTarget; } FORCEINLINE static const FNiagaraVariable& GetVar_ScriptUsage() { return ScriptUsage; } + FORCEINLINE static const FNiagaraVariable& GetVar_ScriptContext() { return ScriptContext; } FORCEINLINE static const FNiagaraVariable& GetVar_Emitter_InterpSpawnStartDt() { return Emitter_InterpSpawnStartDt; } FORCEINLINE static const FNiagaraVariable& GetVar_Emitter_SpawnGroup() { return Emitter_SpawnGroup; } @@ -159,6 +160,10 @@ public: FORCEINLINE static const FNiagaraVariable& GetVar_Particles_RibbonTwist() { return Particles_RibbonTwist; } FORCEINLINE static const FNiagaraVariable& GetVar_Particles_RibbonFacing() { return Particles_RibbonFacing; } FORCEINLINE static const FNiagaraVariable& GetVar_Particles_RibbonLinkOrder() { return Particles_RibbonLinkOrder; } + FORCEINLINE static const FNiagaraVariable& GetVar_Particles_RibbonU0Override() { return Particles_RibbonU0Override; } + FORCEINLINE static const FNiagaraVariable& GetVar_Particles_RibbonV0RangeOverride() { return Particles_RibbonV0RangeOverride; } + FORCEINLINE static const FNiagaraVariable& GetVar_Particles_RibbonU1Override() { return Particles_RibbonU1Override; } + FORCEINLINE static const FNiagaraVariable& GetVar_Particles_RibbonV1RangeOverride() { return Particles_RibbonV1RangeOverride; } FORCEINLINE static const FNiagaraVariable& GetVar_Particles_VisibilityTag() { return Particles_VisibilityTag; } FORCEINLINE static const FNiagaraVariable& GetVar_Particles_ComponentsEnabled() { return Particles_ComponentsEnabled; } @@ -262,8 +267,13 @@ private: static FNiagaraVariable Particles_RibbonFacing; static FNiagaraVariable Particles_RibbonLinkOrder; static FNiagaraVariable Particles_ComponentsEnabled; + static FNiagaraVariable Particles_RibbonU0Override; + static FNiagaraVariable Particles_RibbonV0RangeOverride; + static FNiagaraVariable Particles_RibbonU1Override; + static FNiagaraVariable Particles_RibbonV1RangeOverride; static FNiagaraVariable ScriptUsage; + static FNiagaraVariable ScriptContext; static FNiagaraVariable DataInstance_Alive; static FNiagaraVariable Translator_BeginDefaults; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraParameterStore.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraParameterStore.h index 9c88e5d7bbae..13b557c52531 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraParameterStore.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraParameterStore.h @@ -123,10 +123,27 @@ struct FNiagaraVariableWithOffset : public FNiagaraVariableBase FORCEINLINE FNiagaraVariableWithOffset(const FNiagaraVariableWithOffset& InRef) : FNiagaraVariableBase(InRef.GetType(), InRef.GetName()), Offset(InRef.Offset) {} FORCEINLINE FNiagaraVariableWithOffset(const FNiagaraVariableBase& InVariable, int32 InOffset) : FNiagaraVariableBase(InVariable.GetType(), InVariable.GetName()), Offset(InOffset) {} + bool Serialize(FArchive& Ar); +#if WITH_EDITORONLY_DATA + void PostSerialize(const FArchive& Ar); +#endif + UPROPERTY() int32 Offset; }; +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithSerializer = true, +#if WITH_EDITORONLY_DATA + WithPostSerialize = true, +#endif + }; +}; + /** Base storage class for Niagara parameter values. */ USTRUCT() struct NIAGARA_API FNiagaraParameterStore @@ -285,6 +302,9 @@ public: FORCEINLINE const TArray& GetDataInterfaces()const { return DataInterfaces; } FORCEINLINE const TArray& GetParameterDataArray()const { return ParameterData; } + FORCEINLINE int32 Num() const {return SortedParameterOffsets.Num(); } + FORCEINLINE bool IsEmpty() const { return SortedParameterOffsets.Num() == 0; } + virtual void SanityCheckData(bool bInitInterfaces = true); // Called to initially set up the parameter store to *exactly* match the input store (other than any bindings and the internal name of it). @@ -394,7 +414,21 @@ public: if (Parameter.IsDataInterface()) { ensure(DestStore.DataInterfaces.IsValidIndex(DestIndex)); - DataInterfaces[SrcIndex]->CopyTo(DestStore.DataInterfaces[DestIndex]); + UNiagaraDataInterface* DestDataInterface = DestStore.DataInterfaces[DestIndex]; + if (DestDataInterface == nullptr) + { + if (ensureMsgf(DestStore.Owner != nullptr, TEXT("Destination data interface pointer was null and a new one couldn't be created because the destination store's owner pointer was also null."))) + { + UE_LOG(LogNiagara, Warning, TEXT("While trying to copy parameter data the destination data interface was null, creating a new one. Parameter: %s Destination Store Owner: %s"), *Parameter.GetName().ToString(), *DestStore.Owner->GetPathName()); + DestDataInterface = NewObject(DestStore.Owner, Parameter.GetType().GetClass()); + DestStore.DataInterfaces[DestIndex] = DestDataInterface; + } + else + { + return; + } + } + DataInterfaces[SrcIndex]->CopyTo(DestDataInterface); DestStore.OnInterfaceChange(); } else if (Parameter.IsUObject()) diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRenderer.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRenderer.h index f025529e4f85..ef3b6fa79515 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRenderer.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRenderer.h @@ -33,7 +33,7 @@ struct FNiagaraDynamicDataBase FNiagaraDynamicDataBase(FNiagaraDynamicDataBase& Other) = delete; FNiagaraDynamicDataBase& operator=(const FNiagaraDynamicDataBase& Other) = delete; - FNiagaraDataBuffer* GetParticleDataToRender(bool bUseTranslucent = false) const; + FNiagaraDataBuffer* GetParticleDataToRender(bool bIsLowLatencyTranslucent = false) const; FORCEINLINE ENiagaraSimTarget GetSimTarget() const { return SimTarget; } FORCEINLINE FMaterialRelevance GetMaterialRelevance() const { return MaterialRelevance; } @@ -76,7 +76,7 @@ public: FNiagaraRenderer(const FNiagaraRenderer& Other) = delete; FNiagaraRenderer& operator=(const FNiagaraRenderer& Other) = delete; - virtual void Initialize(const UNiagaraRendererProperties *InProps, const FNiagaraEmitterInstance* Emitter); + virtual void Initialize(const UNiagaraRendererProperties *InProps, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent); virtual void CreateRenderThreadResources(NiagaraEmitterInstanceBatcher* Batcher); virtual void ReleaseRenderThreadResources(); @@ -86,7 +86,7 @@ public: virtual void GatherSimpleLights(FSimpleLightArray& OutParticleLights)const {} virtual int32 GetDynamicDataSize()const { return 0; } - virtual bool IsMaterialValid(UMaterialInterface* Mat)const { return Mat != nullptr; } + virtual bool IsMaterialValid(const UMaterialInterface* Mat)const { return Mat != nullptr; } static void SortIndices(const struct FNiagaraGPUSortInfo& SortInfo, const FNiagaraRendererVariableInfo& SortVariable, const FNiagaraDataBuffer& Buffer, FGlobalDynamicReadBuffer::FAllocation& OutIndices); @@ -112,8 +112,12 @@ public: FORCEINLINE ENiagaraSimTarget GetSimTarget() const { return SimTarget; } + virtual void GetUsedMaterials(TArray& UsedMaterials, bool bGetDebugMaterials) { UsedMaterials.Append(BaseMaterials_GT); } protected: + virtual void ProcessMaterialParameterBindings(TConstArrayView< FNiagaraMaterialAttributeBinding > InMaterialParameterBindings, const FNiagaraEmitterInstance* InEmitter, TConstArrayView InMaterials) const; + + struct FNiagaraDynamicDataBase *DynamicDataRender; #if RHI_RAYTRACING diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererComponents.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererComponents.h index 7169beea1bf5..50be41bf1d3f 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererComponents.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererComponents.h @@ -26,7 +26,7 @@ public: FNiagaraRendererComponents(ERHIFeatureLevel::Type FeatureLevel, const UNiagaraRendererProperties *InProps, const FNiagaraEmitterInstance* Emitter); //FNiagaraRenderer interface - virtual void Initialize(const UNiagaraRendererProperties* InProps, const FNiagaraEmitterInstance* Emitter) override; + virtual void Initialize(const UNiagaraRendererProperties* InProps, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) override; virtual FNiagaraDynamicDataBase *GenerateDynamicData(const FNiagaraSceneProxy* Proxy, const UNiagaraRendererProperties* InProperties, const FNiagaraEmitterInstance* Emitter) const override; //FNiagaraRenderer interface END diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererMeshes.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererMeshes.h index ebdf7e164480..1237d4bbc757 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererMeshes.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererMeshes.h @@ -26,7 +26,7 @@ public: virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector, const FNiagaraSceneProxy *SceneProxy) const override; virtual FNiagaraDynamicDataBase* GenerateDynamicData(const FNiagaraSceneProxy* Proxy, const UNiagaraRendererProperties* InProperties, const FNiagaraEmitterInstance* Emitter) const override; virtual int32 GetDynamicDataSize()const override; - virtual bool IsMaterialValid(UMaterialInterface* Mat)const override; + virtual bool IsMaterialValid(const UMaterialInterface* Mat)const override; #if RHI_RAYTRACING virtual void GetDynamicRayTracingInstances(FRayTracingMaterialGatheringContext& Context, TArray& OutRayTracingInstances, const FNiagaraSceneProxy* Proxy) final override; #endif diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererProperties.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererProperties.h index 4062a325fcbf..ebc31850663c 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererProperties.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererProperties.h @@ -25,6 +25,8 @@ struct FSlateBrush; extern int32 GbEnableMinimalGPUBuffers; + + /** Mapping between a variable in the source dataset and the location we place it in the GPU buffer passed to the VF. */ struct FNiagaraRendererVariableInfo { @@ -60,6 +62,7 @@ struct FNiagaraRendererLayout { void Initialize(int32 NumVariables); bool SetVariable(const FNiagaraDataSetCompiledData* CompiledData, const FNiagaraVariable& Variable, int32 VFVarOffset); + bool SetVariableFromBinding(const FNiagaraDataSetCompiledData* CompiledData, const FNiagaraVariableAttributeBinding& VariableBinding, int32 VFVarOffset); void Finalize(); TConstArrayView GetVFVariables_RenderThread() const { check(IsInRenderingThread()); return MakeArrayView(VFVariables_RT); } @@ -94,8 +97,11 @@ public: { } + + virtual void PostInitProperties() override; + //UObject Interface End - virtual FNiagaraRenderer* CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter) PURE_VIRTUAL ( UNiagaraRendererProperties::CreateEmitterRenderer, return nullptr;); + virtual FNiagaraRenderer* CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) PURE_VIRTUAL ( UNiagaraRendererProperties::CreateEmitterRenderer, return nullptr;); virtual class FNiagaraBoundsCalculator* CreateBoundsCalculator() PURE_VIRTUAL(UNiagaraRendererProperties::CreateBoundsCalculator, return nullptr;); virtual void GetUsedMaterials(const FNiagaraEmitterInstance* InEmitter, TArray& OutMaterials) const PURE_VIRTUAL(UNiagaraRendererProperties::GetUsedMaterials,); @@ -104,10 +110,15 @@ public: const TArray& GetAttributeBindings() const { return AttributeBindings; } uint32 ComputeMaxUsedComponents(const FNiagaraDataSetCompiledData* CompiledDataSetData) const; - virtual bool NeedsLoadForTargetPlatform(const class ITargetPlatform* TargetPlatform) const override; + virtual bool NeedsLoadForTargetPlatform(const class ITargetPlatform* TargetPlatform) const override; + /** In the case that we need parameters bound in that aren't Particle variables, these should be set up here so that the data is appropriately populated after the simulation.*/ + virtual bool PopulateRequiredBindings(FNiagaraParameterStore& InParameterStore) { return false; } + #if WITH_EDITORONLY_DATA + virtual bool IsSupportedVariableForBinding(const FNiagaraVariableBase& InSourceForBinding, const FName& InTargetBindingName) const; + virtual void RenameEmitter(const FName& InOldName, const UNiagaraEmitter* InRenamedEmitter) {}; virtual bool IsMaterialValidForRenderer(UMaterial* Material, FText& InvalidMessage) { return true; } virtual void FixMaterial(UMaterial* Material) { } @@ -133,6 +144,8 @@ public: #endif // WITH_EDITORONLY_DATA + virtual ENiagaraRendererSourceDataMode GetCurrentSourceMode() const { return ENiagaraRendererSourceDataMode::Particles;} + // GPU simulation uses DrawIndirect, so the sim step needs to know indices per instance in order to prepare the draw call parameters virtual uint32 GetNumIndicesPerInstance() const { return 0; } @@ -142,6 +155,8 @@ public: virtual void SetIsEnabled(bool bInIsEnabled) { bIsEnabled = bInIsEnabled; } virtual void CacheFromCompiledData(const FNiagaraDataSetCompiledData* CompiledData) {} + + virtual bool NeedsMIDsForMaterials() const { return false; } /** Platforms on which this renderer is enabled. */ UPROPERTY(EditAnywhere, Category = "Scalability") @@ -162,6 +177,9 @@ public: protected: TArray AttributeBindings; + virtual void PostLoadBindings(ENiagaraRendererSourceDataMode InSourceMode); + virtual void UpdateSourceModeDerivates(ENiagaraRendererSourceDataMode InSourceMode, bool bFromPropertyEdit = false); + // Copy of variables in the attribute binding, updated when GetBoundAttributes() is called. TArray CurrentBoundAttributes; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererRibbons.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererRibbons.h index c84d554b80ff..f899e60c23b4 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererRibbons.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererRibbons.h @@ -28,7 +28,7 @@ public: virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector, const FNiagaraSceneProxy *SceneProxy) const override; virtual FNiagaraDynamicDataBase *GenerateDynamicData(const FNiagaraSceneProxy* Proxy, const UNiagaraRendererProperties* InProperties, const FNiagaraEmitterInstance* Emitteride) const override; virtual int32 GetDynamicDataSize()const override; - virtual bool IsMaterialValid(UMaterialInterface* Mat)const override; + virtual bool IsMaterialValid(const UMaterialInterface* Mat)const override; #if RHI_RAYTRACING virtual void GetDynamicRayTracingInstances(FRayTracingMaterialGatheringContext& Context, TArray& OutRayTracingInstances, const FNiagaraSceneProxy* Proxy) final override; #endif @@ -77,14 +77,8 @@ private: FCPUSimParticleDataAllocation AllocateParticleDataIfCPUSim(struct FNiagaraDynamicDataRibbon* DynamicDataRibbon, FGlobalDynamicReadBuffer& DynamicReadBuffer) const; ENiagaraRibbonFacingMode FacingMode; - float UV0TilingDistance; - FVector2D UV0Scale; - FVector2D UV0Offset; - ENiagaraRibbonAgeOffsetMode UV0AgeOffsetMode; - float UV1TilingDistance; - FVector2D UV1Scale; - FVector2D UV1Offset; - ENiagaraRibbonAgeOffsetMode UV1AgeOffsetMode; + FNiagaraRibbonUVSettings UV0Settings; + FNiagaraRibbonUVSettings UV1Settings; ENiagaraRibbonDrawDirection DrawDirection; ENiagaraRibbonTessellationMode TessellationMode; float CustomCurveTension; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererSprites.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererSprites.h index 9407f2c2d2ed..078ba3a580a5 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererSprites.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRendererSprites.h @@ -27,7 +27,7 @@ public: virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector, const FNiagaraSceneProxy *SceneProxy) const override; virtual FNiagaraDynamicDataBase* GenerateDynamicData(const FNiagaraSceneProxy* Proxy, const UNiagaraRendererProperties* InProperties, const FNiagaraEmitterInstance* Emitter) const override; virtual int GetDynamicDataSize()const override; - virtual bool IsMaterialValid(UMaterialInterface* Mat)const override; + virtual bool IsMaterialValid(const UMaterialInterface* Mat)const override; #if RHI_RAYTRACING virtual void GetDynamicRayTracingInstances(FRayTracingMaterialGatheringContext& Context, TArray& OutRayTracingInstances, const FNiagaraSceneProxy* Proxy) final override; @@ -42,7 +42,7 @@ private: }; FCPUSimParticleDataAllocation ConditionalAllocateCPUSimParticleData(FNiagaraDynamicDataSprites *DynamicDataSprites, const FNiagaraRendererLayout* RendererLayout, FGlobalDynamicReadBuffer& DynamicReadBuffer) const; - TUniformBufferRef CreatePerViewUniformBuffer(const FSceneView* View, const FSceneViewFamily& ViewFamily, const FNiagaraSceneProxy *SceneProxy, const FNiagaraRendererLayout* RendererLayout) const; + TUniformBufferRef CreatePerViewUniformBuffer(const FSceneView* View, const FSceneViewFamily& ViewFamily, const FNiagaraSceneProxy *SceneProxy, const FNiagaraRendererLayout* RendererLayout, const FNiagaraDynamicDataSprites* DynamicDataSprites) const; void SetVertexFactoryParticleData( class FNiagaraSpriteVertexFactory& VertexFactory, FNiagaraDynamicDataSprites *DynamicDataSprites, @@ -65,6 +65,7 @@ private: ) const; //Cached data from the properties struct. + ENiagaraRendererSourceDataMode SourceMode; ENiagaraSpriteAlignment Alignment; ENiagaraSpriteFacingMode FacingMode; FVector2D PivotInUVSpace; @@ -73,12 +74,16 @@ private: uint32 bSubImageBlend : 1; uint32 bRemoveHMDRollInVR : 1; uint32 bSortOnlyWhenTranslucent : 1; + uint32 bGpuLowLatencyTranslucency : 1; float MinFacingCameraBlendDistance; float MaxFacingCameraBlendDistance; FNiagaraCutoutVertexBuffer CutoutVertexBuffer; int32 NumCutoutVertexPerSubImage = 0; uint32 MaterialParamValidMask = 0; + int32 VFBoundOffsetsInParamStore[ENiagaraSpriteVFLayout::Type::Num]; + uint32 bSetAnyBoundVars : 1; + const FNiagaraRendererLayout* RendererLayoutWithCustomSort; const FNiagaraRendererLayout* RendererLayoutWithoutCustomSort; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRibbonRendererProperties.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRibbonRendererProperties.h index fd12b947dbdf..af876f99aee4 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRibbonRendererProperties.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraRibbonRendererProperties.h @@ -57,6 +57,72 @@ enum class ENiagaraRibbonTessellationMode : uint8 Disabled }; +/** Specifies options for handling UVs at the leading and trailing edges of ribbons. */ +UENUM() +enum class ENiagaraRibbonUVEdgeMode +{ + /** The UV value at the edge will smoothly transition across the segment using normalized age. + This will result in UV values which are outside of the standard 0-1 range and works best with + clamped textures. */ + SmoothTransition, + /** The UV value at the edge will be locked to 0 at the leading edge, or locked to 1 at the + Trailing edge. */ + Locked, +}; + +/** Specifies options for distributing UV values across ribbon segments. */ +UENUM() +enum class ENiagaraRibbonUVDistributionMode +{ + /** Ribbon UVs will be scaled to the 0-1 range and distributed evenly across uv segments regardless of segment length. */ + ScaledUniformly, + /** Ribbon UVs will be scaled to the 0-1 range and will be distributed along the ribbon segments based on their length, i.e. short segments get less UV range and large segments get more. */ + ScaledUsingRibbonSegmentLength, + /** Ribbon UVs will be tiled along the length of the ribbon based on segment length and the Tile Over Length Scale value. NOTE: This is not equivalent to distance tiling which tiles over owner distance traveled, this requires per particle U override values and can be setup with modules. */ + TiledOverRibbonLength +}; + +/** Defines settings for UV behavior for a UV channel on ribbons. */ +USTRUCT() +struct FNiagaraRibbonUVSettings +{ + GENERATED_BODY(); + + FNiagaraRibbonUVSettings(); + + /** Specifies how UVs behave at the leading edge of the ribbon where particles are being added. */ + UPROPERTY(EditAnywhere, Category = UVs) + ENiagaraRibbonUVEdgeMode LeadingEdgeMode; + + /** Specifies how UVs behave at the trailing edge of the ribbon where particles are being removed. */ + UPROPERTY(EditAnywhere, Category = UVs) + ENiagaraRibbonUVEdgeMode TrailingEdgeMode; + + /** Specifies how ribbon UVs are distributed along the length of a ribbon. */ + UPROPERTY(EditAnywhere, Category = UVs) + ENiagaraRibbonUVDistributionMode DistributionMode; + + /** Specifies the length in world units to use when tiling UVs across the ribbon when using the tiled distribution mode. */ + UPROPERTY(EditAnywhere, Category = UVs) + float TilingLength; + + /** Specifies and additional offsets which are applied to the UV range */ + UPROPERTY(EditAnywhere, Category = UVs) + FVector2D Offset; + + /** Specifies and additional scalers which are applied to the UV range. */ + UPROPERTY(EditAnywhere, Category = UVs) + FVector2D Scale; + + /** Enables overriding overriding the U componenet with values read from the particles. When enabled edge behavior and distribution are ignored. */ + UPROPERTY(EditAnywhere, Category = UVs) + bool bEnablePerParticleUOverride; + + /** Enables overriding the range of the V component with values read from the particles. */ + UPROPERTY(EditAnywhere, Category = UVs) + bool bEnablePerParticleVRangeOverride; +}; + namespace ENiagaraRibbonVFLayout { enum Type @@ -73,6 +139,10 @@ namespace ENiagaraRibbonVFLayout MaterialParam1, MaterialParam2, MaterialParam3, + U0Override, + V0RangeOverride, + U1Override, + V1RangeOverride, Num, }; }; @@ -97,7 +167,7 @@ public: static void InitCDOPropertiesAfterModuleStartup(); //UNiagaraRendererProperties Interface - virtual FNiagaraRenderer* CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter) override; + virtual FNiagaraRenderer* CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) override; virtual class FNiagaraBoundsCalculator* CreateBoundsCalculator() override; virtual void GetUsedMaterials(const FNiagaraEmitterInstance* InEmitter, TArray& OutMaterials) const override; virtual bool IsSimTargetSupported(ENiagaraSimTarget InSimTarget) const override { return (InSimTarget == ENiagaraSimTarget::CPUSim); }; @@ -122,29 +192,40 @@ public: UPROPERTY(EditAnywhere, Category = "Ribbon Rendering") ENiagaraRibbonFacingMode FacingMode; + UPROPERTY(EditAnywhere, Category = "Ribbon Rendering") + FNiagaraRibbonUVSettings UV0Settings; + + UPROPERTY(EditAnywhere, Category = "Ribbon Rendering") + FNiagaraRibbonUVSettings UV1Settings; + +#if WITH_EDITORONLY_DATA +private: /** Tiles UV0 based on the distance traversed by the ribbon. Disables offsetting UVs by age. */ - UPROPERTY(EditAnywhere, Category = "Ribbon Rendering") - float UV0TilingDistance; - UPROPERTY(EditAnywhere, Category = "Ribbon Rendering") - FVector2D UV0Scale; - UPROPERTY(EditAnywhere, Category = "Ribbon Rendering") - FVector2D UV0Offset; + UPROPERTY() + float UV0TilingDistance_DEPRECATED; + UPROPERTY() + FVector2D UV0Scale_DEPRECATED; + UPROPERTY() + FVector2D UV0Offset_DEPRECATED; /** Defines the mode to use when offsetting UV channel 0 by age which enables smooth texture movement when particles are added and removed at the end of the ribbon. Not used when the RibbonLinkOrder binding is in use or when tiling distance is in use. */ - UPROPERTY(EditAnywhere, Category = "Ribbon Rendering") - ENiagaraRibbonAgeOffsetMode UV0AgeOffsetMode; + UPROPERTY() + ENiagaraRibbonAgeOffsetMode UV0AgeOffsetMode_DEPRECATED; /** Tiles UV1 based on the distance traversed by the ribbon. Disables offsetting UVs by age. */ - UPROPERTY(EditAnywhere, Category = "Ribbon Rendering") - float UV1TilingDistance; - UPROPERTY(EditAnywhere, Category = "Ribbon Rendering") - FVector2D UV1Scale; - UPROPERTY(EditAnywhere, Category = "Ribbon Rendering") - FVector2D UV1Offset; + UPROPERTY() + float UV1TilingDistance_DEPRECATED; + UPROPERTY() + FVector2D UV1Scale_DEPRECATED; + UPROPERTY() + FVector2D UV1Offset_DEPRECATED; /** Defines the mode to use when offsetting UV channel 1 by age which enables smooth texture movement when particles are added and removed at the end of the ribbon. Not used when the RibbonLinkOrder binding is in use or when tiling distance is in use. */ - UPROPERTY(EditAnywhere, Category = "Ribbon Rendering") - ENiagaraRibbonAgeOffsetMode UV1AgeOffsetMode; + UPROPERTY() + ENiagaraRibbonAgeOffsetMode UV1AgeOffsetMode_DEPRECATED; +#endif + +public: /** If true, the particles are only sorted when using a translucent material. */ UPROPERTY(EditAnywhere, Category = "Ribbon Rendering") @@ -237,6 +318,22 @@ public: UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Bindings") FNiagaraVariableAttributeBinding DynamicMaterial3Binding; + /** Which attribute should we use for UV0 U when generating ribbons?*/ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Bindings") + FNiagaraVariableAttributeBinding U0OverrideBinding; + + /** Which attribute should we use for UV0 V when generating ribbons?*/ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Bindings") + FNiagaraVariableAttributeBinding V0RangeOverrideBinding; + + /** Which attribute should we use for UV1 U when generating ribbons?*/ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Bindings") + FNiagaraVariableAttributeBinding U1OverrideBinding; + + /** Which attribute should we use for UV1 V when generating ribbons?*/ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Bindings") + FNiagaraVariableAttributeBinding V1RangeOverrideBinding; + bool bSortKeyDataSetAccessorIsAge = false; FNiagaraDataSetAccessor SortKeyDataSetAccessor; FNiagaraDataSetAccessor PositionDataSetAccessor; @@ -247,6 +344,9 @@ public: FNiagaraDataSetAccessor MaterialParam1DataSetAccessor; FNiagaraDataSetAccessor MaterialParam2DataSetAccessor; FNiagaraDataSetAccessor MaterialParam3DataSetAccessor; + bool U0OverrideIsBound; + bool U1OverrideIsBound; + FNiagaraDataSetAccessor RibbonIdDataSetAccessor; FNiagaraDataSetAccessor RibbonFullIDDataSetAccessor; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSettings.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSettings.h index 7aad0c2e7edf..b43dab13558e 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSettings.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSettings.h @@ -50,7 +50,7 @@ class NIAGARA_API UNiagaraSettings : public UDeveloperSettings virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; public: - DECLARE_MULTICAST_DELEGATE_TwoParams(FOnNiagaraSettingsChanged, const FString&, const UNiagaraSettings*); + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnNiagaraSettingsChanged, const FName&, const UNiagaraSettings*); /** Gets a multicast delegate which is called whenever one of the parameters in this settings object changes. */ static FOnNiagaraSettingsChanged& OnSettingsChanged(); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSimulationStageBase.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSimulationStageBase.h index 49a2698c0b23..eb6b2343ff46 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSimulationStageBase.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSimulationStageBase.h @@ -18,14 +18,26 @@ class NIAGARA_API UNiagaraSimulationStageBase : public UNiagaraMergeable GENERATED_BODY() public: + UNiagaraSimulationStageBase() + { + bEnabled = true; + } + UPROPERTY() UNiagaraScript* Script; UPROPERTY(EditAnywhere, Category = "Simulation Stage") FName SimulationStageName; - virtual bool AppendCompileHash(FNiagaraCompileHashVisitor* InVisitor) const { return true; } + UPROPERTY() + uint32 bEnabled : 1; + virtual bool AppendCompileHash(FNiagaraCompileHashVisitor* InVisitor) const; +#if WITH_EDITOR + void SetEnabled(bool bEnabled); + void RequestRecompile(); + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; +#endif }; UCLASS(meta = (DisplayName = "Generic Simulation Stage")) @@ -49,6 +61,9 @@ public: UPROPERTY(EditAnywhere, Category = "Simulation Stage", meta = (DisplayName = "Emitter Reset Only", Tooltip = "When enabled the stage will only run on the first tick after the emitter is reset, only valid for data interface iteration stages", EditCondition = "IterationSource == ENiagaraIterationSource::DataInterface")) uint32 bSpawnOnly : 1; + UPROPERTY(EditAnywhere, Category = "Simulation Stage", meta = (DisplayName = "Partial Particle Update", Tooltip = "When enabled we will not output all particle variables to improve performance where possible. This option is hazardous if you are reading data from other particles.", EditCondition = "IterationSource == ENiagaraIterationSource::Particles")) + uint32 bPartialParticleUpdate : 1; + UPROPERTY(EditAnywhere, Category = "Simulation Stage", meta = (editcondition = "IterationSource == ENiagaraIterationSource::DataInterface")) FNiagaraVariableDataInterfaceBinding DataInterface; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSpriteRendererProperties.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSpriteRendererProperties.h index 605dda673c56..74a388adec06 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSpriteRendererProperties.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSpriteRendererProperties.h @@ -87,11 +87,13 @@ public: static void InitCDOPropertiesAfterModuleStartup(); //UNiagaraRendererProperties interface - virtual FNiagaraRenderer* CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter) override; + virtual FNiagaraRenderer* CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, const FNiagaraEmitterInstance* Emitter, const UNiagaraComponent* InComponent) override; virtual class FNiagaraBoundsCalculator* CreateBoundsCalculator() override; virtual void GetUsedMaterials(const FNiagaraEmitterInstance* InEmitter, TArray& OutMaterials) const override; - virtual bool IsSimTargetSupported(ENiagaraSimTarget InSimTarget) const override { return true; }; + virtual bool IsSimTargetSupported(ENiagaraSimTarget InSimTarget) const override { return true; }; + virtual bool PopulateRequiredBindings(FNiagaraParameterStore& InParameterStore) override; #if WITH_EDITOR + virtual void RenameEmitter(const FName& InOldName, const UNiagaraEmitter* InRenamedEmitter) override; virtual bool IsMaterialValidForRenderer(UMaterial* Material, FText& InvalidMessage) override; virtual void FixMaterial(UMaterial* Material) override; virtual const TArray& GetOptionalAttributes() override; @@ -99,6 +101,8 @@ public: virtual void GetRendererTooltipWidgets(const FNiagaraEmitterInstance* InEmitter, TArray>& OutWidgets, TSharedPtr InThumbnailPool) const override; virtual void GetRendererFeedback(const UNiagaraEmitter* InEmitter, TArray& OutErrors, TArray& OutWarnings, TArray& OutInfo) const override; #endif + virtual ENiagaraRendererSourceDataMode GetCurrentSourceMode() const override { return SourceMode; } + virtual void CacheFromCompiledData(const FNiagaraDataSetCompiledData* CompiledData) override; //UNiagaraMaterialRendererProperties interface END @@ -109,6 +113,10 @@ public: UPROPERTY(EditAnywhere, Category = "Sprite Rendering") UMaterialInterface* Material; + /** Whether or not to draw a single element for the Emitter or to draw the particles.*/ + UPROPERTY(EditAnywhere, Category = "Sprite Rendering") + ENiagaraRendererSourceDataMode SourceMode; + /** Use the UMaterialInterface bound to this user variable if it is set to a valid value. If this is bound to a valid value and Material is also set, UserParamBinding wins.*/ UPROPERTY(EditAnywhere, Category = "Sprite Rendering") FNiagaraUserParameterBinding MaterialUserParamBinding; @@ -145,6 +153,14 @@ public: UPROPERTY(EditAnywhere, Category = "Sorting") uint32 bSortOnlyWhenTranslucent : 1; + /** + If true and a GPU emitter, we will use the current frames data to render with regardless of where the batcher may execute the dispatches. + If you have other emitters that are not translucent and using data that forces it to be a frame latent (i.e. view uniform buffer) you may need to disable + on renderers with translucent materials if you need the frame they are reading to match exactly. + */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Rendering") + uint32 bGpuLowLatencyTranslucency : 1; + /** When FacingMode is FacingCameraDistanceBlend, the distance at which the sprite is fully facing the camera plane. */ UPROPERTY(EditAnywhere, Category = "Sprite Rendering", meta = (UIMin = "0")) float MinFacingCameraBlendDistance; @@ -221,9 +237,17 @@ public: UPROPERTY(EditAnywhere, Category = "Bindings") FNiagaraVariableAttributeBinding NormalizedAgeBinding; + /** If this array has entries, we will create a MaterialInstanceDynamic per Emitter instance from Material and set the Material parameters using the Niagara simulation variables listed.*/ + UPROPERTY(EditAnywhere, Category = "Bindings") + TArray< FNiagaraMaterialAttributeBinding > MaterialParameterBindings; + void InitBindings(); + virtual bool NeedsMIDsForMaterials() const { return MaterialParameterBindings.Num() > 0; } + + #if WITH_EDITORONLY_DATA + virtual bool IsSupportedVariableForBinding(const FNiagaraVariableBase& InSourceForBinding, const FName& InTargetBindingName) const override; /** Use the cutout texture from the material opacity mask, or if none exist, from the material opacity. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cutout") @@ -260,7 +284,7 @@ public: FNiagaraRendererLayout RendererLayoutWithCustomSort; FNiagaraRendererLayout RendererLayoutWithoutCustomSort; uint32 MaterialParamValidMask = 0; - + private: /** Derived data for this asset, generated off of SubUVTexture. */ FSubUVDerivedData DerivedData; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemInstance.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemInstance.h index 43f81de709eb..2c211f85e960 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemInstance.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemInstance.h @@ -12,7 +12,6 @@ #include "NiagaraDataInterface.h" class FNiagaraWorldManager; -class UNiagaraComponent; class FNiagaraSystemInstance; class FNiagaraSystemSimulation; class NiagaraEmitterInstanceBatcher; @@ -24,10 +23,12 @@ class NIAGARA_API FNiagaraSystemInstance friend class FNiagaraGPUSystemTick; public: + DECLARE_DELEGATE(FOnPostTick); + +#if WITH_EDITOR DECLARE_MULTICAST_DELEGATE(FOnInitialized); DECLARE_MULTICAST_DELEGATE_OneParam(FOnComplete, FNiagaraSystemInstance*); -#if WITH_EDITOR DECLARE_MULTICAST_DELEGATE(FOnReset); DECLARE_MULTICAST_DELEGATE(FOnDestroyed); #endif @@ -39,7 +40,7 @@ public: { /** Resets the System instance and simulations. */ ResetAll, - /** Resets the System instance but not the simualtions */ + /** Resets the System instance but not the simulations */ ResetSystem, /** Full reinitialization of the system and emitters. */ ReInit, @@ -49,8 +50,9 @@ public: FORCEINLINE bool GetAreDataInterfacesInitialized() const { return bDataInterfacesInitialized; } - /** Creates a new niagara System instance with the supplied component. */ - explicit FNiagaraSystemInstance(UNiagaraComponent* InComponent); + /** Creates a new Niagara system instance. */ + FNiagaraSystemInstance(UWorld& InWorld, UNiagaraSystem& InAsset, FNiagaraUserRedirectionParameterStore* InOverrideParameters = nullptr, + USceneComponent* InAttachComponent = nullptr, ENiagaraTickBehavior InTickBehavior = ENiagaraTickBehavior::UsePrereqs, bool bInPooled = false); /** Cleanup*/ virtual ~FNiagaraSystemInstance(); @@ -64,7 +66,7 @@ public: void Deactivate(bool bImmediate = false); void Complete(); - void OnPooledReuse(); + void OnPooledReuse(UWorld& NewWorld); void SetPaused(bool bInPaused); FORCEINLINE bool IsPaused()const { return bPaused; } @@ -109,7 +111,7 @@ public: /** Requests the the simulation be reset on the next tick. */ void Reset(EResetMode Mode); - void ComponentTick(float DeltaSeconds, const FGraphEventRef& MyCompletionGraphEvent); + void ManualTick(float DeltaSeconds, const FGraphEventRef& MyCompletionGraphEvent); /** Initial phase of system instance tick. Must be executed on the game thread. */ void Tick_GameThread(float DeltaSeconds); @@ -165,8 +167,10 @@ public: /** Gets the simulation for the supplied emitter handle. */ TSharedPtr GetSimulationForHandle(const FNiagaraEmitterHandle& EmitterHandle); - UNiagaraSystem* GetSystem()const; - FORCEINLINE UNiagaraComponent *GetComponent() { return Component; } + FORCEINLINE UWorld* GetWorld() const { return World; } + FORCEINLINE UNiagaraSystem* GetSystem() const { return Asset.Get(); } + FORCEINLINE USceneComponent* GetAttachComponent() { return AttachComponent.Get(); } + FORCEINLINE FNiagaraUserRedirectionParameterStore* GetOverrideParameters() { return OverrideParameters; } FORCEINLINE TArray > &GetEmitters() { return Emitters; } FORCEINLINE const TArray >& GetEmitters() const { return Emitters; } FORCEINLINE const FBox& GetLocalBounds() { return LocalBounds; } @@ -178,6 +182,9 @@ public: FORCEINLINE bool NeedsGPUTick()const{ return ActiveGPUEmitterCount > 0 /*&& Component->IsRegistered()*/ && !IsComplete();} + /** Gets a multicast delegate which is called after this instance has finished ticking for the frame on the game thread */ + FORCEINLINE void SetOnPostTick(const FOnPostTick& InPostTickDelegate) { OnPostTickDelegate = InPostTickDelegate; } + #if WITH_EDITOR /** Gets a multicast delegate which is called whenever this instance is initialized with an System asset. */ FOnInitialized& OnInitialized(); @@ -207,16 +214,23 @@ public: return nullptr; } - bool UsesEmitter(const UNiagaraEmitter* Emitter)const; - bool UsesScript(const UNiagaraScript* Script)const; - //bool UsesDataInterface(UNiagaraDataInterface* Interface); - bool UsesCollection(const UNiagaraParameterCollection* Collection)const; + FORCEINLINE const FNiagaraPerInstanceDIFuncInfo& GetPerInstanceDIFunction(ENiagaraSystemSimulationScript ScriptType, int32 FuncIndex)const { return PerInstanceDIFunctions[(int32)ScriptType][FuncIndex]; } - FORCEINLINE bool IsPendingSpawn()const { return bPendingSpawn; } +#if WITH_EDITORONLY_DATA + bool UsesEmitter(const UNiagaraEmitter* Emitter) const; + bool UsesScript(const UNiagaraScript* Script) const; + //bool UsesDataInterface(UNiagaraDataInterface* Interface); + bool UsesCollection(const UNiagaraParameterCollection* Collection) const; +#endif + + FORCEINLINE bool IsPendingSpawn() const { return bPendingSpawn; } FORCEINLINE void SetPendingSpawn(bool bInValue) { bPendingSpawn = bInValue; } - FORCEINLINE float GetAge()const { return Age; } + FORCEINLINE float GetAge() const { return Age; } FORCEINLINE int32 GetTickCount() const { return TickCount; } + + FORCEINLINE float GetLastRenderTime() const { return LastRenderTime; } + FORCEINLINE void SetLastRenderTime(float TimeSeconds) { LastRenderTime = TimeSeconds; } FORCEINLINE TSharedPtr GetSystemSimulation()const { @@ -262,8 +276,10 @@ public: NiagaraEmitterInstanceBatcher* GetBatcher() const { return Batcher; } - static bool AllocateSystemInstance(class UNiagaraComponent* InComponent, TUniquePtr< FNiagaraSystemInstance >& OutSystemInstanceAllocation); - static bool DeallocateSystemInstance(TUniquePtr< FNiagaraSystemInstance >& SystemInstanceAllocation); + static bool AllocateSystemInstance(TUniquePtr& OutSystemInstanceAllocation, UWorld& InWorld, UNiagaraSystem& InAsset, + FNiagaraUserRedirectionParameterStore* InOverrideParameters = nullptr, USceneComponent* InAttachComponent = nullptr, + ENiagaraTickBehavior InTickBehavior = ENiagaraTickBehavior::UsePrereqs, bool bInPooled = false); + static bool DeallocateSystemInstance(TUniquePtr& SystemInstanceAllocation); /*void SetHasGPUEmitters(bool bInHasGPUEmitters) { bHasGPUEmitters = bInHasGPUEmitters; }*/ bool HasGPUEmitters() { return bHasGPUEmitters; } @@ -287,6 +303,7 @@ public: #if WITH_EDITOR void RaiseNeedsUIResync(); + bool HandleNeedsUIResync(); #endif /** Get the current tick behavior */ @@ -302,6 +319,16 @@ public: return ComponentTasks.Enqueue(Task); } + /** Gets the current world transform of the system */ + FORCEINLINE const FTransform& GetWorldTransform() const { return WorldTransform; } + /** Sets the world transform */ + FORCEINLINE void SetWorldTransform(const FTransform& InTransform) { WorldTransform = InTransform; } + + int32 GetSystemInstanceIndex() const + { + return SystemInstanceIndex; + } + private: void DestroyDataInterfaceInstanceData(); @@ -329,15 +356,22 @@ private: TSharedPtr SystemSimulation; - UNiagaraComponent* Component; - + UWorld* World; + TWeakObjectPtr Asset; + FNiagaraUserRedirectionParameterStore* OverrideParameters; + TWeakObjectPtr AttachComponent; UActorComponent* PrereqComponent; + FTransform WorldTransform; + ENiagaraTickBehavior TickBehavior; /** The age of the System instance. */ float Age; + /** The last time this system rendered */ + float LastRenderTime; + /** The tick count of the System instance. */ int32 TickCount; @@ -347,6 +381,8 @@ private: TArray< TSharedRef > Emitters; + FOnPostTick OnPostTickDelegate; + #if WITH_EDITOR FOnInitialized OnInitializedDelegate; FOnComplete OnCompleteDelegate; @@ -359,7 +395,7 @@ private: TSharedPtr>, ESPMode::ThreadSafe> CurrentCapture; TSharedPtr CurrentCaptureGuid; bool bWasSoloPriorToCaptureRequest; - TMap>, ESPMode::ThreadSafe> > CapturedFrames; + TMap>, ESPMode::ThreadSafe>> CapturedFrames; #endif FNiagaraSystemInstanceID ID; @@ -373,6 +409,11 @@ private: /** Map of data interfaces to their instance data. */ TArray, int32>> DataInterfaceInstanceDataOffsets; + /** + A set of function bindings for DI calls that must be made per system instance. + */ + TArray PerInstanceDIFunctions[(int32)ENiagaraSystemSimulationScript::Num]; + /** Per system instance parameters. These can be fed by the component and are placed into a dataset for execution for the system scripts. */ FNiagaraParameterStore InstanceParameters; @@ -405,9 +446,6 @@ private: /** The system contains data interfaces that can have tick group prerequisites. */ uint32 bDataInterfacesHaveTickPrereqs : 1; - /** True if our bounds have changed and we require pushing that to the rendering thread. */ - uint32 bIsTransformDirty : 1; - /** True if we require a call to FinalizeTick_GameThread(). Typically this is called from a GT task but can be called in WaitForAsync. */ uint32 bNeedsFinalize : 1; @@ -417,6 +455,13 @@ private: uint32 bLODDistanceIsValid : 1; + /** True if the system instance is pooled. Prevents unbinding of parameters on completing the system */ + uint32 bPooled : 1; + +#if WITH_EDITOR + uint32 bNeedsUIResync : 1; +#endif + /** If async work was running when we request an Activate we will store the reset mode and perform in finalize to avoid stalling the GameThread. */ EResetMode DeferredResetMode = EResetMode::None; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemSimulation.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemSimulation.h index bfc276e18ce7..0410f8aad6c8 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemSimulation.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemSimulation.h @@ -249,7 +249,6 @@ public: ~FNiagaraSystemSimulation(); bool Init(UNiagaraSystem* InSystem, UWorld* InWorld, bool bInIsSolo, ETickingGroup TickGroup); void Destroy(); - bool Tick(float DeltaSeconds); bool IsValid()const { return WeakSystem.Get() != nullptr && bCanExecute && World != nullptr; } @@ -294,8 +293,8 @@ public: bool GetIsSolo() const { return bIsSolo; } - FNiagaraScriptExecutionContext& GetSpawnExecutionContext() { return SpawnExecContext; } - FNiagaraScriptExecutionContext& GetUpdateExecutionContext() { return UpdateExecContext; } + FNiagaraScriptExecutionContextBase* GetSpawnExecutionContext() { return SpawnExecContext.Get(); } + FNiagaraScriptExecutionContextBase* GetUpdateExecutionContext() { return UpdateExecContext.Get(); } void AddTickGroupPromotion(FNiagaraSystemInstance* Instance); int32 AddPendingSystemInstance(FNiagaraSystemInstance* Instance); @@ -308,6 +307,10 @@ public: ENiagaraGPUTickHandlingMode GetGPUTickHandlingMode()const; + /** If true we use legacy simulation contexts that could not handle per instance DI calls in the system scripts and would force the whole simulation solo. */ + static bool UseLegacySystemSimulationContexts(); + static void OnChanged_UseLegacySystemSimulationContexts(class IConsoleVariable* CVar); + protected: /** Sets constant parameter values */ void SetupParameters_GameThread(float DeltaSeconds); @@ -323,14 +326,9 @@ protected: /** Builds the constant buffer table for a given script execution */ void BuildConstantBufferTable( const FNiagaraGlobalParameters& GlobalParameters, - FNiagaraScriptExecutionContext& ExecContext, + TUniquePtr& ExecContext, FScriptExecutionConstantBufferTable& ConstantBufferTable) const; - /** Should we push the system sim tick off the game thread. */ - FORCEINLINE bool ShouldTickAsync(const FNiagaraSystemSimulationTickContext& Context); - /** Should we push the system instance ticks off the game thread. */ - FORCEINLINE bool ShouldTickInstancesAsync(const FNiagaraSystemSimulationTickContext& Context); - void AddSystemToTickBatch(FNiagaraSystemInstance* Instance, FNiagaraSystemSimulationTickContext& Context); void FlushTickBatch(FNiagaraSystemSimulationTickContext& Context); @@ -361,8 +359,8 @@ protected: FNiagaraDataSet SpawnInstanceParameterDataSet; FNiagaraDataSet UpdateInstanceParameterDataSet; - FNiagaraScriptExecutionContext SpawnExecContext; - FNiagaraScriptExecutionContext UpdateExecContext; + TUniquePtr SpawnExecContext; + TUniquePtr UpdateExecContext; /** Bindings that pull per component parameters into the spawn parameter dataset. */ FNiagaraParameterStoreToDataSetBinding SpawnInstanceParameterToDataSetBinding; @@ -377,6 +375,8 @@ protected: TArray> DataSetToEmitterEventParameters; /** Binding to push system attributes into each emitter gpu parameters. */ TArray DataSetToEmitterGPUParameters; + /** Binding to push system attributes into each emitter renderer parameters. */ + TArray DataSetToEmitterRendererParameters; /** Direct bindings for Engine variables in System Spawn and Update scripts. */ @@ -423,4 +423,6 @@ protected: mutable FString CrashReporterTag; NiagaraEmitterInstanceBatcher* Batcher = nullptr; + + static bool bUseLegacyExecContexts; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraTypes.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraTypes.h index 729769a2013d..8573e3b2e90b 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraTypes.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraTypes.h @@ -348,6 +348,8 @@ enum class ENiagaraExecutionState : uint32 Num UMETA(Hidden) }; + + USTRUCT() struct NIAGARA_API FNiagaraCompileHashVisitorDebugInfo { @@ -989,6 +991,7 @@ public: static const FNiagaraTypeDefinition& GetIDDef() { return IDDef; } static const FNiagaraTypeDefinition& GetUObjectDef() { return UObjectDef; } static const FNiagaraTypeDefinition& GetUMaterialDef() { return UMaterialDef; } + static const FNiagaraTypeDefinition& GetUTextureDef() { return UTextureDef; } static const FNiagaraTypeDefinition& GetHalfDef() { return HalfDef; } static const FNiagaraTypeDefinition& GetHalfVec2Def() { return HalfVec2Def; } @@ -1020,6 +1023,7 @@ public: static UEnum* GetExecutionStateSouceEnum() { return ExecutionStateSourceEnum; } static UEnum* GetSimulationTargetEnum() { return SimulationTargetEnum; } static UEnum* GetScriptUsageEnum() { return ScriptUsageEnum; } + static UEnum* GetScriptContextEnum() { return ScriptContextEnum; } static UEnum* GetParameterPanelCategoryEnum() { return ParameterPanelCategoryEnum; } static UEnum* GetParameterScopeEnum() { return ParameterScopeEnum; } @@ -1060,6 +1064,7 @@ private: static FNiagaraTypeDefinition IDDef; static FNiagaraTypeDefinition UObjectDef; static FNiagaraTypeDefinition UMaterialDef; + static FNiagaraTypeDefinition UTextureDef; static FNiagaraTypeDefinition HalfDef; static FNiagaraTypeDefinition HalfVec2Def; @@ -1084,9 +1089,11 @@ private: static UClass* UObjectClass; static UClass* UMaterialClass; + static UClass* UTextureClass; static UEnum* SimulationTargetEnum; static UEnum* ScriptUsageEnum; + static UEnum* ScriptContextEnum; static UEnum* ExecutionStateEnum; static UEnum* ExecutionStateSourceEnum; @@ -1135,6 +1142,11 @@ const FNiagaraTypeDefinition& FNiagaraTypeDefinition::Get() if (TIsSame::Value) { return FNiagaraTypeDefinition::GetIDDef(); } } +FORCEINLINE uint32 GetTypeHash(const FNiagaraTypeDefinition& Type) +{ + return HashCombine(GetTypeHash(Type.GetStruct()), GetTypeHash(Type.GetEnum())); +} + ////////////////////////////////////////////////////////////////////////// @@ -1145,7 +1157,14 @@ const FNiagaraTypeDefinition& FNiagaraTypeDefinition::Get() class NIAGARA_API FNiagaraTypeRegistry { public: - static const TArray &GetRegisteredTypes() + enum + { + MaxRegisteredTypes = 512, + }; + + using RegisteredTypesArray = TArray>; + + static const RegisteredTypesArray& GetRegisteredTypes() { return RegisteredTypes; } @@ -1174,21 +1193,28 @@ public: static void ClearUserDefinedRegistry() { + FRWScopeLock Lock(RegisteredTypesLock, FRWScopeLockType::SLT_Write); + for (const FNiagaraTypeDefinition& Def : RegisteredUserDefinedTypes) { - RegisteredTypes.Remove(Def); RegisteredPayloadTypes.Remove(Def); RegisteredParamTypes.Remove(Def); + RegisteredNumericTypes.Remove(Def); } - RegisteredNumericTypes.Empty(); RegisteredUserDefinedTypes.Empty(); + + // note that we don't worry about cleaning up RegisteredTypes or RegisteredTypeIndexMap because we don't + // want to invalidate any indexes that are already stored in FNiagaraTypeDefinitionHandle. If re-registered + // they will be given the same index, and if they are orphaned we don't want to have invalid indices on the handle. } static void Register(const FNiagaraTypeDefinition &NewType, bool bCanBeParameter, bool bCanBePayload, bool bIsUserDefined) { + FRWScopeLock Lock(RegisteredTypesLock, FRWScopeLockType::SLT_Write); + //TODO: Make this a map of type to a more verbose set of metadata? Such as the hlsl defs, offset table for conversions etc. - RegisteredTypes.AddUnique(NewType); + RegisteredTypeIndexMap.Add(GetTypeHash(NewType), RegisteredTypes.AddUnique(NewType)); if (bCanBeParameter) { @@ -1211,40 +1237,66 @@ public: } } - static void Deregister(const FNiagaraTypeDefinition& Type) + static int32 RegisterIndexed(const FNiagaraTypeDefinition& NewType) { - RegisteredTypes.Remove(Type); - RegisteredParamTypes.Remove(Type); - RegisteredPayloadTypes.Remove(Type); - RegisteredUserDefinedTypes.Remove(Type); - RegisteredNumericTypes.Remove(Type); - } - - FNiagaraTypeDefinition GetTypeDefFromStruct(UStruct* Struct) - { - for (FNiagaraTypeDefinition& TypeDef : RegisteredTypes) { - if (Struct == TypeDef.GetStruct()) + FReadScopeLock Lock(RegisteredTypesLock); + const uint32 TypeHash = GetTypeHash(NewType); + if (const int32* ExistingIndex = RegisteredTypeIndexMap.Find(TypeHash)) { - return TypeDef; + return *ExistingIndex; } } - return FNiagaraTypeDefinition(); + FRWScopeLock Lock(RegisteredTypesLock, FRWScopeLockType::SLT_Write); + const int32 Index = RegisteredTypes.AddUnique(NewType); + RegisteredTypeIndexMap.Add(GetTypeHash(NewType), Index); + return Index; } private: - static TArray RegisteredTypes; + + static RegisteredTypesArray RegisteredTypes; + static TArray RegisteredParamTypes; static TArray RegisteredPayloadTypes; static TArray RegisteredUserDefinedTypes; static TArray RegisteredNumericTypes; + + static TMap RegisteredTypeIndexMap; + static FRWLock RegisteredTypesLock; }; -FORCEINLINE uint32 GetTypeHash(const FNiagaraTypeDefinition& Type) +USTRUCT() +struct FNiagaraTypeDefinitionHandle { - return HashCombine(GetTypeHash(Type.GetStruct()), GetTypeHash(Type.GetEnum())); -} + GENERATED_USTRUCT_BODY() + + FNiagaraTypeDefinitionHandle() : RegisteredTypeIndex(INDEX_NONE) {} + explicit FNiagaraTypeDefinitionHandle(const FNiagaraTypeDefinition& Type) : RegisteredTypeIndex(Register(Type)) {} + explicit FNiagaraTypeDefinitionHandle(const FNiagaraTypeDefinitionHandle& Handle) : RegisteredTypeIndex(Handle.RegisteredTypeIndex) {} + + const FNiagaraTypeDefinition& operator*() const { return Resolve(); } + const FNiagaraTypeDefinition* operator->() const { return &Resolve(); } + + bool operator==(const FNiagaraTypeDefinitionHandle& Other) const + { + return RegisteredTypeIndex == Other.RegisteredTypeIndex; + } + bool operator!=(const FNiagaraTypeDefinitionHandle& Other) const + { + return RegisteredTypeIndex != Other.RegisteredTypeIndex; + } + +private: + friend FArchive& operator<<(FArchive& Ar, FNiagaraTypeDefinitionHandle& Handle); + + NIAGARA_API const FNiagaraTypeDefinition& Resolve() const; + NIAGARA_API int32 Register(const FNiagaraTypeDefinition& TypeDef) const; + + UPROPERTY() + int32 RegisteredTypeIndex = INDEX_NONE; +}; ////////////////////////////////////////////////////////////////////////// @@ -1253,14 +1305,34 @@ struct FNiagaraVariableBase { GENERATED_USTRUCT_BODY() - FORCEINLINE FNiagaraVariableBase() : Name(NAME_None), TypeDef(FNiagaraTypeDefinition::GetVec4Def()) { } - FORCEINLINE FNiagaraVariableBase(const FNiagaraVariableBase &Other) : Name(Other.Name), TypeDef(Other.TypeDef) { } - FORCEINLINE FNiagaraVariableBase(const FNiagaraTypeDefinition& InType, const FName& InName) : Name(InName), TypeDef(InType) { } + FORCEINLINE FNiagaraVariableBase() + : Name(NAME_None) + , TypeDefHandle(FNiagaraTypeDefinition::GetVec4Def()) +#if WITH_EDITORONLY_DATA + , TypeDef_DEPRECATED(FNiagaraTypeDefinition::GetVec4Def()) +#endif + {} + + FORCEINLINE FNiagaraVariableBase(const FNiagaraVariableBase &Other) + : Name(Other.Name) + , TypeDefHandle(Other.TypeDefHandle) +#if WITH_EDITORONLY_DATA + , TypeDef_DEPRECATED(Other.TypeDef_DEPRECATED) +#endif + {} + + FORCEINLINE FNiagaraVariableBase(const FNiagaraTypeDefinition& InType, const FName& InName) + : Name(InName) + , TypeDefHandle(InType) +#if WITH_EDITORONLY_DATA + , TypeDef_DEPRECATED(InType) +#endif + {} /** Check if Name and Type definition are the same. The actual stored value is not checked here.*/ bool operator==(const FNiagaraVariableBase& Other)const { - return Name == Other.Name && TypeDef == Other.TypeDef; + return Name == Other.Name && TypeDefHandle == Other.TypeDefHandle; } /** Check if Name and Type definition are not the same. The actual stored value is not checked here.*/ @@ -1272,31 +1344,43 @@ struct FNiagaraVariableBase /** Variables are the same name but if types are auto-assignable, allow them to match. */ bool IsEquivalent(const FNiagaraVariableBase& Other, bool bAllowAssignableTypes = true)const { - return Name == Other.Name && (TypeDef == Other.TypeDef || (bAllowAssignableTypes && FNiagaraTypeDefinition::TypesAreAssignable(TypeDef, Other.TypeDef))); + return Name == Other.Name && (TypeDefHandle == Other.TypeDefHandle || (bAllowAssignableTypes && FNiagaraTypeDefinition::TypesAreAssignable(*TypeDefHandle, *Other.TypeDefHandle))); } - FORCEINLINE void SetName(FName InName) { Name = InName; } - FORCEINLINE const FName& GetName() const { return Name; } + FORCEINLINE void SetName(FName InName) + { + Name = InName; + } + FORCEINLINE const FName& GetName() const + { + return Name; + } - void SetType(const FNiagaraTypeDefinition& InTypeDef) { TypeDef = InTypeDef; } - const FNiagaraTypeDefinition& GetType()const { return TypeDef; } + void SetType(const FNiagaraTypeDefinition& InTypeDef) + { + TypeDefHandle = FNiagaraTypeDefinitionHandle(InTypeDef); + } + const FNiagaraTypeDefinition& GetType()const + { + return *TypeDefHandle; + } FORCEINLINE bool IsDataInterface()const { return GetType().IsDataInterface(); } FORCEINLINE bool IsUObject()const { return GetType().IsUObject(); } int32 GetSizeInBytes() const { - return TypeDef.GetSize(); + return TypeDefHandle->GetSize(); } int32 GetAlignment()const { - return TypeDef.GetAlignment(); + return TypeDefHandle->GetAlignment(); } bool IsValid() const { - return Name != NAME_None && TypeDef.IsValid(); + return Name != NAME_None && TypeDefHandle->IsValid(); } FORCEINLINE bool IsInNameSpace(FString Namespace) const @@ -1304,12 +1388,39 @@ struct FNiagaraVariableBase return Name.ToString().StartsWith(Namespace + TEXT(".")); } + FORCEINLINE bool IsInNameSpace(const FName& Namespace) const + { + return Name.ToString().StartsWith(Namespace.ToString() + TEXT(".")); + } + + bool Serialize(FArchive& Ar); +#if WITH_EDITORONLY_DATA + void PostSerialize(const FArchive& Ar); +#endif + protected: UPROPERTY(EditAnywhere, Category = "Variable") FName Name; UPROPERTY(EditAnywhere, Category = "Variable") - FNiagaraTypeDefinition TypeDef; + FNiagaraTypeDefinitionHandle TypeDefHandle; + +#if WITH_EDITORONLY_DATA + UPROPERTY(meta = (DeprecatedProperty)) + FNiagaraTypeDefinition TypeDef_DEPRECATED; +#endif +}; + +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithSerializer = true, +#if WITH_EDITORONLY_DATA + WithPostSerialize = true, +#endif + }; }; FORCEINLINE uint32 GetTypeHash(const FNiagaraVariableBase& Var) @@ -1329,7 +1440,7 @@ struct FNiagaraVariable : public FNiagaraVariableBase FNiagaraVariable(const FNiagaraVariable &Other) : FNiagaraVariableBase(Other) { - if (Other.TypeDef.IsValid() && Other.IsDataAllocated()) + if (Other.GetType().IsValid() && Other.IsDataAllocated()) { SetData(Other.GetData()); } @@ -1349,7 +1460,7 @@ struct FNiagaraVariable : public FNiagaraVariableBase bool operator==(const FNiagaraVariable& Other)const { //-TODO: Should this check the value??? - return Name == Other.Name && TypeDef == Other.TypeDef; + return Name == Other.Name && TypeDefHandle == Other.TypeDefHandle; } /** Check if Name and Type definition are not the same. The actual stored value is not checked here.*/ @@ -1361,7 +1472,7 @@ struct FNiagaraVariable : public FNiagaraVariableBase /** Checks if the types match and either both variables are uninitialized or both hold exactly the same data.*/ bool HoldsSameData(const FNiagaraVariable& Other) const { - if (TypeDef != Other.TypeDef) { + if (TypeDefHandle != Other.TypeDefHandle) { return false; } if (!IsDataAllocated() && !Other.IsDataAllocated()) { @@ -1373,17 +1484,20 @@ struct FNiagaraVariable : public FNiagaraVariableBase // Var data operations void AllocateData() { - if (VarData.Num() != TypeDef.GetSize()) + if (VarData.Num() != TypeDefHandle->GetSize()) { - VarData.SetNumZeroed(TypeDef.GetSize()); + VarData.SetNumZeroed(TypeDefHandle->GetSize()); } } - bool IsDataAllocated()const { return VarData.Num() > 0 && VarData.Num() == TypeDef.GetSize(); } + bool IsDataAllocated() const + { + return VarData.Num() > 0 && VarData.Num() == TypeDefHandle->GetSize(); + } void CopyTo(uint8* Dest) const { - check(TypeDef.GetSize() == VarData.Num()); + check(TypeDefHandle->GetSize() == VarData.Num()); check(IsDataAllocated()); FMemory::Memcpy(Dest, VarData.GetData(), VarData.Num()); } @@ -1391,7 +1505,7 @@ struct FNiagaraVariable : public FNiagaraVariableBase template void SetValue(const T& Data) { - check(sizeof(T) == TypeDef.GetSize()); + check(sizeof(T) == TypeDefHandle->GetSize()); AllocateData(); FMemory::Memcpy(VarData.GetData(), &Data, VarData.Num()); } @@ -1399,10 +1513,10 @@ struct FNiagaraVariable : public FNiagaraVariableBase template T GetValue() const { - check(sizeof(T) == TypeDef.GetSize()); + check(sizeof(T) == TypeDefHandle->GetSize()); check(IsDataAllocated()); T Value; - FMemory::Memcpy(&Value, GetData(), TypeDef.GetSize()); + FMemory::Memcpy(&Value, GetData(), TypeDefHandle->GetSize()); return Value; } @@ -1436,7 +1550,7 @@ struct FNiagaraVariable : public FNiagaraVariableBase FString ToString()const { FString Ret = Name.ToString() + TEXT("("); - Ret += TypeDef.ToString(VarData.GetData()); + Ret += TypeDefHandle->ToString(VarData.GetData()); Ret += TEXT(")"); return Ret; } @@ -1492,6 +1606,11 @@ struct FNiagaraVariable : public FNiagaraVariableBase return BestMatchIdx; } + bool Serialize(FArchive& Ar); +#if WITH_EDITORONLY_DATA + void PostSerialize(const FArchive& Ar); +#endif + private: //This gets serialized but do we need to worry about endianness doing things like this? If not, where does that get handled? //TODO: Remove storage here entirely and move everything to an FNiagaraParameterStore. @@ -1499,10 +1618,22 @@ private: TArray VarData; }; +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithSerializer = true, +#if WITH_EDITORONLY_DATA + WithPostSerialize = true, +#endif + }; +}; + template<> inline bool FNiagaraVariable::GetValue() const { - check(TypeDef == FNiagaraTypeDefinition::GetBoolDef()); + check(*TypeDefHandle == FNiagaraTypeDefinition::GetBoolDef()); check(IsDataAllocated()); FNiagaraBool* BoolStruct = (FNiagaraBool*)GetData(); return BoolStruct->GetValue(); @@ -1511,7 +1642,7 @@ inline bool FNiagaraVariable::GetValue() const template<> inline void FNiagaraVariable::SetValue(const bool& Data) { - check(TypeDef == FNiagaraTypeDefinition::GetBoolDef()); + check(*TypeDefHandle == FNiagaraTypeDefinition::GetBoolDef()); AllocateData(); FNiagaraBool* BoolStruct = (FNiagaraBool*)GetData(); BoolStruct->SetValue(Data); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraUserRedirectionParameterStore.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraUserRedirectionParameterStore.h index a797b2b6c5cf..e72658e9fa6d 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraUserRedirectionParameterStore.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraUserRedirectionParameterStore.h @@ -24,7 +24,11 @@ public: virtual ~FNiagaraUserRedirectionParameterStore() = default; void RecreateRedirections(); - FNiagaraVariableBase FindRedirection(const FNiagaraVariableBase& InVar) const; + + /** If necessary it will replace the supplied variable with the fully qualified namespace (User.) appropriate for a user variable. + Will return false if the variable wasn't able to be converted into a valid user namespaced variable. + */ + bool RedirectUserVariable(FNiagaraVariableBase& UserVar) const; /** Get the list of FNiagaraVariables that are exposed to the user. Note that the values will be stale and are not to be trusted directly. Get the Values using the offset specified by IndexOf or GetParameterValue.*/ FORCEINLINE void GetUserParameters(TArray& OutParameters) const { return UserParameterRedirects.GenerateKeyArray(OutParameters); } @@ -63,14 +67,14 @@ public: /** Turn the input NiagaraVariable into the User namespaced version if needed, independent of whether or not it is in a redirection table.*/ static void MakeUserVariable(FNiagaraVariableBase& InVar); + static bool IsUserParameter(const FNiagaraVariableBase& InVar); + private: /** Map from the variables with shortened display names to the original variables with the full namespace */ UPROPERTY() TMap UserParameterRedirects; - static bool IsUserParameter(const FNiagaraVariableBase& InVar); - FNiagaraVariable GetUserRedirection(const FNiagaraVariable& InVar) const; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraWorldManager.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraWorldManager.h index 4815ce4cf13c..066c6127d417 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraWorldManager.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraWorldManager.h @@ -167,10 +167,15 @@ public: void DumpScalabilityState(); #endif + template + void ForAllSystemSimulations(TAction Func); + + template + static void ForAllWorldManagers(TAction Func); + static void PrimePoolForAllWorlds(UNiagaraSystem* System); void PrimePoolForAllSystems(); void PrimePool(UNiagaraSystem* System); - private: // Callback function registered with global world delegates to instantiate world manager when a game world is created static void OnWorldInit(UWorld* World, const UWorld::InitializationValues IVS); @@ -257,3 +262,27 @@ private: bool bAppHasFocus; }; + +template +void FNiagaraWorldManager::ForAllSystemSimulations(TAction Func) +{ + for (int TG = 0; TG < NiagaraNumTickGroups; ++TG) + { + for (TPair>& SimPair : SystemSimulations[TG]) + { + Func(SimPair.Value.Get()); + } + } +} + +template +void FNiagaraWorldManager::ForAllWorldManagers(TAction Func) +{ + for (auto& Pair : WorldManagers) + { + if (Pair.Value) + { + Func(*Pair.Value); + } + } +} \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Private/NiagaraCustomVersion.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Private/NiagaraCustomVersion.cpp index 92cd90e8e94c..6411b9f1d165 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Private/NiagaraCustomVersion.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Private/NiagaraCustomVersion.cpp @@ -8,4 +8,4 @@ const FGuid FNiagaraCustomVersion::GUID(0xFCF57AFA, 0x50764283, 0xB9A9E658, 0xFF // Register the custom version with core FCustomVersionRegistration GRegisterNiagaraCustomVersion(FNiagaraCustomVersion::GUID, FNiagaraCustomVersion::LatestVersion, TEXT("NiagaraVer")); -const FGuid FNiagaraCustomVersion::LatestScriptCompileVersion(0xF339017C, 0xCAA442B8, 0xB155D36B, 0xFA7CFD7E); \ No newline at end of file +const FGuid FNiagaraCustomVersion::LatestScriptCompileVersion(0xC1D87A35, 0x93795D4C, 0xB2679A52, 0x8A75A134); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraCustomVersion.h b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraCustomVersion.h index 3a2d71d1498b..f7721bfb22ca 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraCustomVersion.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraCustomVersion.h @@ -154,6 +154,12 @@ struct FNiagaraCustomVersion ComponentsOnlyHaveUserVariables, // Make sure that UNiagaraComponents only have override maps for User variables. + RibbonRendererUVRefactor, // Refactor the options for UV settings on the ribbon renderer. + + VariablesUseTypeDefRegistry, // Replace the TypeDefinition in VariableBase with an index into the type registry + + AddLibraryVisibilityProperty, // Expand the visibility options of the scripts to be able to hide a script completely from the user + // DO NOT ADD A NEW VERSION UNLESS YOU HAVE TALKED TO THE NIAGARA LEAD. Mismanagement of these versions can lead to data loss if it is adjusted in multiple streams simultaneously. // ----- ------------------------------------------------- VersionPlusOne, diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraDataInterfaceBase.h b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraDataInterfaceBase.h index de426baa35a9..1e1f451aaa96 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraDataInterfaceBase.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraDataInterfaceBase.h @@ -25,16 +25,55 @@ struct FNiagaraComputeInstanceData; DECLARE_EXPORTED_TEMPLATE_INTRINSIC_TYPE_LAYOUT(template<>, TIndexedPtr, NIAGARACORE_API); -struct FNiagaraDataInterfaceSetArgs +struct FNiagaraDataInterfaceArgs { - TShaderRefBase Shader; - FNiagaraDataInterfaceProxy* DataInterface = nullptr; - FNiagaraSystemInstanceID SystemInstance = FNiagaraSystemInstanceID(); - const NiagaraEmitterInstanceBatcher* Batcher = nullptr; - const FNiagaraComputeInstanceData* ComputeInstanceData = nullptr; - uint32 SimulationStageIndex = 0; - bool IsOutputStage = false; - bool IsIterationStage = false; + FNiagaraDataInterfaceArgs(FNiagaraDataInterfaceProxy* InDataInterface, FNiagaraSystemInstanceID InSystemInstanceID, const NiagaraEmitterInstanceBatcher* InBatcher) + : DataInterface(InDataInterface) + , SystemInstanceID(InSystemInstanceID) + , Batcher(InBatcher) + { + } + + FNiagaraDataInterfaceProxy* DataInterface; + FNiagaraSystemInstanceID SystemInstanceID; + const NiagaraEmitterInstanceBatcher* Batcher; +}; + +struct FNiagaraDataInterfaceStageArgs : public FNiagaraDataInterfaceArgs +{ + FNiagaraDataInterfaceStageArgs(FNiagaraDataInterfaceProxy* InDataInterface, FNiagaraSystemInstanceID InSystemInstanceID, const NiagaraEmitterInstanceBatcher* InBatcher, uint32 InSimulationStageIndex, bool InIsOutputStage, bool InIsIterationStage) + : FNiagaraDataInterfaceArgs(InDataInterface, InSystemInstanceID, InBatcher) + , SimulationStageIndex(InSimulationStageIndex) + , IsOutputStage(InIsOutputStage) + , IsIterationStage(InIsIterationStage) + { + } + + uint32 SimulationStageIndex; + bool IsOutputStage; + bool IsIterationStage; +}; + +struct FNiagaraDataInterfaceSetArgs : public FNiagaraDataInterfaceArgs +{ + typedef TShaderRefBase FShaderReference; + + FNiagaraDataInterfaceSetArgs( + FNiagaraDataInterfaceProxy* InDataInterface, FNiagaraSystemInstanceID InSystemInstanceID, const NiagaraEmitterInstanceBatcher* InBatcher, const FShaderReference& InShader, const FNiagaraComputeInstanceData* InComputeInstanceData, uint32 InSimulationStageIndex, bool InIsOutputStage, bool InIsIterationStage) + : FNiagaraDataInterfaceArgs(InDataInterface, InSystemInstanceID, InBatcher) + , Shader(InShader) + , ComputeInstanceData(InComputeInstanceData) + , SimulationStageIndex(InSimulationStageIndex) + , IsOutputStage(InIsOutputStage) + , IsIterationStage(InIsIterationStage) + { + } + + FShaderReference Shader; + const FNiagaraComputeInstanceData* ComputeInstanceData; + uint32 SimulationStageIndex; + bool IsOutputStage; + bool IsIterationStage; }; /** diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraComponentRendererPropertiesDetails.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraComponentRendererPropertiesDetails.cpp index d52ce56b0bf1..665b5e6c38ad 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraComponentRendererPropertiesDetails.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraComponentRendererPropertiesDetails.cpp @@ -158,7 +158,7 @@ void FNiagaraComponentRendererPropertiesDetails::CustomizeDetails(IDetailLayoutB NameWidget.ToSharedRef() ]; - bool IsConvertingValue = Binding->PropertyType.IsValid() && Binding->PropertyType != Binding->AttributeBinding.BoundVariable.GetType(); + bool IsConvertingValue = Binding->PropertyType.IsValid() && Binding->PropertyType != Binding->AttributeBinding.GetType(); FName StyleName = IsConvertingValue ? FName("FlatButton.Warning") : FName("FlatButton.Success"); FText Tooltip = IsConvertingValue ? LOCTEXT("NiagaraPropertyBindingToolTipConverting", "Bind to a particle attribute to update this parameter each tick. \nThe currently bound value is auto-converted to fit the target type, which costs some performance.") : LOCTEXT("NiagaraPropertyBindingToolTip", "Bind to a particle attribute to update this parameter each tick."); @@ -283,8 +283,7 @@ void FNiagaraComponentRendererPropertiesDetails::ChangePropertyBinding(TSharedPt { FScopedTransaction Transaction(FText::Format(LOCTEXT("ChangePropertyBinding", "Change component property binding to \"{0}\" "), FText::FromName(BindingVar.GetName()))); FNiagaraComponentPropertyBinding NewBinding = ToPropertyBinding(PropertyHandle, ComponentProperties); - NewBinding.AttributeBinding.BoundVariable = BindingVar; - NewBinding.AttributeBinding.DataSetVariable = FNiagaraConstants::GetAttributeAsDataSetKey(NewBinding.AttributeBinding.BoundVariable); + NewBinding.AttributeBinding.Setup(BindingVar, FNiagaraConstants::GetAttributeAsParticleDataSetKey(BindingVar), BindingVar); ComponentProperties->Modify(); PropertyHandle->NotifyPreChange(); @@ -374,7 +373,7 @@ FText FNiagaraComponentRendererPropertiesDetails::GetCurrentBindingText(TSharedP const FNiagaraComponentPropertyBinding* PropertyBinding = FindBinding(PropertyHandle); if (PropertyBinding) { - return FText::FromName(PropertyBinding->AttributeBinding.BoundVariable.GetName()); + return FText::FromName(PropertyBinding->AttributeBinding.GetName(ENiagaraRendererSourceDataMode::Particles)); } } return FText::FromString(TEXT("Missing")); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraScriptDetails.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraScriptDetails.cpp index 32376ed5aafc..5af9d36779dc 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraScriptDetails.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraScriptDetails.cpp @@ -90,15 +90,41 @@ private: TSharedRef GetAddParameterMenuContent() const { FMenuBuilder AddMenuBuilder(true, nullptr); - for (TSharedPtr AvailableType : CollectionViewModel->GetAvailableTypes()) + TSortedMap>> SubmenusToAdd; + for (TSharedPtr AvailableType : CollectionViewModel->GetAvailableTypesSorted()) { - AddMenuBuilder.AddMenuEntry - ( - AvailableType->GetNameText(), - FText(), - FSlateIcon(), - FUIAction(FExecuteAction::CreateSP(CollectionViewModel.ToSharedRef(), &INiagaraParameterCollectionViewModel::AddParameter, AvailableType)) - ); + FText SubmenuText = FNiagaraEditorUtilities::GetTypeDefinitionCategory(*AvailableType); + if (SubmenuText.IsEmptyOrWhitespace()) + { + AddMenuBuilder.AddMenuEntry + ( + AvailableType->GetNameText(), + FText(), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(CollectionViewModel.ToSharedRef(), &INiagaraParameterCollectionViewModel::AddParameter, AvailableType)) + ); + } + else + { + SubmenusToAdd.FindOrAdd(SubmenuText.ToString()).Add(AvailableType); + } + } + for (const auto& Entry : SubmenusToAdd) + { + TArray> SubmenuEntries = Entry.Value; + AddMenuBuilder.AddSubMenu(FText::FromString(Entry.Key), FText(), FNewMenuDelegate::CreateLambda([SubmenuEntries, this](FMenuBuilder& InSubMenuBuilder) + { + for (TSharedPtr AvailableType : SubmenuEntries) + { + InSubMenuBuilder.AddMenuEntry + ( + AvailableType->GetNameText(), + FText(), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(CollectionViewModel.ToSharedRef(), &INiagaraParameterCollectionViewModel::AddParameter, AvailableType)) + ); + } + })); } return AddMenuBuilder.MakeWidget(); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraTypeCustomizations.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraTypeCustomizations.cpp index 8a4a73ac94c5..d22b7f478762 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraTypeCustomizations.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraTypeCustomizations.cpp @@ -35,6 +35,7 @@ #include "Widgets/Text/STextBlock.h" #include "NiagaraDataInterfaceRW.h" #include "NiagaraSettings.h" +#include "Widgets/SNiagaraParameterName.h" #define LOCTEXT_NAMESPACE "FNiagaraVariableAttributeBindingCustomization" @@ -117,11 +118,90 @@ void FNiagaraMatrixCustomization::CustomizeChildren(TSharedRef } } +TArray FNiagaraStackAssetAction_VarBind::FindVariables(UNiagaraEmitter* InEmitter, bool bSystem, bool bEmitter, bool bParticles, bool bUser) +{ + TArray Bindings; + TArray Histories; + + UNiagaraScriptSource* Source = Cast(InEmitter->GraphSource); + if (Source) + { + Histories.Append(UNiagaraNodeParameterMapBase::GetParameterMaps(Source->NodeGraph)); + } + + if (bSystem || bEmitter) + { + UNiagaraSystem* Sys = InEmitter->GetTypedOuter(); + if (Sys) + { + Source = Cast(Sys->GetSystemUpdateScript()->GetSource()); + if (Source) + { + Histories.Append(UNiagaraNodeParameterMapBase::GetParameterMaps(Source->NodeGraph)); + } + } + } + + + + for (const FNiagaraParameterMapHistory& History : Histories) + { + for (const FNiagaraVariable& Var : History.Variables) + { + if (FNiagaraParameterMapHistory::IsAttribute(Var) && bParticles) + { + Bindings.AddUnique(Var); + } + else if (FNiagaraParameterMapHistory::IsSystemParameter(Var) && bSystem) + { + Bindings.AddUnique(Var); + } + else if (Var.IsInNameSpace(InEmitter->GetUniqueEmitterName()) && bEmitter) + { + TMap Aliases; + Aliases.Add(InEmitter->GetUniqueEmitterName(), FNiagaraConstants::EmitterNamespace.ToString()); + Bindings.AddUnique(Var.ResolveAliases(Var, Aliases)); + } + else if (Var.IsInNameSpace(FNiagaraConstants::EmitterNamespace) && bEmitter) + { + Bindings.AddUnique(Var); + } + else if (FNiagaraParameterMapHistory::IsUserParameter(Var) && bUser) + { + Bindings.AddUnique(Var); + } + } + } + + if (bUser) + { + UNiagaraSystem* Sys = InEmitter->GetTypedOuter(); + if (Sys) + { + for (const FNiagaraVariable Var : Sys->GetExposedParameters().ReadParameterVariables()) + { + Bindings.AddUnique(Var); + } + } + } + return Bindings; +} + + +FName FNiagaraVariableAttributeBindingCustomization::GetVariableName() const +{ + if (BaseEmitter && TargetVariableBinding) + { + return (TargetVariableBinding->GetName(RenderProps->GetCurrentSourceMode())); + } + return FName(); +} + FText FNiagaraVariableAttributeBindingCustomization::GetCurrentText() const { if (BaseEmitter && TargetVariableBinding) { - return FText::FromName(TargetVariableBinding->BoundVariable.GetName()); + return FText::FromName(TargetVariableBinding->GetName(RenderProps->GetCurrentSourceMode())); } return FText::FromString(TEXT("Missing")); } @@ -130,15 +210,9 @@ FText FNiagaraVariableAttributeBindingCustomization::GetTooltipText() const { if (BaseEmitter && TargetVariableBinding) { - FString DefaultValueStr = TargetVariableBinding->DefaultValueIfNonExistent.GetName().ToString(); - - if (!TargetVariableBinding->DefaultValueIfNonExistent.GetName().IsValid() || TargetVariableBinding->DefaultValueIfNonExistent.IsDataAllocated() == true) - { - DefaultValueStr = TargetVariableBinding->DefaultValueIfNonExistent.GetType().ToString(TargetVariableBinding->DefaultValueIfNonExistent.GetData()); - DefaultValueStr.TrimEndInline(); - } + FString DefaultValueStr = TargetVariableBinding->GetDefaultValueString(); - FText TooltipDesc = FText::Format(LOCTEXT("AttributeBindingTooltip", "Use the variable \"{0}\" if it exists, otherwise use the default \"{1}\" "), FText::FromName(TargetVariableBinding->BoundVariable.GetName()), + FText TooltipDesc = FText::Format(LOCTEXT("AttributeBindingTooltip", "Use the variable \"{0}\" if it exists, otherwise use the default \"{1}\" "), FText::FromName(TargetVariableBinding->GetName(RenderProps->GetCurrentSourceMode())), FText::FromString(DefaultValueStr)); return TooltipDesc; } @@ -169,22 +243,20 @@ TArray FNiagaraVariableAttributeBindingCustomization::GetNames(UNiagaraEm { TArray Names; - UNiagaraScriptSource* Source = Cast(InEmitter->GraphSource); - if (Source) + bool bSystem = true; + bool bEmitter = true; + bool bParticles = true; + bool bUser = true; + TArray Vars = FNiagaraStackAssetAction_VarBind::FindVariables(InEmitter, bSystem, bEmitter, bParticles, bUser); + for (const FNiagaraVariableBase& Var : Vars) { - TArray Histories = UNiagaraNodeParameterMapBase::GetParameterMaps(Source->NodeGraph); - for (const FNiagaraParameterMapHistory& History : Histories) + if (RenderProps && PropertyHandle.IsValid() && PropertyHandle->GetProperty() && RenderProps->IsSupportedVariableForBinding(Var, *PropertyHandle->GetProperty()->GetName())) { - for (const FNiagaraVariable& Var : History.Variables) - { - if (FNiagaraParameterMapHistory::IsAttribute(Var) && Var.GetType() == TargetVariableBinding->BoundVariable.GetType()) - { - Names.AddUnique(Var.GetName()); - } - } + if (Var.GetType() == TargetVariableBinding->GetType()) + Names.AddUnique(Var.GetName()); } - } + return Names; } @@ -210,8 +282,11 @@ TSharedRef FNiagaraVariableAttributeBindingCustomization::OnCreateWidge + SVerticalBox::Slot() .AutoHeight() [ - SNew(STextBlock) - .Text(InCreateData->Action->GetMenuDescription()) + SNew(SNiagaraParameterName) + .ParameterName(((FNiagaraStackAssetAction_VarBind* const)InCreateData->Action.Get())->VarName) + .IsReadOnly(true) + //SNew(STextBlock) + //.Text(InCreateData->Action->GetMenuDescription()) .ToolTipText(InCreateData->Action->GetTooltipDescription()) ]; } @@ -244,23 +319,75 @@ void FNiagaraVariableAttributeBindingCustomization::ChangeSource(FName InVarName { Obj->Modify(); } + check(BaseEmitter); + check(RenderProps); PropertyHandle->NotifyPreChange(); - TargetVariableBinding->BoundVariable.SetName(InVarName); - TargetVariableBinding->DataSetVariable = FNiagaraConstants::GetAttributeAsDataSetKey(TargetVariableBinding->BoundVariable); + TargetVariableBinding->SetValue(InVarName, BaseEmitter, RenderProps->GetCurrentSourceMode()); PropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet); PropertyHandle->NotifyFinishedChangingProperties(); } +void FNiagaraVariableAttributeBindingCustomization::ResetToDefault() +{ + UE_LOG(LogNiagaraEditor, Warning, TEXT("Reset to default!")); +} + +EVisibility FNiagaraVariableAttributeBindingCustomization::IsResetToDefaultsVisible() const +{ + check(BaseEmitter); + check(RenderProps); + check(TargetVariableBinding); + check(DefaultVariableBinding); + return (!TargetVariableBinding->MatchesDefault(*DefaultVariableBinding, RenderProps->GetCurrentSourceMode())) + ? EVisibility::Visible + : EVisibility::Hidden; +} + +FReply FNiagaraVariableAttributeBindingCustomization::OnResetToDefaultsClicked() +{ + FScopedTransaction Transaction(LOCTEXT("ResetBindingParam", "Reset binding")); + TArray Objects; + PropertyHandle->GetOuterObjects(Objects); + for (UObject* Obj : Objects) + { + Obj->Modify(); + } + check(BaseEmitter); + check(RenderProps); + check(TargetVariableBinding); + check(DefaultVariableBinding); + + PropertyHandle->NotifyPreChange(); + TargetVariableBinding->ResetToDefault(*DefaultVariableBinding, BaseEmitter, RenderProps->GetCurrentSourceMode()); + PropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + PropertyHandle->NotifyFinishedChangingProperties(); + return FReply::Handled(); +} + void FNiagaraVariableAttributeBindingCustomization::CustomizeHeader(TSharedRef InPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) { + RenderProps = nullptr; + BaseEmitter = nullptr; PropertyHandle = InPropertyHandle; TArray Objects; PropertyHandle->GetOuterObjects(Objects); bool bAddDefault = true; + + InPropertyHandle->SetOnPropertyResetToDefault(FSimpleDelegate::CreateLambda([this]() { ResetToDefault(); })); + //InPropertyHandle->ExecuteCustomResetToDefault + + /*FResetToDefaultOverride ResetOverride = FResetToDefaultOverride::Create( + FIsResetToDefaultVisible::CreateSP(this, &FMotionControllerDetails::IsSourceValueModified), + FResetToDefaultHandler::CreateSP(this, &FMotionControllerDetails::OnResetSourceValue) + ); + + PropertyRow.OverrideResetToDefault(ResetOverride); */ + InPropertyHandle->MarkResetToDefaultCustomized(true); + if (Objects.Num() == 1) { - UNiagaraRendererProperties* RenderProps = Cast(Objects[0]); + RenderProps = Cast(Objects[0]); if (RenderProps) { BaseEmitter = Cast(RenderProps->GetOuter()); @@ -268,6 +395,7 @@ void FNiagaraVariableAttributeBindingCustomization::CustomizeHeader(TSharedRefGetValueBaseAddress((uint8*)Objects[0]); + DefaultVariableBinding = (FNiagaraVariableAttributeBinding*)PropertyHandle->GetValueBaseAddress((uint8*)Objects[0]->GetClass()->GetDefaultObject()); HeaderRow .NameContent() @@ -277,15 +405,45 @@ void FNiagaraVariableAttributeBindingCustomization::CustomizeHeader(TSharedRefAction->GetMenuDescription()) + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 1) + [ + SNew(SButton) + .IsFocusable(false) + .ToolTipText(LOCTEXT("ResetToDefaultToolTip", "Reset to Default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(this, &FNiagaraVariableAttributeBindingCustomization::IsResetToDefaultsVisible) + .OnClicked(this, &FNiagaraVariableAttributeBindingCustomization::OnResetToDefaultsClicked) + .Content() + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] ] ]; bAddDefault = false; @@ -314,6 +472,15 @@ void FNiagaraVariableAttributeBindingCustomization::CustomizeHeader(TSharedRefParameter.GetName()); + } + return FName(); +} + FText FNiagaraUserParameterBindingCustomization::GetCurrentText() const { if (BaseSystem && TargetUserParameterBinding) @@ -327,7 +494,7 @@ FText FNiagaraUserParameterBindingCustomization::GetTooltipText() const { if (BaseSystem && TargetUserParameterBinding && TargetUserParameterBinding->Parameter.IsValid()) { - FText TooltipDesc = FText::Format(LOCTEXT("ParameterBindingTooltip", "Bound to the user parameter \"{0}\""), FText::FromName(TargetUserParameterBinding->Parameter.GetName())); + FText TooltipDesc = FText::Format(LOCTEXT("UserParameterBindingTooltip", "Bound to the user parameter \"{0}\""), FText::FromName(TargetUserParameterBinding->Parameter.GetName())); return TooltipDesc; } return FText::FromString(TEXT("Missing")); @@ -392,9 +559,12 @@ TSharedRef FNiagaraUserParameterBindingCustomization::OnCreateWidgetFor + SVerticalBox::Slot() .AutoHeight() [ - SNew(STextBlock) - .Text(InCreateData->Action->GetMenuDescription()) - .ToolTipText(InCreateData->Action->GetTooltipDescription()) + SNew(SNiagaraParameterName) + .ParameterName(((FNiagaraStackAssetAction_VarBind* const)InCreateData->Action.Get())->VarName) + .IsReadOnly(true) + //SNew(STextBlock) + //.Text(InCreateData->Action->GetMenuDescription()) + .ToolTipText(InCreateData->Action->GetTooltipDescription()) ]; } @@ -458,14 +628,17 @@ void FNiagaraUserParameterBindingCustomization::CustomizeHeader(TSharedRefNiagaraVariable.GetName(); + } + return FName(); +} + +FName FNiagaraMaterialAttributeBindingCustomization::GetNiagaraChildVariableName() const +{ + if (BaseSystem && TargetParameterBinding) + { + return TargetParameterBinding->NiagaraChildVariable.GetName(); + } + return FName(); +} + +FText FNiagaraMaterialAttributeBindingCustomization::GetNiagaraCurrentText() const +{ + if (BaseSystem && TargetParameterBinding) + { + return MakeCurrentText(TargetParameterBinding->NiagaraVariable, TargetParameterBinding->NiagaraChildVariable); + } + return FText::FromString(TEXT("Missing")); +} + + +FText FNiagaraMaterialAttributeBindingCustomization::MakeCurrentText(const FNiagaraVariableBase& BaseVar, const FNiagaraVariableBase& ChildVar) +{ + if (BaseVar.GetName().IsNone()) + { + return FText::FromName(NAME_None); + } + + FString DisplayNameString = FName::NameToDisplayString(BaseVar.GetName().ToString(), false); + FNiagaraTypeDefinition TargetType = BaseVar.GetType(); + if (ChildVar.GetName() != NAME_None) + { + DisplayNameString += TEXT(" \"") + FName::NameToDisplayString(ChildVar.GetName().ToString(), false) + TEXT("\""); + TargetType = ChildVar.GetType(); + } + + DisplayNameString += TEXT(" (") + FName::NameToDisplayString(TargetType.GetFName().ToString(), false) + TEXT(")"); + + const FText NameText = FText::FromString(DisplayNameString); + return NameText; +} + +FText FNiagaraMaterialAttributeBindingCustomization::GetNiagaraTooltipText() const +{ + if (BaseSystem && TargetParameterBinding && TargetParameterBinding->NiagaraVariable.IsValid()) + { + FText TooltipDesc = FText::Format(LOCTEXT("MaterialAttributeBindingTooltip", "Bound to the parameter \"{0}\""), MakeCurrentText(TargetParameterBinding->NiagaraVariable, TargetParameterBinding->NiagaraChildVariable)); + return TooltipDesc; + } + return FText::FromString(TEXT("Missing")); +} + +TSharedRef FNiagaraMaterialAttributeBindingCustomization::OnGetNiagaraMenuContent() const +{ + FGraphActionMenuBuilder MenuBuilder; + + return SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + .Padding(5) + [ + SNew(SBox) + [ + SNew(SGraphActionMenu) + .OnActionSelected(const_cast(this), &FNiagaraMaterialAttributeBindingCustomization::OnNiagaraActionSelected) + .OnCreateWidgetForAction(SGraphActionMenu::FOnCreateWidgetForAction::CreateSP(const_cast(this), &FNiagaraMaterialAttributeBindingCustomization::OnCreateWidgetForNiagaraAction)) + .OnCollectAllActions(const_cast(this), &FNiagaraMaterialAttributeBindingCustomization::CollectAllNiagaraActions) + .AutoExpandActionMenu(false) + .ShowFilterTextBox(true) + ] + ]; +} + + +bool FNiagaraMaterialAttributeBindingCustomization::IsCompatibleNiagaraVariable(const FNiagaraVariable& InVar) const +{ + { + + if (InVar.GetType() == FNiagaraTypeDefinition::GetFloatDef() || + InVar.GetType() == FNiagaraTypeDefinition::GetVec4Def() || + InVar.GetType() == FNiagaraTypeDefinition::GetColorDef() || + InVar.GetType() == FNiagaraTypeDefinition::GetVec2Def() || + InVar.GetType() == FNiagaraTypeDefinition::GetVec3Def() || + InVar.GetType() == FNiagaraTypeDefinition::GetUObjectDef() || + InVar.GetType() == FNiagaraTypeDefinition::GetUTextureDef()) + { + return true; + } + else if (InVar.GetType().IsDataInterface()) + { + return true; + } + } + return false; +} + +TArray > FNiagaraMaterialAttributeBindingCustomization::GetNiagaraNames() const +{ + TArray> Names; + TArray < FNiagaraVariableBase > BaseVars; + + if (BaseSystem && BaseEmitter && TargetParameterBinding) + { + bool bSystem = true; + bool bEmitter = true; + bool bParticles = false; + bool bUser = true; + BaseVars = FNiagaraStackAssetAction_VarBind::FindVariables(BaseEmitter, bSystem, bEmitter, bParticles, bUser); + + +#if 0 + // Add all user variables... + + for (const FNiagaraVariable& Var : BaseSystem->GetExposedParameters().ReadParameterVariables()) + { + if (IsCompatibleNiagaraVariable(Var)) + BaseVars.AddUnique(Var); + } + + // Add all system variables + for (const FNiagaraVariable& Var : BaseSystem->GetSystemUpdateScript()->GetVMExecutableData().Attributes) + { + if (FNiagaraParameterMapHistory::IsSystemParameter(Var) && IsCompatibleNiagaraVariable(Var)) + { + BaseVars.AddUnique(Var); + } + } + + // Add all Emitter variables + for (const FNiagaraVariable& Var : BaseSystem->GetSystemUpdateScript()->GetVMExecutableData().Attributes) + { + if (FNiagaraParameterMapHistory::IsInNamespace(Var, BaseEmitter->GetUniqueEmitterName()) && IsCompatibleNiagaraVariable(Var)) + { + BaseVars.AddUnique(Var); + } + } + + TArray Scripts; + Scripts.Add(BaseSystem->GetSystemUpdateScript()); + Scripts.Add(BaseSystem->GetSystemSpawnScript()); + BaseEmitter->GetScripts(Scripts, false); + + for (UNiagaraScript* Script : Scripts) + { + TArray& CachedDIs = Script->GetCachedDefaultDataInterfaces(); + for (const FNiagaraScriptDataInterfaceInfo& Info : CachedDIs) + { + BaseVars.AddUnique(FNiagaraVariableBase(FNiagaraTypeDefinition(Info.DataInterface->GetClass()), Info.Name)); + } + } +#endif + + for (const FNiagaraVariableBase& BaseVar : BaseVars) + { + if (BaseVar.IsDataInterface()) + { + UNiagaraDataInterface* DI = BaseVar.GetType().GetClass()->GetDefaultObject(); + if (DI && DI->CanExposeVariables()) + { + TArray ChildVars; + DI->GetExposedVariables(ChildVars); + for (const FNiagaraVariableBase& ChildVar : ChildVars) + { + if (IsCompatibleNiagaraVariable(ChildVar)) + { + Names.AddUnique(TPair(BaseVar, ChildVar)); + } + } + } + } + else if (IsCompatibleNiagaraVariable(BaseVar)) + { + if (RenderProps && TargetParameterBinding && RenderProps->IsSupportedVariableForBinding(BaseVar, TargetParameterBinding->MaterialParameterName)) + { + Names.AddUnique(TPair(BaseVar, FNiagaraVariableBase())); + } + } + } + + + + } + + return Names; +} + +void FNiagaraMaterialAttributeBindingCustomization::CollectAllNiagaraActions(FGraphActionListBuilderBase& OutAllActions) +{ + TArray> ParamNames = GetNiagaraNames(); + for (TPair ParamPair : ParamNames) + { + FText CategoryName = FText(); + const FText NameText = MakeCurrentText(ParamPair.Key, ParamPair.Value); + const FText TooltipDesc = FText::Format(LOCTEXT("BindToParameter", "Bind to the Niagara Variable \"{0}\" "), NameText); + FNiagaraStackAssetAction_VarBind* VarBind = new FNiagaraStackAssetAction_VarBind(ParamPair.Key.GetName(), CategoryName, NameText, + TooltipDesc, 0, FText()); + VarBind->BaseVar = ParamPair.Key; + VarBind->ChildVar = ParamPair.Value; + TSharedPtr NewNodeAction(VarBind); + OutAllActions.AddAction(NewNodeAction); + } +} + + +FText FNiagaraMaterialAttributeBindingCustomization::GetNiagaraChildVariableText() const +{ + + FName ChildVarName = GetNiagaraChildVariableName(); + FText ChildVarNameText = ChildVarName.IsNone() == false ? FText::FromString(TEXT("| ") + ChildVarName.ToString()) : FText::GetEmpty(); + return ChildVarNameText; +} + +EVisibility FNiagaraMaterialAttributeBindingCustomization::GetNiagaraChildVariableVisibility() const +{ + FName ChildVarName = GetNiagaraChildVariableName(); + return ChildVarName.IsNone() ? EVisibility::Collapsed : EVisibility::Visible; +} + +TSharedRef FNiagaraMaterialAttributeBindingCustomization::OnCreateWidgetForNiagaraAction(struct FCreateWidgetForActionData* const InCreateData) +{ + FName ChildVarName = (((FNiagaraStackAssetAction_VarBind* const)InCreateData->Action.Get())->ChildVar.GetName()); + FText ChildVarNameText = ChildVarName.IsNone() == false ? FText::FromString(TEXT("| ") + ChildVarName.ToString()) : FText::GetEmpty(); + return SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(3, 0) + [ + SNew(SNiagaraParameterName) + .ParameterName(((FNiagaraStackAssetAction_VarBind* const)InCreateData->Action.Get())->VarName) + .IsReadOnly(true) + //SNew(STextBlock) + //.Text(InCreateData->Action->GetMenuDescription()) + .ToolTipText(InCreateData->Action->GetTooltipDescription()) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(3, 0) + [ + SNew(STextBlock) + .Visibility(ChildVarName.IsNone() ? EVisibility::Collapsed : EVisibility::Visible) + .Text(ChildVarNameText) + ] + ]; +} + + +void FNiagaraMaterialAttributeBindingCustomization::OnNiagaraActionSelected(const TArray< TSharedPtr >& SelectedActions, ESelectInfo::Type InSelectionType) +{ + if (InSelectionType == ESelectInfo::OnMouseClick || InSelectionType == ESelectInfo::OnKeyPress || SelectedActions.Num() == 0) + { + for (int32 ActionIndex = 0; ActionIndex < SelectedActions.Num(); ActionIndex++) + { + TSharedPtr CurrentAction = SelectedActions[ActionIndex]; + + if (CurrentAction.IsValid()) + { + FSlateApplication::Get().DismissAllMenus(); + FNiagaraStackAssetAction_VarBind* EventSourceAction = (FNiagaraStackAssetAction_VarBind*)CurrentAction.Get(); + ChangeNiagaraSource(EventSourceAction); + } + } + } +} + +void FNiagaraMaterialAttributeBindingCustomization::ChangeNiagaraSource(FNiagaraStackAssetAction_VarBind* InVar) +{ + FScopedTransaction Transaction(FText::Format(LOCTEXT("ChangeParameterSource", " Change Parameter Source to \"{0}\" "), FText::FromName(InVar->VarName))); + TArray Objects; + PropertyHandle->GetOuterObjects(Objects); + for (UObject* Obj : Objects) + { + Obj->Modify(); + } + + PropertyHandle->NotifyPreChange(); + TargetParameterBinding->NiagaraVariable = InVar->BaseVar; + TargetParameterBinding->NiagaraChildVariable = InVar->ChildVar; + TargetParameterBinding->CacheValues(BaseEmitter); + //TargetParameterBinding->Parameter.SetType(FNiagaraTypeDefinition::GetUObjectDef()); Do not override the type here! + //TargetVariableBinding->DataSetVariable = FNiagaraConstants::GetAttributeAsDataSetKey(TargetVariableBinding->BoundVariable); + PropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + PropertyHandle->NotifyFinishedChangingProperties(); +} + +FText FNiagaraMaterialAttributeBindingCustomization::GetMaterialCurrentText() const +{ + if (BaseSystem && TargetParameterBinding) + { + return FText::FromName(TargetParameterBinding->MaterialParameterName); + } + return FText::FromString(TEXT("Missing")); +} + +FText FNiagaraMaterialAttributeBindingCustomization::GetMaterialTooltipText() const +{ + if (BaseSystem && TargetParameterBinding && TargetParameterBinding->MaterialParameterName.IsValid()) + { + FText TooltipDesc = FText::Format(LOCTEXT("MaterialParameterBindingTooltip", "Bound to the parameter \"{0}\""), FText::FromName(TargetParameterBinding->MaterialParameterName)); + return TooltipDesc; + } + return FText::FromString(TEXT("Missing")); +} + +TSharedRef FNiagaraMaterialAttributeBindingCustomization::OnGetMaterialMenuContent() const +{ + FGraphActionMenuBuilder MenuBuilder; + + return SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + .Padding(5) + [ + SNew(SBox) + [ + SNew(SGraphActionMenu) + .OnActionSelected(const_cast(this), &FNiagaraMaterialAttributeBindingCustomization::OnMaterialActionSelected) + .OnCreateWidgetForAction(SGraphActionMenu::FOnCreateWidgetForAction::CreateSP(const_cast(this), &FNiagaraMaterialAttributeBindingCustomization::OnCreateWidgetForMaterialAction)) + .OnCollectAllActions(const_cast(this), &FNiagaraMaterialAttributeBindingCustomization::CollectAllMaterialActions) + .AutoExpandActionMenu(false) + .ShowFilterTextBox(true) + ] + ]; +} + +TArray FNiagaraMaterialAttributeBindingCustomization::GetMaterialNames() const +{ + TArray Names; + + if (BaseSystem && TargetParameterBinding && PropertyHandle) + { + TArray Objects; + PropertyHandle->GetOuterObjects(Objects); + + if (Objects.Num() == 1) + { + UNiagaraRendererProperties* RendererProperties = Cast(Objects[0]); + TArray Materials; + if (RendererProperties) + { + RendererProperties->GetUsedMaterials(nullptr, Materials); + } + + TArray ParameterInfo; + for (UMaterialInterface* Material : Materials) + { + if (!Material) + { + continue; + } + + { + TArray LocalParameterInfo; + TArray ParameterIds; + Material->GetAllTextureParameterInfo(LocalParameterInfo, ParameterIds); + ParameterInfo.Append(LocalParameterInfo); + } + { + TArray LocalParameterInfo; + TArray ParameterIds; + Material->GetAllScalarParameterInfo(LocalParameterInfo, ParameterIds); + ParameterInfo.Append(LocalParameterInfo); + } + { + TArray LocalParameterInfo; + TArray ParameterIds; + Material->GetAllVectorParameterInfo(LocalParameterInfo, ParameterIds); + ParameterInfo.Append(LocalParameterInfo); + } + } + + for (const FMaterialParameterInfo& Var : ParameterInfo) + { + Names.AddUnique(Var.Name); + } + } + } + + return Names; +} + +void FNiagaraMaterialAttributeBindingCustomization::CollectAllMaterialActions(FGraphActionListBuilderBase& OutAllActions) +{ + TArray ParamNames = GetMaterialNames(); + for (FName ParamName : ParamNames) + { + FText CategoryName = FText(); + FString DisplayNameString = FName::NameToDisplayString(ParamName.ToString(), false); + const FText NameText = FText::FromString(DisplayNameString); + const FText TooltipDesc = FText::Format(LOCTEXT("BindToParameter", "Bind to the Material Variable \"{0}\" "), FText::FromString(DisplayNameString)); + TSharedPtr NewNodeAction(new FNiagaraStackAssetAction_VarBind(ParamName, CategoryName, NameText, + TooltipDesc, 0, FText())); + OutAllActions.AddAction(NewNodeAction); + } +} + +TSharedRef FNiagaraMaterialAttributeBindingCustomization::OnCreateWidgetForMaterialAction(struct FCreateWidgetForActionData* const InCreateData) +{ + return SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(STextBlock) + .Text(InCreateData->Action->GetMenuDescription()) + .ToolTipText(InCreateData->Action->GetTooltipDescription()) + ]; +} + + +void FNiagaraMaterialAttributeBindingCustomization::OnMaterialActionSelected(const TArray< TSharedPtr >& SelectedActions, ESelectInfo::Type InSelectionType) +{ + if (InSelectionType == ESelectInfo::OnMouseClick || InSelectionType == ESelectInfo::OnKeyPress || SelectedActions.Num() == 0) + { + for (int32 ActionIndex = 0; ActionIndex < SelectedActions.Num(); ActionIndex++) + { + TSharedPtr CurrentAction = SelectedActions[ActionIndex]; + + if (CurrentAction.IsValid()) + { + FSlateApplication::Get().DismissAllMenus(); + FNiagaraStackAssetAction_VarBind* EventSourceAction = (FNiagaraStackAssetAction_VarBind*)CurrentAction.Get(); + ChangeMaterialSource(EventSourceAction->VarName); + } + } + } +} + +void FNiagaraMaterialAttributeBindingCustomization::ChangeMaterialSource(FName InVarName) +{ + FScopedTransaction Transaction(FText::Format(LOCTEXT("ChangeParameterSource", " Change Parameter Source to \"{0}\" "), FText::FromName(InVarName))); + TArray Objects; + PropertyHandle->GetOuterObjects(Objects); + for (UObject* Obj : Objects) + { + Obj->Modify(); + } + + PropertyHandle->NotifyPreChange(); + TargetParameterBinding->MaterialParameterName = InVarName; + //TargetParameterBinding->Parameter.SetType(FMaterialTypeDefinition::GetUObjectDef()); Do not override the type here! + //TargetVariableBinding->DataSetVariable = FMaterialConstants::GetAttributeAsDataSetKey(TargetVariableBinding->BoundVariable); + PropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + PropertyHandle->NotifyFinishedChangingProperties(); +} + +void FNiagaraMaterialAttributeBindingCustomization::CustomizeHeader(TSharedRef InPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) +{ + PropertyHandle = InPropertyHandle; + bool bAddDefault = true; + + + if (bAddDefault) + { + HeaderRow + .NameContent() + [ + PropertyHandle->CreatePropertyNameWidget() + ] + .ValueContent() + .MaxDesiredWidth(200.f) + [ + SNew(STextBlock) + .Text(LOCTEXT("ParamHeaderValue", "Binding")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ]; + } +} + + +void FNiagaraMaterialAttributeBindingCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + RenderProps = nullptr; + BaseSystem = nullptr; + BaseEmitter = nullptr; + + TArray Objects; + PropertyHandle->GetOuterObjects(Objects); + if (Objects.Num() == 1) + { + RenderProps = Cast(Objects[0]); + BaseSystem = Objects[0]->GetTypedOuter(); + BaseEmitter = Objects[0]->GetTypedOuter(); + if (BaseSystem) + { + TargetParameterBinding = (FNiagaraMaterialAttributeBinding*)PropertyHandle->GetValueBaseAddress((uint8*)Objects[0]); + + TSharedPtr ChildPropertyHandle = StructPropertyHandle->GetChildHandle(0); + FDetailWidgetRow& RowMaterial = ChildBuilder.AddCustomRow(FText::GetEmpty()); + RowMaterial + .NameContent() + [ + ChildPropertyHandle->CreatePropertyNameWidget() + ] + .ValueContent() + .MaxDesiredWidth(200.f) + [ + SNew(SComboButton) + .OnGetMenuContent(this, &FNiagaraMaterialAttributeBindingCustomization::OnGetMaterialMenuContent) + .ContentPadding(1) + .ToolTipText(this, &FNiagaraMaterialAttributeBindingCustomization::GetMaterialTooltipText) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .ButtonContent() + [ + SNew(STextBlock) + .Text(this, &FNiagaraMaterialAttributeBindingCustomization::GetMaterialCurrentText) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ]; + + ChildPropertyHandle = StructPropertyHandle->GetChildHandle(1); + FDetailWidgetRow& RowNiagara = ChildBuilder.AddCustomRow(FText::GetEmpty()); + RowNiagara + .NameContent() + [ + ChildPropertyHandle->CreatePropertyNameWidget() + ] + .ValueContent() + .MaxDesiredWidth(200.f) + [ + SNew(SComboButton) + .OnGetMenuContent(this, &FNiagaraMaterialAttributeBindingCustomization::OnGetNiagaraMenuContent) + .ContentPadding(1) + .ToolTipText(this, &FNiagaraMaterialAttributeBindingCustomization::GetNiagaraTooltipText) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .ButtonContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(5, 0) + [ + SNew(SNiagaraParameterName) + .ParameterName(this, &FNiagaraMaterialAttributeBindingCustomization::GetNiagaraVariableName) + .IsReadOnly(true) + ] + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(5, 0) + [ + SNew(STextBlock) + .Visibility(this, &FNiagaraMaterialAttributeBindingCustomization::GetNiagaraChildVariableVisibility) + .Text(this, &FNiagaraMaterialAttributeBindingCustomization::GetNiagaraChildVariableText) + ] + ] + ]; + } + } +} + +////////////////////////////////////////////////////////////////////////// + +FName FNiagaraDataInterfaceBindingCustomization::GetVariableName() const +{ + if (BaseStage && TargetDataInterfaceBinding) + { + return (TargetDataInterfaceBinding->BoundVariable.GetName()); + } + return FName(); +} FText FNiagaraDataInterfaceBindingCustomization::GetCurrentText() const { @@ -504,7 +1248,7 @@ FText FNiagaraDataInterfaceBindingCustomization::GetTooltipText() const { if (BaseStage && TargetDataInterfaceBinding && TargetDataInterfaceBinding->BoundVariable.IsValid()) { - FText TooltipDesc = FText::Format(LOCTEXT("ParameterBindingTooltip", "Bound to the user parameter \"{0}\""), FText::FromName(TargetDataInterfaceBinding->BoundVariable.GetName())); + FText TooltipDesc = FText::Format(LOCTEXT("DataInterfaceBindingTooltip", "Bound to the user parameter \"{0}\""), FText::FromName(TargetDataInterfaceBinding->BoundVariable.GetName())); return TooltipDesc; } return FText::FromString(TEXT("Missing")); @@ -601,8 +1345,11 @@ TSharedRef FNiagaraDataInterfaceBindingCustomization::OnCreateWidgetFor + SVerticalBox::Slot() .AutoHeight() [ - SNew(STextBlock) - .Text(InCreateData->Action->GetMenuDescription()) + SNew(SNiagaraParameterName) + .ParameterName(((FNiagaraStackAssetAction_VarBind* const)InCreateData->Action.Get())->VarName) + .IsReadOnly(true) + //SNew(STextBlock) + //.Text(InCreateData->Action->GetMenuDescription()) .ToolTipText(InCreateData->Action->GetTooltipDescription()) ]; } @@ -667,11 +1414,13 @@ void FNiagaraDataInterfaceBindingCustomization::CustomizeHeader(TSharedRefIsValid()) + { + return (TargetVariableBinding->Name); + } + return FName(); +} + FText FNiagaraScriptVariableBindingCustomization::GetCurrentText() const { if (BaseGraph && TargetVariableBinding && TargetVariableBinding->IsValid()) @@ -815,8 +1573,11 @@ TSharedRef FNiagaraScriptVariableBindingCustomization::OnCreateWidgetFo + SVerticalBox::Slot() .AutoHeight() [ - SNew(STextBlock) - .Text(InCreateData->Action->GetMenuDescription()) + SNew(SNiagaraParameterName) + .ParameterName(((FNiagaraStackAssetAction_VarBind* const)InCreateData->Action.Get())->VarName) + .IsReadOnly(true) + //SNew(STextBlock) + //.Text(InCreateData->Action->GetMenuDescription()) .ToolTipText(InCreateData->Action->GetTooltipDescription()) ]; } @@ -881,11 +1642,13 @@ void FNiagaraScriptVariableBindingCustomization::CustomizeHeader(TSharedRef FindVariables(UNiagaraEmitter* InEmitter, bool bSystem, bool bEmitter, bool bParticles, bool bUser); }; class FNiagaraVariableAttributeBindingCustomization : public IPropertyTypeCustomization @@ -115,10 +120,15 @@ public: virtual void CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override {}; /** IPropertyTypeCustomization interface end */ private: + EVisibility IsResetToDefaultsVisible() const; + FReply OnResetToDefaultsClicked(); + void ResetToDefault(); + FName GetVariableName() const; FText GetCurrentText() const; FText GetTooltipText() const; TSharedRef OnGetMenuContent() const; TArray GetNames(class UNiagaraEmitter* InEmitter) const; + void ChangeSource(FName InVarName); void CollectAllActions(FGraphActionListBuilderBase& OutAllActions); TSharedRef OnCreateWidgetForAction(struct FCreateWidgetForActionData* const InCreateData); @@ -126,8 +136,9 @@ private: TSharedPtr PropertyHandle; class UNiagaraEmitter* BaseEmitter; + class UNiagaraRendererProperties* RenderProps; struct FNiagaraVariableAttributeBinding* TargetVariableBinding; - + const struct FNiagaraVariableAttributeBinding* DefaultVariableBinding; }; class FNiagaraUserParameterBindingCustomization : public IPropertyTypeCustomization @@ -146,6 +157,7 @@ public: private: FText GetCurrentText() const; FText GetTooltipText() const; + FName GetVariableName() const; TSharedRef OnGetMenuContent() const; TArray GetNames() const; void ChangeSource(FName InVarName); @@ -159,6 +171,54 @@ private: }; +class FNiagaraMaterialAttributeBindingCustomization : public IPropertyTypeCustomization +{ +public: + /** @return A new instance of this class */ + static TSharedRef MakeInstance() + { + return MakeShared(); + } + + /** IPropertyTypeCustomization interface begin */ + virtual void CustomizeHeader(TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + virtual void CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override ; + /** IPropertyTypeCustomization interface end */ +private: + FText GetNiagaraCurrentText() const; + FText GetNiagaraTooltipText() const; + FName GetNiagaraVariableName() const; + FText GetNiagaraChildVariableText() const; + FName GetNiagaraChildVariableName() const; + EVisibility GetNiagaraChildVariableVisibility() const; + + TSharedRef OnGetNiagaraMenuContent() const; + TArray> GetNiagaraNames() const; + void ChangeNiagaraSource(FNiagaraStackAssetAction_VarBind* InVar); + void CollectAllNiagaraActions(FGraphActionListBuilderBase& OutAllActions); + TSharedRef OnCreateWidgetForNiagaraAction(struct FCreateWidgetForActionData* const InCreateData); + void OnNiagaraActionSelected(const TArray< TSharedPtr >& SelectedActions, ESelectInfo::Type InSelectionType); + + FText GetMaterialCurrentText() const; + FText GetMaterialTooltipText() const; + TSharedRef OnGetMaterialMenuContent() const; + TArray GetMaterialNames() const; + void ChangeMaterialSource(FName InVarName); + void CollectAllMaterialActions(FGraphActionListBuilderBase& OutAllActions); + TSharedRef OnCreateWidgetForMaterialAction(struct FCreateWidgetForActionData* const InCreateData); + void OnMaterialActionSelected(const TArray< TSharedPtr >& SelectedActions, ESelectInfo::Type InSelectionType); + + bool IsCompatibleNiagaraVariable(const struct FNiagaraVariable& InVar) const; + static FText MakeCurrentText(const FNiagaraVariableBase& BaseVar, const FNiagaraVariableBase& ChildVar); + + TSharedPtr PropertyHandle; + class UNiagaraSystem* BaseSystem; + class UNiagaraEmitter* BaseEmitter; + class UNiagaraRendererProperties* RenderProps; + struct FNiagaraMaterialAttributeBinding* TargetParameterBinding; + +}; + class FNiagaraDataInterfaceBindingCustomization : public IPropertyTypeCustomization { public: @@ -175,6 +235,7 @@ public: private: FText GetCurrentText() const; FText GetTooltipText() const; + FName GetVariableName() const; TSharedRef OnGetMenuContent() const; TArray GetNames() const; void ChangeSource(FName InVarName); @@ -205,6 +266,7 @@ public: /** IPropertyTypeCustomization interface end */ private: /** Helpers */ + FName GetVariableName() const; FText GetCurrentText() const; FText GetTooltipText() const; TSharedRef OnGetMenuContent() const; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/EdGraphSchema_Niagara.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/EdGraphSchema_Niagara.cpp index 2565366a28d4..a49d92e1fc58 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/EdGraphSchema_Niagara.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/EdGraphSchema_Niagara.cpp @@ -565,8 +565,7 @@ TArray > UEdGraphSchema_Niagara::GetGra { if (bAddMakes) { - const TArray& RegisteredTypes = FNiagaraTypeRegistry::GetRegisteredTypes(); - for (const FNiagaraTypeDefinition& Type : RegisteredTypes) + for (const FNiagaraTypeDefinition& Type : FNiagaraTypeRegistry::GetRegisteredTypes()) { if (Type.IsInternalType()) { @@ -583,8 +582,7 @@ TArray > UEdGraphSchema_Niagara::GetGra if (bAddBreaks) { - const TArray& RegisteredTypes = FNiagaraTypeRegistry::GetRegisteredTypes(); - for (const FNiagaraTypeDefinition& Type : RegisteredTypes) + for (const FNiagaraTypeDefinition& Type : FNiagaraTypeRegistry::GetRegisteredTypes()) { if (Type.IsInternalType()) { @@ -1260,7 +1258,7 @@ FNiagaraVariable UEdGraphSchema_Niagara::PinToNiagaraVariable(const UEdGraphPin* if (bHasValue == false) { FString OwningNodePath = Pin->GetOwningNode() != nullptr ? Pin->GetOwningNode()->GetPathName() : TEXT("Unknown"); - UE_LOG(LogNiagaraEditor, Error, TEXT("PinToNiagaraVariable: Failed to convert default value '%s' to type %s. Owning node path: %s"), *Pin->DefaultValue, *Var.GetType().GetName(), *OwningNodePath); + UE_LOG(LogNiagaraEditor, Warning, TEXT("PinToNiagaraVariable: Failed to convert default value '%s' to type %s. Owning node path: %s"), *Pin->DefaultValue, *Var.GetType().GetName(), *OwningNodePath); } } else @@ -1268,7 +1266,7 @@ FNiagaraVariable UEdGraphSchema_Niagara::PinToNiagaraVariable(const UEdGraphPin* if (Pin->GetOwningNode() != nullptr && nullptr == Cast(Pin->GetOwningNode())) { FString OwningNodePath = Pin->GetOwningNode() != nullptr ? Pin->GetOwningNode()->GetPathName() : TEXT("Unknown"); - UE_LOG(LogNiagaraEditor, Error, TEXT("Pin had default value string, but default values aren't supported for variables of type {%s}. Owning node path: %s"), *Var.GetType().GetName(), *OwningNodePath); + UE_LOG(LogNiagaraEditor, Warning, TEXT("Pin had default value string, but default values aren't supported for variables of type {%s}. Owning node path: %s"), *Var.GetType().GetName(), *OwningNodePath); } } } @@ -1278,7 +1276,7 @@ FNiagaraVariable UEdGraphSchema_Niagara::PinToNiagaraVariable(const UEdGraphPin* FNiagaraEditorUtilities::ResetVariableToDefaultValue(Var); if (Var.GetData() == nullptr) { - UE_LOG(LogNiagaraEditor, Error, TEXT("ResetVariableToDefaultValue called, but failed on var %s type %s. "), *Var.GetName().ToString(), *Var.GetType().GetName()); + UE_LOG(LogNiagaraEditor, Warning, TEXT("ResetVariableToDefaultValue called, but failed on var %s type %s. "), *Var.GetName().ToString(), *Var.GetType().GetName()); } } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraClipboard.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraClipboard.cpp index 5717241b36a6..bb792d636acb 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraClipboard.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraClipboard.cpp @@ -263,6 +263,15 @@ UNiagaraClipboardFunctionInput* UNiagaraClipboardEditorScriptingUtilities::Creat return const_cast(CreateLocalValue(InOuter, InInputName, InputType, bInHasEditCondition, bInEditConditionValue, FloatValue)); } +UNiagaraClipboardFunctionInput* UNiagaraClipboardEditorScriptingUtilities::CreateVec2LocalValueInput(UObject* InOuter, FName InInputName, bool bInHasEditCondition, bool bInEditConditionValue, FVector2D InVec2Value) +{ + FNiagaraTypeDefinition InputType = FNiagaraTypeDefinition::GetVec2Def(); + TArray Vec2Value; + Vec2Value.AddUninitialized(InputType.GetSize()); + FMemory::Memcpy(Vec2Value.GetData(), &InVec2Value, InputType.GetSize()); + + return const_cast(CreateLocalValue(InOuter, InInputName, InputType, bInHasEditCondition, bInEditConditionValue, Vec2Value)); +} UNiagaraClipboardFunctionInput* UNiagaraClipboardEditorScriptingUtilities::CreateVec3LocalValueInput(UObject* InOuter, FName InInputName, bool bInHasEditCondition, bool bInEditConditionValue, FVector InVec3Value) { @@ -320,7 +329,7 @@ UNiagaraClipboardFunctionInput* UNiagaraClipboardEditorScriptingUtilities::Creat UNiagaraClipboardFunctionInput* UNiagaraClipboardEditorScriptingUtilities::CreateLinkedValueInput(UObject* InOuter, FName InInputName, FName InInputTypeName, bool bInHasEditCondition, bool bInEditConditionValue, FName InLinkedValue) { - FNiagaraTypeDefinition InputType = GetRegisteredTypeDefinitionByName(InInputName); + FNiagaraTypeDefinition InputType = GetRegisteredTypeDefinitionByName(InInputTypeName); if (InputType.IsValid()) { return const_cast(UNiagaraClipboardFunctionInput::CreateLinkedValue( @@ -349,7 +358,7 @@ UNiagaraClipboardFunctionInput* UNiagaraClipboardEditorScriptingUtilities::Creat UNiagaraClipboardFunctionInput* UNiagaraClipboardEditorScriptingUtilities::CreateExpressionValueInput(UObject* InOuter, FName InInputName, FName InInputTypeName, bool bInHasEditCondition, bool bInEditConditionValue, const FString& InExpressionValue) { - FNiagaraTypeDefinition InputType = GetRegisteredTypeDefinitionByName(InInputName); + FNiagaraTypeDefinition InputType = GetRegisteredTypeDefinitionByName(InInputTypeName); if (InputType.IsValid()) { return const_cast(UNiagaraClipboardFunctionInput::CreateExpressionValue( diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraCompiler.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraCompiler.cpp index 6798fa84cdb5..ef9b9f2014c6 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraCompiler.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraCompiler.cpp @@ -6,27 +6,21 @@ #include "NiagaraGraph.h" #include "NiagaraEditorModule.h" #include "NiagaraComponent.h" -#include "Interfaces/ITargetPlatformManagerModule.h" -#include "Interfaces/IShaderFormat.h" #include "ShaderFormatVectorVM.h" -#include "NiagaraConstants.h" #include "NiagaraSystem.h" #include "NiagaraNodeEmitter.h" #include "NiagaraNodeInput.h" #include "NiagaraFunctionLibrary.h" -#include "NiagaraScriptSource.h" #include "NiagaraDataInterface.h" -#include "NiagaraDataInterfaceStaticMesh.h" -#include "NiagaraDataInterfaceCurlNoise.h" #include "ViewModels/Stack/NiagaraStackGraphUtilities.h" #include "NiagaraNodeFunctionCall.h" #include "NiagaraNodeParameterMapSet.h" #include "INiagaraEditorTypeUtilities.h" #include "NiagaraEditorUtilities.h" -#include "NiagaraNodeEmitter.h" #include "NiagaraNodeOutput.h" #include "ShaderCore.h" #include "EdGraphSchema_Niagara.h" +#include "EdGraphUtilities.h" #include "Misc/FileHelper.h" #include "ShaderCompiler.h" #include "NiagaraShader.h" @@ -34,7 +28,6 @@ #include "NiagaraRendererProperties.h" #include "NiagaraSimulationStageBase.h" #include "Serialization/MemoryReader.h" -#include "HAL/ThreadSafeBool.h" #include "../../Niagara/Private/NiagaraPrecompileContainer.h" #define LOCTEXT_NAMESPACE "NiagaraCompiler" @@ -143,7 +136,6 @@ void FNiagaraCompileRequestData::VisitReferencedGraphsRecursive(UNiagaraGraph* I { return; } - UPackage* OwningPackage = InGraph->GetOutermost(); TArray Nodes; InGraph->GetNodesOfClass(Nodes); @@ -182,8 +174,11 @@ void FNiagaraCompileRequestData::VisitReferencedGraphsRecursive(UNiagaraGraph* I bool bHasNumericParams = FunctionGraph->HasNumericParameters(); bool bHasNumericInputs = false; - UPackage* FunctionPackage = FunctionGraph->GetOutermost(); - bool bFromDifferentPackage = OwningPackage != FunctionPackage; + // Any function which is not directly owned by it's outer function call node must be cloned since its graph + // will be modified in some way with it's internals function calls replaced with cloned references, or with + // numeric fixup. Currently the only scripts which don't need cloning are scripts used by UNiagaraNodeAssignment + // module nodes and UNiagaraNodeCustomHlsl expression dynamic input nodes. + bool bRequiresClonedScript = FunctionScript->GetOuter()->IsA() == false; TArray CallOutputs; TArray CallInputs; @@ -216,40 +211,7 @@ void FNiagaraCompileRequestData::VisitReferencedGraphsRecursive(UNiagaraGraph* I if (!PreprocessedFunctions.Contains(FunctionGraph)) { UNiagaraScript* DupeScript = nullptr; - bool bNeedsDuplicateAndPreprocess = false; - if (bFromDifferentPackage || bHasNumericParams) - { - bNeedsDuplicateAndPreprocess = true; - } - else - { - // If the script isn't in a separate asset (e.g. scratch pad), and it doesn't have numeric inputs or outputs then we need to check the internal pins for numerics - // since those scripts need to be duplicated and preprocessed as well. - auto NodeHasNumericPins = [Schema](UEdGraphNode* Node) - { - UNiagaraNode* NiagaraNode = Cast(Node); - if (NiagaraNode == nullptr) - { - return false; - } - - for (UEdGraphPin* Pin : NiagaraNode->Pins) - { - if (Schema->PinToTypeDefinition(Pin) == FNiagaraTypeDefinition::GetGenericNumericDef()) - { - return true; - } - } - return false; - }; - - if(FunctionGraph->Nodes.ContainsByPredicate(NodeHasNumericPins)) - { - bNeedsDuplicateAndPreprocess = true; - } - } - - if (bNeedsDuplicateAndPreprocess == false) + if (bRequiresClonedScript == false) { DupeScript = FunctionScript; ProcessedGraph = FunctionGraph; @@ -312,7 +274,7 @@ void FNiagaraCompileRequestData::VisitReferencedGraphsRecursive(UNiagaraGraph* I FoundArray->Add(Data); VisitReferencedGraphsRecursive(ProcessedGraph, ConstantResolver, bNeedsCompilation); } - else if (bFromDifferentPackage) + else if(bRequiresClonedScript) { TArray* FoundArray = PreprocessedFunctions.Find(FunctionGraph); check(FoundArray != nullptr && FoundArray->Num() != 0); @@ -524,10 +486,9 @@ void FNiagaraCompileRequestData::FinishPrecompile(UNiagaraScriptSource* ScriptSo Builder.EnableScriptWhitelist(true, FoundOutputNode->GetUsage()); Builder.BuildParameterMaps(FoundOutputNode, true); - TArray Histories = Builder.Histories; - ensure(Histories.Num() <= 1); + ensure(Builder.Histories.Num() <= 1); - for (FNiagaraParameterMapHistory& History : Histories) + for (FNiagaraParameterMapHistory& History : Builder.Histories) { History.OriginatingScriptUsage = FoundOutputNode->GetUsage(); for (FNiagaraVariable& Var : History.Variables) @@ -543,31 +504,36 @@ void FNiagaraCompileRequestData::FinishPrecompile(UNiagaraScriptSource* ScriptSo NumSimStageNodes++; } - PrecompiledHistories.Append(Histories); + PrecompiledHistories.Append(Builder.Histories); Builder.EndTranslation(TranslationName); } if (SimStages && NumSimStageNodes) { - SpawnOnlyPerStage.AddZeroed(NumSimStageNodes); - NumIterationsPerStage.AddZeroed(NumSimStageNodes); - IterationSourcePerStage.AddZeroed(NumSimStageNodes); - StageGuids.AddDefaulted(NumSimStageNodes); - StageNames.AddDefaulted(NumSimStageNodes); - int32 NumProvidedStages = SimStages->Num(); + SpawnOnlyPerStage.Reserve(NumSimStageNodes); + PartialParticleUpdatePerStage.Reserve(NumSimStageNodes); + NumIterationsPerStage.Reserve(NumSimStageNodes); + IterationSourcePerStage.Reserve(NumSimStageNodes); + StageGuids.Reserve(NumSimStageNodes); + StageNames.Reserve(NumSimStageNodes); + const int32 NumProvidedStages = SimStages->Num(); - for (int32 i = 0; i < NumSimStageNodes && i < NumProvidedStages; i++) + for (int32 i=0; i < NumSimStageNodes && i < NumProvidedStages; ++i) { - UNiagaraSimulationStageGeneric* GenericStage = Cast((*SimStages)[i]); - - if (GenericStage) + UNiagaraSimulationStageBase* SimStage = (*SimStages)[i]; + if (SimStage == nullptr || !SimStage->bEnabled) { - NumIterationsPerStage[i] = GenericStage->Iterations; - IterationSourcePerStage[i] = GenericStage->IterationSource == ENiagaraIterationSource::DataInterface ? GenericStage->DataInterface.BoundVariable.GetName() : FName(); - SpawnOnlyPerStage[i] = GenericStage->bSpawnOnly; - StageGuids[i] = GenericStage->Script->GetUsageId(); - StageNames[i] = GenericStage->SimulationStageName; + continue; + } + if ( UNiagaraSimulationStageGeneric* GenericStage = Cast(SimStage) ) + { + NumIterationsPerStage.Add(GenericStage->Iterations); + IterationSourcePerStage.Add(GenericStage->IterationSource == ENiagaraIterationSource::DataInterface ? GenericStage->DataInterface.BoundVariable.GetName() : FName()); + SpawnOnlyPerStage.Add(GenericStage->bSpawnOnly); + PartialParticleUpdatePerStage.Add(GenericStage->bPartialParticleUpdate); + StageGuids.Add(GenericStage->Script->GetUsageId()); + StageNames.Add(GenericStage->SimulationStageName); } } } @@ -751,8 +717,6 @@ TSharedPtr FNiagaraEditorMo { for (const FNiagaraVariable& BoundAttribute : RendererProperty->GetBoundAttributes()) { - const int32 OrigCount = BasePtr->EmitterData[i]->RequiredRendererVariables.Num(); - BasePtr->EmitterData[i]->RequiredRendererVariables.AddUnique(BoundAttribute); } } @@ -938,8 +902,6 @@ int32 FNiagaraEditorModule::CompileScript(const FNiagaraCompileRequestDataBase* { SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_Module_CompileScript); - double StartTime = FPlatformTime::Seconds(); - check(InCompileRequest != NULL); const FNiagaraCompileRequestData* CompileRequest = (const FNiagaraCompileRequestData*)InCompileRequest; TArray CookedRapidIterationParams = CompileRequest->GetUseRapidIterationParams() ? TArray() : CompileRequest->RapidIterationParams; @@ -1357,6 +1319,11 @@ TOptional FHlslNiagaraCompiler::GetCompileResult(int32 J Results.Data->LastAssemblyTranslation = CompilationOutput.AssemblyAsString; Results.Data->LastOpCount = CompilationOutput.NumOps; + if (GbForceNiagaraVMBinaryDump != 0 && Results.Data.IsValid()) + { + DumpHLSLText(Results.Data->LastAssemblyTranslation, CompilationJob->CompileResults.DumpDebugInfoPath); + } + Results.Data->InternalParameters.Empty(); for (int32 i = 0; i < CompilationOutput.InternalConstantOffsets.Num(); ++i) { diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraComponentBroker.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraComponentBroker.h new file mode 100644 index 000000000000..b61d7e6d955f --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraComponentBroker.h @@ -0,0 +1,37 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "ComponentAssetBroker.h" +#include "NiagaraComponent.h" + +class FNiagaraComponentBroker : public IComponentAssetBroker +{ +public: + UClass* GetSupportedAssetClass() override + { + return UNiagaraSystem::StaticClass(); + } + + virtual bool AssignAssetToComponent(UActorComponent* InComponent, UObject* InAsset) override + { + if (UNiagaraComponent* NiagaraComponent = Cast(InComponent)) + { + if (UNiagaraSystem* NiagaraSystem = Cast(InAsset)) + { + NiagaraComponent->SetAsset(NiagaraSystem); + return true; + } + } + return false; + } + + virtual UObject* GetAssetFromComponent(UActorComponent* InComponent) override + { + if (UNiagaraComponent* NiagaraComponent = Cast(InComponent)) + { + return NiagaraComponent->GetFXSystemAsset(); + } + return nullptr; + } +}; \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorModule.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorModule.cpp index d1edcefb34a6..174f228cad4e 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorModule.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorModule.cpp @@ -13,6 +13,7 @@ #include "SequencerSettings.h" #include "AssetRegistryModule.h" #include "ThumbnailRendering/ThumbnailManager.h" +#include "Stats/Stats.h" #include "AssetTypeActions/AssetTypeActions_NiagaraSystem.h" #include "AssetTypeActions/AssetTypeActions_NiagaraEmitter.h" @@ -74,6 +75,7 @@ #include "NiagaraEditorCommands.h" #include "NiagaraClipboard.h" #include "NiagaraMessageManager.h" +#include "NiagaraComponentBroker.h" #include "MovieScene/Parameters/MovieSceneNiagaraBoolParameterTrack.h" #include "MovieScene/Parameters/MovieSceneNiagaraFloatParameterTrack.h" @@ -847,6 +849,11 @@ void FNiagaraEditorModule::StartupModule() FNiagaraUserParameterBinding::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FNiagaraUserParameterBindingCustomization::MakeInstance)); + + PropertyModule.RegisterCustomPropertyTypeLayout( + FNiagaraMaterialAttributeBinding::StaticStruct()->GetFName(), + FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FNiagaraMaterialAttributeBindingCustomization::MakeInstance)); + PropertyModule.RegisterCustomPropertyTypeLayout( FNiagaraScriptHighlight::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FNiagaraScriptHighlightDetails::MakeInstance)); @@ -863,6 +870,9 @@ void FNiagaraEditorModule::StartupModule() FNiagaraEditorCommands::Register(); + NiagaraComponentBroker = MakeShareable(new FNiagaraComponentBroker); + FComponentAssetBrokerage::RegisterBroker(NiagaraComponentBroker, UNiagaraComponent::StaticClass(), true, true); + TSharedPtr GraphPanelPinFactory = MakeShareable(new FNiagaraScriptGraphPanelPinFactory()); GraphPanelPinFactory->RegisterTypePin(FNiagaraTypeDefinition::GetFloatStruct(), FNiagaraScriptGraphPanelPinFactory::FCreateGraphPin::CreateLambda( @@ -906,6 +916,7 @@ void FNiagaraEditorModule::StartupModule() RegisterTypeUtilities(FNiagaraTypeDefinition::GetQuatDef(), MakeShareable(new FNiagaraEditorQuatTypeUtilities())); RegisterTypeUtilities(FNiagaraTypeDefinition::GetColorDef(), MakeShareable(new FNiagaraEditorColorTypeUtilities())); RegisterTypeUtilities(FNiagaraTypeDefinition::GetMatrix4Def(), MakeShareable(new FNiagaraEditorMatrixTypeUtilities())); + RegisterTypeUtilities(FNiagaraTypeDefinition::GetIDDef(), MakeShareable(new FNiagaraEditorNiagaraIDTypeUtilities())); RegisterTypeUtilities(FNiagaraTypeDefinition(UNiagaraDataInterfaceCurve::StaticClass()), MakeShared()); RegisterTypeUtilities(FNiagaraTypeDefinition(UNiagaraDataInterfaceVector2DCurve::StaticClass()), MakeShared()); @@ -1089,6 +1100,11 @@ void FNiagaraEditorModule::ShutdownModule() UnregisterSettings(); + if (UObjectInitialized()) + { + FComponentAssetBrokerage::UnregisterBroker(NiagaraComponentBroker); + } + ISequencerModule* SequencerModule = FModuleManager::GetModulePtr("Sequencer"); if (SequencerModule != nullptr) { @@ -1179,9 +1195,11 @@ FNiagaraEditorModule& FNiagaraEditorModule::Get() return FModuleManager::LoadModuleChecked("NiagaraEditor"); } -void FNiagaraEditorModule::OnNiagaraSettingsChangedEvent(const FString& PropertyName, const UNiagaraSettings* Settings) +void FNiagaraEditorModule::OnNiagaraSettingsChangedEvent(const FName& PropertyName, const UNiagaraSettings* Settings) { - if (PropertyName == "AdditionalParameterTypes" || PropertyName == "AdditionalPayloadTypes") + if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSettings, AdditionalParameterTypes) + || PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSettings, AdditionalPayloadTypes) + || PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraSettings, AdditionalParameterEnums)) { FNiagaraTypeDefinition::RecreateUserDefinedTypeRegistry(); } @@ -1414,18 +1432,21 @@ void FNiagaraEditorModule::AddReferencedObjects(FReferenceCollector& Collector) void FNiagaraEditorModule::OnPreGarbageCollection() { - // For commandlets like GenerateDistillFileSetsCommandlet, they just load the package and do some hierarchy navigation within it - // tracking sub-assets, then they garbage collect. Since nothing is holding onto the system at the root level, it will be summarily - // killed and any of references will also be killed. To thwart this for now, we are forcing the compilations to complete BEFORE - // garbage collection kicks in. To do otherwise for now has too many loose ends (a system may be left around after the level has been - // unloaded, leaving behind weird external references, etc). This should be revisited when more time is available (i.e. not days before a - // release is due to go out). - for (TObjectIterator It; It; ++It) + if (IsRunningCommandlet()) { - UNiagaraSystem* System = *It; - if (System && System->HasOutstandingCompilationRequests()) + // For commandlets like GenerateDistillFileSetsCommandlet, they just load the package and do some hierarchy navigation within it + // tracking sub-assets, then they garbage collect. Since nothing is holding onto the system at the root level, it will be summarily + // killed and any of references will also be killed. To thwart this for now, we are forcing the compilations to complete BEFORE + // garbage collection kicks in. To do otherwise for now has too many loose ends (a system may be left around after the level has been + // unloaded, leaving behind weird external references, etc). This should be revisited when more time is available (i.e. not days before a + // release is due to go out). + for (TObjectIterator It; It; ++It) { - System->WaitForCompilationComplete(); + UNiagaraSystem* System = *It; + if (System && System->HasOutstandingCompilationRequests()) + { + System->WaitForCompilationComplete(); + } } } } @@ -1480,6 +1501,7 @@ void FNiagaraEditorModule::EnqueueObjectForDeferredDestructionInternal(FDeferred bool FNiagaraEditorModule::DeferredDestructObjects(float InDeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FNiagaraEditorModule_DeferredDestructObjects); EnqueuedForDeferredDestruction.Empty(); return false; } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorUtilities.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorUtilities.cpp index b9435c67f4c3..c165fcebb456 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorUtilities.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorUtilities.cpp @@ -16,7 +16,6 @@ #include "NiagaraScript.h" #include "NiagaraNodeOutput.h" #include "NiagaraOverviewNode.h" -#include "EdGraphUtilities.h" #include "NiagaraConstants.h" #include "Widgets/SWidget.h" #include "Widgets/Text/STextBlock.h" @@ -30,17 +29,14 @@ #include "ViewModels/NiagaraEmitterHandleViewModel.h" #include "ViewModels/NiagaraOverviewGraphViewModel.h" #include "ViewModels/NiagaraSystemSelectionViewModel.h" -#include "HAL/PlatformApplicationMisc.h" #include "AssetRegistryModule.h" #include "Misc/FeedbackContext.h" #include "EdGraphSchema_Niagara.h" #include "HAL/PlatformFileManager.h" #include "Misc/FileHelper.h" #include "EdGraph/EdGraphPin.h" -#include "NiagaraNodeWriteDataSet.h" #include "ViewModels/Stack/NiagaraParameterHandle.h" #include "NiagaraNodeStaticSwitch.h" -#include "NiagaraNodeFunctionCall.h" #include "NiagaraParameterMapHistory.h" #include "ScopedTransaction.h" #include "NiagaraStackEditorData.h" @@ -49,8 +45,8 @@ #include "ContentBrowserModule.h" #include "Modules/ModuleManager.h" #include "AssetToolsModule.h" +#include "NiagaraCustomVersion.h" #include "Subsystems/AssetEditorSubsystem.h" -#include "Framework/Commands/GenericCommands.h" #include "UObject/TextProperty.h" #include "Editor/EditorEngine.h" #include "Widgets/Notifications/SNotificationList.h" @@ -925,6 +921,38 @@ bool FNiagaraEditorUtilities::IsCompilableAssetClass(UClass* AssetClass) return CompilableClasses.Contains(AssetClass); } +FText FNiagaraEditorUtilities::GetVariableTypeCategory(const FNiagaraVariable& Variable) +{ + return GetTypeDefinitionCategory(Variable.GetType()); +} + +FText FNiagaraEditorUtilities::GetTypeDefinitionCategory(const FNiagaraTypeDefinition& TypeDefinition) +{ + FText Category = FText::GetEmpty(); + if (TypeDefinition.IsDataInterface()) + { + Category = LOCTEXT("NiagaraParameterMenuGroupDI", "Data Interface"); + } + else if (TypeDefinition.IsEnum()) + { + Category = LOCTEXT("NiagaraParameterMenuGroupEnum", "Enum"); + } + else if (TypeDefinition.IsUObject()) + { + Category = LOCTEXT("NiagaraParameterMenuGroupObject", "Object"); + } + else if (TypeDefinition.GetNameText().ToString().Contains("event")) + { + Category = LOCTEXT("NiagaraParameterMenuGroupEventType", "Event"); + } + else + { + // add common types like bool, vector, etc into a category of their own so they appear first in the search + Category = LOCTEXT("NiagaraParameterMenuGroupCommon", "Common"); + } + return Category; +} + void FNiagaraEditorUtilities::MarkDependentCompilableAssetsDirty(TArray InObjects) { const FText LoadAndMarkDirtyDisplayName = NSLOCTEXT("NiagaraEditor", "MarkDependentAssetsDirtySlowTask", "Loading and marking dependent assets dirty."); @@ -1346,27 +1374,11 @@ void FNiagaraEditorUtilities::GetFilteredScriptAssets(FGetFilteredScriptAssetsOp } } - // Check if library script - if (InFilter.bIncludeNonLibraryScripts == false) + // Check script visibility + ENiagaraScriptLibraryVisibility ScriptVisibility = GetScriptAssetVisibility(FilteredScriptAssets[i]); + if (ScriptVisibility == ENiagaraScriptLibraryVisibility::Hidden || (InFilter.bIncludeNonLibraryScripts == false && ScriptVisibility != ENiagaraScriptLibraryVisibility::Library)) { - bool bScriptIsLibrary = true; - bool bFoundLibScriptTag = FilteredScriptAssets[i].GetTagValue(GET_MEMBER_NAME_CHECKED(UNiagaraScript, bExposeToLibrary), bScriptIsLibrary); - - if (bFoundLibScriptTag == false) - { - if (FilteredScriptAssets[i].IsAssetLoaded()) - { - UNiagaraScript* Script = static_cast(FilteredScriptAssets[i].GetAsset()); - if (Script != nullptr) - { - bScriptIsLibrary = Script->bExposeToLibrary; - } - } - } - if (bScriptIsLibrary == false) - { - continue; - } + continue; } OutFilteredScriptAssets.Add(FilteredScriptAssets[i]); @@ -1442,10 +1454,12 @@ const FNiagaraEmitterHandle* FNiagaraEditorUtilities::GetEmitterHandleForEmitter [&Emitter](const FNiagaraEmitterHandle& EmitterHandle) { return EmitterHandle.GetInstance() == &Emitter; }); } -bool FNiagaraEditorUtilities::IsScriptAssetInLibrary(const FAssetData& ScriptAssetData) +ENiagaraScriptLibraryVisibility FNiagaraEditorUtilities::GetScriptAssetVisibility(const FAssetData& ScriptAssetData) { - bool bIsInLibrary; - bool bIsLibraryTagFound = ScriptAssetData.GetTagValue(GET_MEMBER_NAME_CHECKED(UNiagaraScript, bExposeToLibrary), bIsInLibrary); + FString Value; + bool bIsLibraryTagFound = ScriptAssetData.GetTagValue(GET_MEMBER_NAME_CHECKED(UNiagaraScript, LibraryVisibility), Value); + + ENiagaraScriptLibraryVisibility ScriptVisibility = ENiagaraScriptLibraryVisibility::Invalid; if (bIsLibraryTagFound == false) { if (ScriptAssetData.IsAssetLoaded()) @@ -1453,15 +1467,30 @@ bool FNiagaraEditorUtilities::IsScriptAssetInLibrary(const FAssetData& ScriptAss UNiagaraScript* Script = static_cast(ScriptAssetData.GetAsset()); if (Script != nullptr) { - bIsInLibrary = Script->bExposeToLibrary; + ScriptVisibility = Script->LibraryVisibility; } } - else - { - bIsInLibrary = false; - } } - return bIsInLibrary; + else + { + UEnum* VisibilityEnum = StaticEnum(); + int32 Index = VisibilityEnum->GetIndexByNameString(Value); + ScriptVisibility = (ENiagaraScriptLibraryVisibility) VisibilityEnum->GetValueByIndex(Index == INDEX_NONE ? 0 : Index); + } + + if (ScriptVisibility == ENiagaraScriptLibraryVisibility::Invalid) + { + // Check the deprecated tag value as a fallback. If even that property cannot be found the asset must be pretty old and should just be exposed as that was the default in the beginning. + bool bIsExposed = false; + bIsLibraryTagFound = ScriptAssetData.GetTagValue(FName("bExposeToLibrary"), bIsExposed); + ScriptVisibility = !bIsLibraryTagFound || bIsExposed ? ENiagaraScriptLibraryVisibility::Library : ENiagaraScriptLibraryVisibility::Unexposed; + } + return ScriptVisibility; +} + +bool FNiagaraEditorUtilities::IsScriptAssetInLibrary(const FAssetData& ScriptAssetData) +{ + return GetScriptAssetVisibility(ScriptAssetData) == ENiagaraScriptLibraryVisibility::Library; } NIAGARAEDITOR_API FText FNiagaraEditorUtilities::FormatScriptName(FName Name, bool bIsInLibrary) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraGraph.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraGraph.cpp index 50ae375b9a0a..41eefa29f1ab 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraGraph.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraGraph.cpp @@ -1,23 +1,15 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraGraph.h" -#include "Modules/ModuleManager.h" #include "NiagaraCommon.h" #include "NiagaraEditorModule.h" #include "NiagaraScript.h" #include "NiagaraComponent.h" -#include "UObject/UObjectHash.h" -#include "UObject/UObjectIterator.h" -#include "ComponentReregisterContext.h" #include "NiagaraConstants.h" #include "NiagaraSystem.h" #include "NiagaraNodeOutput.h" #include "NiagaraNodeInput.h" -#include "NiagaraNodeWriteDataSet.h" -#include "NiagaraNodeReadDataSet.h" -#include "NiagaraScript.h" #include "NiagaraScriptSource.h" -#include "NiagaraDataInterface.h" #include "GraphEditAction.h" #include "EdGraphSchema_Niagara.h" #include "NiagaraNodeParameterMapBase.h" @@ -25,17 +17,13 @@ #include "NiagaraNodeParameterMapSet.h" #include "NiagaraNodeReroute.h" #include "INiagaraEditorTypeUtilities.h" -#include "NiagaraConstants.h" -#include "NiagaraEditorModule.h" #include "NiagaraEditorUtilities.h" #include "NiagaraNode.h" -#include "EdGraphSchema_Niagara.h" -#include "ViewModels/Stack/NiagaraParameterHandle.h" +#include "NiagaraCustomVersion.h" #include "NiagaraNodeStaticSwitch.h" #include "NiagaraScriptVariable.h" #include "NiagaraHlslTranslator.h" #include "NiagaraNodeFunctionCall.h" -#include "NiagaraScriptVariable.h" #include "Misc/SecureHash.h" DECLARE_CYCLE_STAT(TEXT("NiagaraEditor - Graph - FindInputNodes"), STAT_NiagaraEditor_Graph_FindInputNodes, STATGROUP_NiagaraEditor); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.cpp index 9b5c45057703..412b522f1113 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.cpp @@ -5,14 +5,11 @@ #include "NiagaraGraph.h" #include "NiagaraScriptSource.h" #include "EdGraphUtilities.h" -#include "UObject/UObjectHash.h" #include "NiagaraNode.h" #include "NiagaraNodeFunctionCall.h" #include "NiagaraNodeIf.h" #include "NiagaraNodeInput.h" #include "NiagaraNodeOutput.h" -#include "NiagaraNodeReadDataSet.h" -#include "NiagaraNodeWriteDataSet.h" #include "NiagaraNodeParameterMapGet.h" #include "NiagaraNodeParameterMapSet.h" #include "NiagaraNodeParameterMapFor.h" @@ -20,16 +17,11 @@ #include "NiagaraNodeOp.h" #include "NiagaraNodeConvert.h" #include "EdGraphSchema_Niagara.h" -#include "Interfaces/ITargetPlatformManagerModule.h" -#include "Interfaces/IShaderFormat.h" -#include "ShaderFormatVectorVM.h" #include "NiagaraConstants.h" -#include "NiagaraSystem.h" #include "NiagaraNodeEmitter.h" #include "INiagaraEditorTypeUtilities.h" #include "NiagaraEditorUtilities.h" #include "NiagaraEditorModule.h" -#include "NiagaraNodeReroute.h" #include "NiagaraSimulationStageBase.h" #include "NiagaraFunctionLibrary.h" @@ -46,7 +38,6 @@ #include "NiagaraParameterCollection.h" #include "NiagaraEditorTickables.h" #include "ShaderCore.h" -#include "NiagaraShaderCompilationManager.h" #include "NiagaraEditorSettings.h" #include "NiagaraNodeStaticSwitch.h" @@ -109,7 +100,7 @@ void FNiagaraShaderQueueTickable::ProcessQueue() UE_LOG(LogNiagaraEditor, Log, TEXT("GPU shader compile skipped. Id %d"), NewShaderMap->GetCompilingId()); continue; } - UNiagaraScript* CompilableScript = ShaderScript->GetBaseVMScript(); + UNiagaraScript* CompilableScript = CastChecked(ShaderScript->GetBaseVMScript()); // For now System scripts don't generate HLSL and go through a special pass... // [OP] thinking they'll likely never run on GPU anyways @@ -139,8 +130,8 @@ void FNiagaraShaderQueueTickable::ProcessQueue() TRefCountPtr CompilerEnvironment = new FShaderCompilerEnvironment(); FString ShaderCode = CompilableScript->GetVMExecutableData().LastHlslTranslationGPU; - // When not running in the editor, the shaders are created in-sync in the postload. - const bool bSynchronousCompile = !GIsEditor || GIsAutomationTesting; + // Shaders are created in-sync in the postload when running the automated tests. + const bool bSynchronousCompile = GIsAutomationTesting; // Compile the shaders for the script. NewShaderMap->Compile(ShaderScript, Item.ShaderMapId, CompilerEnvironment, NewCompilationOutput, Item.Platform, bSynchronousCompile, Item.bApply); @@ -877,10 +868,11 @@ void FHlslNiagaraTranslator::TrimAttributes(const FNiagaraCompileOptions& InComp } // or are required by the renderer - if (CompileData->RequiredRendererVariables.Contains(Var)) + // Commenting this out for now as we explicitly add these to the preserve list + /*if (CompileData->RequiredRendererVariables.Contains(Var)) { return false; - } + }*/ // or are special? if (Var == SYS_PARAM_PARTICLES_UNIQUE_ID) @@ -910,6 +902,7 @@ void FHlslNiagaraTranslator::TrimAttributes(const FNiagaraCompileOptions& InComp } } + //UE_LOG(LogNiagaraEditor, Warning, TEXT("Trimming variable %s"), *Var.GetName().ToString()) return true; })); } @@ -1056,6 +1049,8 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara TranslationStages[0].SimulationStageIndexMax = 1; TranslationStages[1].SimulationStageIndexMin = 0; TranslationStages[1].SimulationStageIndexMax = 1; + TranslationStages[0].bWritesParticles = true; + TranslationStages[1].bWritesParticles = true; ParamMapHistories.AddDefaulted(2); ParamMapSetVariablesToChunks.AddDefaulted(2); break; @@ -1073,6 +1068,8 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara TranslationStages[0].SimulationStageIndexMax = 1; TranslationStages[1].SimulationStageIndexMin = 0; TranslationStages[1].SimulationStageIndexMax = 1; + TranslationStages[0].bWritesParticles = true; + TranslationStages[1].bWritesParticles = true; ParamMapHistories.AddDefaulted(2); ParamMapSetVariablesToChunks.AddDefaulted(2); @@ -1130,28 +1127,51 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara TranslationStages[Index].SimulationStageIndexMax = SimStageStartIndex + NumIterationsThisStage; TranslationStages[Index].NumIterationsThisStage = NumIterationsThisStage; TranslationStages[Index].bSpawnOnly = bSpawnOnly; + TranslationStages[Index].bPartialParticleUpdate = InCompileData->PartialParticleUpdatePerStage.IsValidIndex(SimStageIndex) ? InCompileData->PartialParticleUpdatePerStage[SimStageIndex] : false; TranslationStages[Index].IterationSource = IterationSrc; TranslationStages[Index].SourceSimStage = SimStageIndex; SimStageStartIndex += NumIterationsThisStage; ParamMapHistories.AddDefaulted(1); - // Set up the compile output for the shader stages so that we can properly execute at runtime. - int32 IterationMetaIdx = CompilationOutput.ScriptData.SimulationStageMetaData.AddDefaulted(); - CompilationOutput.ScriptData.SimulationStageMetaData[IterationMetaIdx].bSpawnOnly = bSpawnOnly; - CompilationOutput.ScriptData.SimulationStageMetaData[IterationMetaIdx].IterationSource = IterationSrc; - CompilationOutput.ScriptData.SimulationStageMetaData[IterationMetaIdx].MinStage = TranslationStages[Index].SimulationStageIndexMin; - CompilationOutput.ScriptData.SimulationStageMetaData[IterationMetaIdx].MaxStage = TranslationStages[Index].SimulationStageIndexMax; - // See if we write any "particle" attributes - for (const FNiagaraVariable& Var : FoundHistory.Variables) + for (int32 iVar = 0; iVar < FoundHistory.VariableMetaData.Num(); ++iVar) { - if (FNiagaraParameterMapHistory::IsAttribute(Var)) + // Particle attribute? + if (!FNiagaraParameterMapHistory::IsAttribute(FoundHistory.Variables[iVar])) { - CompilationOutput.ScriptData.SimulationStageMetaData[IterationMetaIdx].bWritesParticles = true; - break; + continue; } + + // Is this an output? + const bool bIsOutput = FoundHistory.PerVariableWriteHistory[iVar].ContainsByPredicate([](const UEdGraphPin* InPin) -> bool { return Cast(InPin->GetOwningNode()) != nullptr; }); + if (!bIsOutput) + { + continue; + } + + //-TODO: Temporarily skip the IGNORE variable, this needs to be cleaned up + static const FName Name_IGNORE("IGNORE"); + FName ParameterName; + if (FoundHistory.VariableMetaData[iVar].GetParameterName(ParameterName) && (ParameterName == Name_IGNORE)) + { + continue; + } + + // We write particle attributes at this stage, store list off so we can potentially selectivly write them later + TranslationStages[Index].bWritesParticles = true; + TranslationStages[Index].SetParticleAttributes.Add(FoundHistory.Variables[iVar]); } + // Set up the compile output for the shader stages so that we can properly execute at runtime. + FSimulationStageMetaData& SimulationStageMetaData = CompilationOutput.ScriptData.SimulationStageMetaData.AddDefaulted_GetRef(); + SimulationStageMetaData.SimulationStageName = InCompileData->StageNames.IsValidIndex(SimStageIndex) ? InCompileData->StageNames[SimStageIndex] : FName(); + SimulationStageMetaData.bSpawnOnly = bSpawnOnly; + SimulationStageMetaData.IterationSource = IterationSrc; + SimulationStageMetaData.MinStage = TranslationStages[Index].SimulationStageIndexMin; + SimulationStageMetaData.MaxStage = TranslationStages[Index].SimulationStageIndexMax; + SimulationStageMetaData.bWritesParticles = TranslationStages[Index].bWritesParticles; + SimulationStageMetaData.bPartialParticleUpdate = TranslationStages[Index].bPartialParticleUpdate; + // Other outputs are written to as appropriate data interfaces are found. See HandleDataInterfaceCall for details. ParamMapSetVariablesToChunks.AddDefaulted(1); @@ -1167,6 +1187,7 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara TranslationStages[0].ChunkModeIndex = ENiagaraCodeChunkMode::Body; TranslationStages[0].SimulationStageIndexMin = 0; TranslationStages[0].SimulationStageIndexMax = 0; + TranslationStages[0].bWritesParticles = true; ParamMapHistories.AddDefaulted(1); ParamMapSetVariablesToChunks.AddDefaulted(1); break; @@ -1697,12 +1718,14 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara for (int32 i = 0; i < CompilationOutput.ScriptData.SimulationStageMetaData.Num(); i++) { - Preamble += FString::Printf(TEXT("// SimStage[%d]\n//\t Iteration Src: \"%s\"\n//\tMinIndex: %d MaxIndex: %d\n//\tbSpawnOnly: %s\n//\tWritesParticles: %s\n"), i, + Preamble += FString::Printf(TEXT("// SimStage[%d]\n//\t Iteration Src: \"%s\"\n//\tMinIndex: %d MaxIndex: %d\n//\tbSpawnOnly: %s\n//\tWritesParticles: %s\n//\tPartialParticleUpdate: %s\n"), i, *CompilationOutput.ScriptData.SimulationStageMetaData[i].IterationSource.ToString(), CompilationOutput.ScriptData.SimulationStageMetaData[i].MinStage, CompilationOutput.ScriptData.SimulationStageMetaData[i].MaxStage, CompilationOutput.ScriptData.SimulationStageMetaData[i].bSpawnOnly ? TEXT("True") : TEXT("False"), - CompilationOutput.ScriptData.SimulationStageMetaData[i].bWritesParticles ? TEXT("True") : TEXT("False")); + CompilationOutput.ScriptData.SimulationStageMetaData[i].bWritesParticles ? TEXT("True") : TEXT("False"), + CompilationOutput.ScriptData.SimulationStageMetaData[i].bPartialParticleUpdate ? TEXT("True") : TEXT("False") + ); for (const FName& Dest : CompilationOutput.ScriptData.SimulationStageMetaData[i].OutputDestinations) { Preamble += FString::Printf(TEXT("//\tOutputs to: \"%s\"\n"), *Dest.ToString()); @@ -2347,7 +2370,14 @@ void FHlslNiagaraTranslator::DefineMainGPUFunctions( } else { - VarFmt = VarName + TEXT("{0} = InputData{1}({2}, {3}, InstanceIdx);\n"); + if (TranslationStages[i].bPartialParticleUpdate) + { + VarFmt = VarName + TEXT("{0} = RWInputData{1}({2}, {3}, InstanceIdx);\n"); + } + else + { + VarFmt = VarName + TEXT("{0} = InputData{1}({2}, {3}, InstanceIdx);\n"); + } if (bUseSimulationStages) { @@ -2489,6 +2519,13 @@ void FHlslNiagaraTranslator::DefineMainGPUFunctions( continue; } + // If we do not write particle data or kill particles we can avoid the write altogether which will allow us to also cull attribute reads to the ones that are only 'required' + if (!TranslationStages[i].bWritesParticles) + { + ensure(TranslationStages[i].bWritesAlive == false); + continue; + } + if (TranslationStages.Num() > 2) { if (StartIdx == i) @@ -2504,6 +2541,7 @@ void FHlslNiagaraTranslator::DefineMainGPUFunctions( " {\n"), TranslationStages[i].SimulationStageIndexMin, TranslationStages[i].SimulationStageIndexMax); } + bool bWriteInstanceCount = !TranslationStages[i].bPartialParticleUpdate; if (TranslationStages[i].bWritesAlive || (i == 1 && TranslationStages[0].bWritesAlive)) { // This stage kills particles, so we must skip the dead ones when writing out the data. @@ -2511,7 +2549,7 @@ void FHlslNiagaraTranslator::DefineMainGPUFunctions( // if we could only do this for newly spawned particles, but unfortunately that would mean placing thread sync operations // under dynamic flow control, which is not allowed. Therefore, we must always use the more expensive path when the spawn phase // can kill particles. - HlslOutput += TEXT("\t\tGStageWritesAlive = true;\n"); + bWriteInstanceCount = false; HlslOutput += TEXT("\t\tconst bool bValid = Context.") + TranslationStages[i].PassNamespace + TEXT(".DataInstance.Alive;\n"); HlslOutput += TEXT("\t\tconst int WriteIndex = OutputIndex(0, true, bValid);\n"); } @@ -2541,9 +2579,11 @@ void FHlslNiagaraTranslator::DefineMainGPUFunctions( const TArray& NiagaraVariables = DataSetVariables[DataSetWrites[DataSetID]]; for (const FNiagaraVariable& Var : NiagaraVariables) { + const bool bWriteToHLSL = !TranslationStages[i].bPartialParticleUpdate || TranslationStages[i].SetParticleAttributes.Contains(Var); + // If coming from a parameter map, use the one on the context, otherwise use the output. FString VarFmt = TEXT("\t\t\tOutputData{1}(0, {2}, {3}, ") + ContextName + GetSanitizedSymbolName(Var.GetName().ToString()) + TEXT("{0});\n"); - GatherVariableForDataSetAccess(Var, VarFmt, IntCounter, FloatCounter, HalfCounter, -1, TEXT("WriteIndex"), HlslOutput); + GatherVariableForDataSetAccess(Var, VarFmt, IntCounter, FloatCounter, HalfCounter, -1, TEXT("WriteIndex"), HlslOutput, bWriteToHLSL); } } @@ -2551,6 +2591,18 @@ void FHlslNiagaraTranslator::DefineMainGPUFunctions( if (TranslationStages.Num() > 2) { + if (bWriteInstanceCount) + { + HlslOutput += TEXT( + "\t\t// If a stage doesn't kill particles, StoreUpdateVariables() never calls AcquireIndex(), so the\n" + "\t\t// count isn't updated. In that case we must manually copy the original count here.\n" + "\t\tif (WriteInstanceCountOffset != 0xFFFFFFFF && GDispatchThreadId.x == 0) \n" + "\t\t{\n" + "\t\t RWInstanceCounts[WriteInstanceCountOffset] = GSpawnStartInstance + SpawnedInstances; \n" + "\t\t}\n" + ); + } + HlslOutput += TEXT("\t}\n"); } @@ -2663,10 +2715,11 @@ void FHlslNiagaraTranslator::DefineMainGPUFunctions( " }\n" " bool bRunUpdateLogic, bRunSpawnLogic;\n" " #if USE_SIMULATION_STAGES\n" - " if (IterationInterfaceCount > 0)\n" + " int IterationInterfaceInstanceCount = SimulationStage_GetInstanceCount();\n" + " if (IterationInterfaceInstanceCount > 0)\n" " {\n" - " bRunUpdateLogic = InstanceID < IterationInterfaceCount && GSimStart != 1;\n" - " bRunSpawnLogic = InstanceID < IterationInterfaceCount && GSimStart == 1;\n" + " bRunUpdateLogic = InstanceID < IterationInterfaceInstanceCount && GSimStart != 1;\n" + " bRunSpawnLogic = InstanceID < IterationInterfaceInstanceCount && GSimStart == 1;\n" " }\n" " else\n" " #endif // USE_SIMULATION_STAGES\n" @@ -2694,7 +2747,7 @@ void FHlslNiagaraTranslator::DefineMainGPUFunctions( " GCurrentPhase = GSpawnPhase; \n" " #if USE_SIMULATION_STAGES\n" " // Only process the spawn info for particle-based stages. Stages with an iteration interface expect the exec index to simply be the thread index.\n" - " if (IterationInterfaceCount > 0)\n" + " if (IterationInterfaceInstanceCount > 0)\n" " {\n" " SetupExecIndexForGPU();\n" " }\n" @@ -2727,18 +2780,16 @@ void FHlslNiagaraTranslator::DefineMainGPUFunctions( " StoreUpdateVariables(Context);\n\n" ); - if (bUseSimulationStages) + if (GetUsesOldShaderStages()) { HlslOutput += TEXT( - " // If a stage doesn't kill particles, StoreUpdateVariables() never calls AcquireIndex(), so the\n" - " // count isn't updated. In that case we must manually copy the original count here.\n" - " if (!GStageWritesAlive && WriteInstanceCountOffset != 0xFFFFFFFF && GDispatchThreadId.x == 0) \n" + " if (WriteInstanceCountOffset != 0xFFFFFFFF && GDispatchThreadId.x == 0) \n" " { \n" " RWInstanceCounts[WriteInstanceCountOffset] = GSpawnStartInstance + SpawnedInstances; \n" " } \n" ); } - + HlslOutput += TEXT("}\n"); } @@ -4348,6 +4399,14 @@ void FHlslNiagaraTranslator::ParameterMapSet(UNiagaraNodeParameterMapSet* SetNod { if (Var == FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("DataInstance.Alive"))) { + const int32 OutputStageIndex = ActiveStageIdx - 2; + if (CompilationOutput.ScriptData.SimulationStageMetaData.IsValidIndex(OutputStageIndex)) + { + CompilationOutput.ScriptData.SimulationStageMetaData[OutputStageIndex].bWritesParticles = true; + CompilationOutput.ScriptData.SimulationStageMetaData[OutputStageIndex].bPartialParticleUpdate = false; + } + TranslationStages[ActiveStageIdx].bWritesParticles = true; + TranslationStages[ActiveStageIdx].bPartialParticleUpdate = false; TranslationStages[ActiveStageIdx].bWritesAlive = true; } AddBodyChunk(ParameterMapInstanceName + TEXT(".") + GetSanitizedSymbolName(Var.GetName().ToString()), TEXT("{0}"), Var.GetType(), Input, false); @@ -4457,6 +4516,25 @@ bool FHlslNiagaraTranslator::GetLiteralConstantVariable(FNiagaraVariable& OutVar OutVar.SetValue(EnumValue); return true; } + else if (OutVar == FNiagaraVariable(FNiagaraTypeDefinition::GetScriptContextEnum(), TEXT("Script.Context"))) + { + ENiagaraScriptUsage Usage = GetCurrentUsage(); + FNiagaraInt32 EnumValue; + if (Usage == ENiagaraScriptUsage::SystemSpawnScript || Usage == ENiagaraScriptUsage::SystemUpdateScript) + { + EnumValue.Value = (uint8)ENiagaraScriptContextStaticSwitch::System; + } + else if (Usage == ENiagaraScriptUsage::EmitterSpawnScript || Usage == ENiagaraScriptUsage::EmitterUpdateScript) + { + EnumValue.Value = (uint8)ENiagaraScriptContextStaticSwitch::Emitter; + } + else + { + EnumValue.Value = (uint8)ENiagaraScriptContextStaticSwitch::Particle; + } + OutVar.SetValue(EnumValue); + return true; + } return false; } @@ -5558,6 +5636,18 @@ void FHlslNiagaraTranslator::FunctionCall(UNiagaraNodeFunctionCall* FunctionNode Source = CastChecked(FunctionNode->FunctionScript->GetSource()); check(Source->GetOutermost() == GetTransientPackage()); } + else if (Signature.bRequiresExecPin) + { + if (CallInputs.Num() == 0 || Schema->PinToTypeDefinition(CallInputs[0]) != FNiagaraTypeDefinition::GetParameterMapDef()) + { + Error(LOCTEXT("FunctionCallInvalidSignatureExecIn", "The first input pin must be a parameter map pin because the signature RequiresExecPin!"), FunctionNode, nullptr); + } + if (CallOutputs.Num() == 0 || Schema->PinToTypeDefinition(CallOutputs[0]) != FNiagaraTypeDefinition::GetParameterMapDef()) + { + Error(LOCTEXT("FunctionCallInvalidSignatureExecOut", "The first output pin must be a parameter map pin because the signature RequiresExecPin!"), FunctionNode, nullptr); + } + } + UNiagaraNodeCustomHlsl* CustomFunctionHlsl = Cast(FunctionNode); if (CustomFunctionHlsl != nullptr) { @@ -6032,6 +6122,23 @@ void FHlslNiagaraTranslator::HandleDataInterfaceCall(FNiagaraScriptDataInterface { Error(FText::Format(LOCTEXT("FunctionCallDataInterfaceGPUMissing", "Function call \"{0}\" does not work on GPU sims."), FText::FromName(InMatchingSignature.Name)), CurNode, nullptr); } + + if (InMatchingSignature.ModuleUsageBitmask != 0 && !UNiagaraScript::IsSupportedUsageContextForBitmask(InMatchingSignature.ModuleUsageBitmask, TranslationStages[ActiveStageIdx].ScriptUsage)) + { + FString AllowedContexts; + TArray Usages = UNiagaraScript::GetSupportedUsageContextsForBitmask(InMatchingSignature.ModuleUsageBitmask); + for (ENiagaraScriptUsage Usage : Usages) + { + if (AllowedContexts.Len() > 0) + { + AllowedContexts.Append(TEXT(", ")); + } + UEnum* EnumClass = StaticEnum(); + check(EnumClass != nullptr); + AllowedContexts.Append(EnumClass->GetNameByValue((int64)Usage).ToString()); + } + Error(FText::Format(LOCTEXT("FunctionCallDataInterfaceWrongContext", "Function call \"{0}\" is not allowed for this stack context. Allowed: {1}"), FText::FromName(InMatchingSignature.Name), FText::FromString(AllowedContexts)), CurNode, nullptr); + } //UE_LOG(LogNiagaraEditor, Log, TEXT("HandleDataInterfaceCall %d %s %s %s"), ActiveStageIdx, *InMatchingSignature.Name.ToString(), InMatchingSignature.bWriteFunction ? TEXT("true") : TEXT("False"), *Info.Name.ToString()); @@ -6388,13 +6495,21 @@ void FHlslNiagaraTranslator::RegisterFunctionCall(ENiagaraScriptUsage ScriptUsag } else { - int32 OwnerIdx = Inputs[0]; - if (OwnerIdx < 0 || OwnerIdx >= CompilationOutput.ScriptData.DataInterfaceInfo.Num()) + + // Usually the DataInterface is the zeroth entry in the signature inputs, unless we are using the exec pin, in which case it is at index 1. + int32 DataInterfaceOwnerIdx = Inputs[0]; + if (InSignature.bRequiresExecPin) + { + ensure(Inputs.IsValidIndex(1)); + DataInterfaceOwnerIdx = Inputs[1]; + } + + if (DataInterfaceOwnerIdx < 0 || DataInterfaceOwnerIdx >= CompilationOutput.ScriptData.DataInterfaceInfo.Num()) { Error(LOCTEXT("FunctionCallDataInterfaceMissingRegistration", "Function call signature does not match to a registered DataInterface. Valid DataInterfaces should be wired into a DataInterface function call."), nullptr, nullptr); return; } - FNiagaraScriptDataInterfaceCompileInfo& Info = CompilationOutput.ScriptData.DataInterfaceInfo[OwnerIdx]; + FNiagaraScriptDataInterfaceCompileInfo& Info = CompilationOutput.ScriptData.DataInterfaceInfo[DataInterfaceOwnerIdx]; // Double-check to make sure that the signature matches those specified by the data // interface. It could be that the existing node has been removed and the graph @@ -6426,6 +6541,11 @@ void FHlslNiagaraTranslator::RegisterFunctionCall(ENiagaraScriptUsage ScriptUsag HandleDataInterfaceCall(Info, DataInterfaceFunctions[FoundMatch]); } + if (DataInterfaceFunctions[FoundMatch].bRequiresExecPin) + { + OutSignature.Inputs.Insert(FNiagaraVariable(FNiagaraTypeDefinition::GetParameterMapDef(), TEXT("InExecPin")), 0); + OutSignature.Outputs.Insert(FNiagaraVariable(FNiagaraTypeDefinition::GetParameterMapDef(), TEXT("OutExecPin")), 0); + } if (Info.UserPtrIdx != INDEX_NONE && CompilationTarget != ENiagaraSimTarget::GPUComputeSim) { //This interface requires per instance data via a user ptr so place the index as the first input. @@ -6610,6 +6730,11 @@ FString FHlslNiagaraTranslator::GetFunctionSignatureSymbol(const FNiagaraFunctio { SigStr += TEXT("_Func_"); } + if (Sig.bRequiresExecPin) + { + SigStr += TEXT("_UEImpureCall_"); // Let the cross compiler know that we intend to keep this. + } + for (const TTuple& Specifier : Sig.FunctionSpecifiers) { SigStr += TEXT("_") + Specifier.Key.ToString() + Specifier.Value.ToString().Replace(TEXT("."), TEXT("")); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.h index 216508b88f2a..84e63ade8bd5 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.h @@ -6,9 +6,6 @@ #include "Kismet2/CompilerResultsLog.h" #include "NiagaraScript.h" #include "NiagaraParameterMapHistory.h" -#include "EdGraphUtilities.h" -#include "UObject/UObjectHash.h" -#include "ComponentReregisterContext.h" #include "NiagaraShaderCompilationManager.h" #include "NiagaraDataInterface.h" #include "TickableEditorObject.h" @@ -114,6 +111,7 @@ public: TArray NumIterationsPerStage; TArray IterationSourcePerStage; TArray SpawnOnlyPerStage; + TArray PartialParticleUpdatePerStage; TArray StageGuids; TArray StageNames; @@ -303,23 +301,30 @@ public: class NIAGARAEDITOR_API FHlslNiagaraTranslationStage { public: - FHlslNiagaraTranslationStage(ENiagaraScriptUsage InScriptUsage, FGuid InUsageId) : ScriptUsage(InScriptUsage), UsageId(InUsageId), OutputNode(nullptr), bInterpolatePreviousParams(false), bCopyPreviousParams(true), ChunkModeIndex((ENiagaraCodeChunkMode)-1), IterationSource(), bSpawnOnly(false), bUsesAlive(false){} - + FHlslNiagaraTranslationStage(ENiagaraScriptUsage InScriptUsage, FGuid InUsageId) + : ScriptUsage(InScriptUsage) + , UsageId(InUsageId) + { + } + ENiagaraScriptUsage ScriptUsage; FGuid UsageId; - UNiagaraNodeOutput* OutputNode; - FString PassNamespace; - bool bInterpolatePreviousParams; - bool bCopyPreviousParams; - ENiagaraCodeChunkMode ChunkModeIndex; + UNiagaraNodeOutput* OutputNode = nullptr; + FString PassNamespace ; + bool bInterpolatePreviousParams = false; + bool bCopyPreviousParams = true; + ENiagaraCodeChunkMode ChunkModeIndex = (ENiagaraCodeChunkMode)-1; FName IterationSource; int32 SimulationStageIndexMin = -1; int32 SimulationStageIndexMax = -1; int32 NumIterationsThisStage = 1; int32 SourceSimStage = -1; - bool bSpawnOnly; - bool bUsesAlive; + bool bSpawnOnly = false; + bool bUsesAlive = false; bool bWritesAlive = false; + bool bWritesParticles = false; + bool bPartialParticleUpdate = false; + TArray SetParticleAttributes; }; class NIAGARAEDITOR_API FHlslNiagaraTranslator diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNode.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNode.cpp index 9c864fcbe38f..bf80bc53ef7f 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNode.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNode.cpp @@ -334,10 +334,14 @@ void UNiagaraNode::AutowireNewNode(UEdGraphPin* FromPin) for (UEdGraphPin* Pin : Pins) { //ENiagaraCompoundType ToType = Schema->GetPinDataType(Pin); - if (/*FromType == ToType &&*/ Pin->Direction == DesiredDirection) + if (Pin->Direction == DesiredDirection) { - FirstPinOfSameType = Pin; - break; + const FPinConnectionResponse Response = Schema->CanCreateConnection(FromPin, Pin); + if (Response.Response != ECanCreateConnectionResponse::CONNECT_RESPONSE_DISALLOW) + { + FirstPinOfSameType = Pin; + break; + } } } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeAssignment.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeAssignment.cpp index 354b25dfb339..cc701f76feee 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeAssignment.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeAssignment.cpp @@ -344,9 +344,11 @@ void UNiagaraNodeAssignment::CollectCreateNewActions(ENiagaraScriptUsage InUsage const FText TypeText = AvailableType.GetNameText(); const FText TooltipDesc = FText::Format(LOCTEXT("NewParameterModuleDescriptionFormat", "Description: Create a new {0} parameter. {1}"), TypeText, VarDesc); FText Category = LOCTEXT("NewParameterModuleCategory", "Create New Parameter"); + FText SubCategory = FNiagaraEditorUtilities::GetVariableTypeCategory(NewParameter); + FText FullCategory = SubCategory.IsEmpty() ? Category : FText::Format(FText::FromString("{0}|{1}"), Category, SubCategory); TSharedRef CreateNewAction = MakeShareable(new FNiagaraMenuAction( - Category, TypeText, TooltipDesc, + FullCategory, TypeText, TooltipDesc, 0, FText(), FNiagaraMenuAction::FOnExecuteStackAction::CreateUObject(this, &UNiagaraNodeAssignment::AddParameter, NewParameter, VarDefaultValue))); OutCreateNewActions.Add(CreateNewAction); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeDataSetBase.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeDataSetBase.cpp index f3af9acb348c..d131caee7eef 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeDataSetBase.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeDataSetBase.cpp @@ -5,6 +5,7 @@ #include "INiagaraCompiler.h" #include "NiagaraEvents.h" #include "EdGraphSchema_Niagara.h" +#include "NiagaraCustomVersion.h" #define LOCTEXT_NAMESPACE "UNiagaraNodeDataSetBase" diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeFunctionCall.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeFunctionCall.cpp index 8ff721820dcd..e563ea295aaa 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeFunctionCall.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeFunctionCall.cpp @@ -265,6 +265,11 @@ void UNiagaraNodeFunctionCall::AllocateDefaultPins() } else { + if (Signature.bRequiresExecPin) + { + UEdGraphPin* NewPin = CreatePin(EGPD_Input, Schema->TypeDefinitionToPinType(FNiagaraTypeDefinition::GetParameterMapDef()), TEXT("")); + NewPin->bDefaultValueIsIgnored = true; + } for (FNiagaraVariable& Input : Signature.Inputs) { @@ -272,6 +277,12 @@ void UNiagaraNodeFunctionCall::AllocateDefaultPins() NewPin->bDefaultValueIsIgnored = false; } + if (Signature.bRequiresExecPin) + { + UEdGraphPin* NewPin = CreatePin(EGPD_Output, Schema->TypeDefinitionToPinType(FNiagaraTypeDefinition::GetParameterMapDef()), TEXT("")); + NewPin->bDefaultValueIsIgnored = true; + } + for (FNiagaraVariable& Output : Signature.Outputs) { UEdGraphPin* NewPin = CreatePin(EGPD_Output, Schema->TypeDefinitionToPinType(Output.GetType()), Output.GetName()); @@ -880,7 +891,7 @@ void UNiagaraNodeFunctionCall::BuildParameterMapHistory(FNiagaraParameterMapHist OutHistory.RegisterParameterMapPin(MatchedPairs[i].Value, MatchedPairs[i].Key); } } - else if (!ScriptIsValid()) + else if (!ScriptIsValid() || Signature.bRequiresExecPin) { RouteParameterMapAroundMe(OutHistory, bRecursive); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeOp.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeOp.cpp index 05ed71531eb7..d8b90b7b7c5e 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeOp.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeOp.cpp @@ -5,6 +5,7 @@ #include "GraphEditorSettings.h" #include "EdGraphSchema_Niagara.h" +#include "NiagaraCustomVersion.h" #define LOCTEXT_NAMESPACE "NiagaraNodeOp" diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapBase.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapBase.cpp index 7605a3bf1f12..36ae1d20a9bb 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapBase.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapBase.cpp @@ -207,11 +207,6 @@ void UNiagaraNodeParameterMapBase::SetPinName(UEdGraphPin* InPin, const FName& I OnPinRenamed(InPin, OldName.ToString()); } -bool UNiagaraNodeParameterMapBase::OnAllowDrop(TSharedPtr DragDropOperation) -{ - return true; -} - bool UNiagaraNodeParameterMapBase::CanRenamePin(const UEdGraphPin* Pin) const { if (IsAddPin(Pin)) @@ -240,6 +235,52 @@ void UNiagaraNodeParameterMapBase::SetIsPinEditNamespaceModifierPending(const UE } } +bool UNiagaraNodeParameterMapBase::CanHandleDropOperation(TSharedPtr DragDropOperation) +{ + if (DragDropOperation->IsOfType()) + { + if (StaticCastSharedPtr(DragDropOperation)->IsCurrentlyHoveringNode(this)) + { + // The FNiagaraParameterGraphDragOperation handles the drop action itself so just return true here and let it handle it when it's executed. + return true; + } + } + else if (DragDropOperation->IsOfType()) + { + TSharedPtr ParameterDragDropOperation = StaticCastSharedPtr(DragDropOperation); + TSharedPtr ParameterAction = StaticCastSharedPtr(ParameterDragDropOperation->GetSourceAction()); + EEdGraphPinDirection NewPinDirection = GetPinDirectionForNewParameters(); + if (ParameterAction.IsValid() && NewPinDirection != EGPD_MAX) + { + FNiagaraVariable Parameter = ParameterAction->GetParameter(); + FNiagaraParameterHandle ParameterHandle(Parameter.GetName()); + FNiagaraNamespaceMetadata ParameterNamespaceMetadata = GetDefault()->GetMetaDataForNamespaces(ParameterHandle.GetHandleParts()); + return ParameterNamespaceMetadata.IsValid() && ParameterNamespaceMetadata.Options.Contains(ENiagaraNamespaceMetadataOptions::HideInScript) == false; + } + } + return false; +} + +bool UNiagaraNodeParameterMapBase::HandleDropOperation(TSharedPtr DropOperation) +{ + if (CanHandleDropOperation(DropOperation) && DropOperation->IsOfType()) + { + TSharedPtr ParameterDropOperation = StaticCastSharedPtr(DropOperation); + TSharedPtr ParameterAction = StaticCastSharedPtr(ParameterDropOperation->GetSourceAction()); + FNiagaraVariable Parameter = ParameterAction->GetParameter(); + EEdGraphPinDirection NewPinDirection = GetPinDirectionForNewParameters(); + + FScopedTransaction AddNewPinTransaction(LOCTEXT("DropParameterOntoParameterMapNode", "Drop parameter onto parameter map node.")); + UEdGraphPin* AddedPin = RequestNewTypedPin(NewPinDirection, Parameter.GetType(), Parameter.GetName()); + if (AddedPin != nullptr) + { + CancelEditablePinName(FText::GetEmpty(), AddedPin); + } + return true; + } + return false; +} + void UNiagaraNodeParameterMapBase::OnPinRenamed(UEdGraphPin* RenamedPin, const FString& OldName) { RenamedPin->PinFriendlyName = FText::FromName(RenamedPin->PinName); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapBase.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapBase.h index 8e5b757a61b1..4276bb0912de 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapBase.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapBase.h @@ -41,8 +41,6 @@ public: void SetPinName(UEdGraphPin* InPin, const FName& InName); - bool OnAllowDrop(TSharedPtr DragDropOperation); - virtual bool CanRenamePinFromContextMenu(const UEdGraphPin* Pin) const override { return false; } virtual bool CanRenamePin(const UEdGraphPin* Pin) const override; @@ -53,6 +51,10 @@ public: void SetIsPinEditNamespaceModifierPending(const UEdGraphPin* Pin, bool bInIsEditNamespaceModifierPending); + bool CanHandleDropOperation(TSharedPtr DragDropOperation); + + bool HandleDropOperation(TSharedPtr DropOperation); + protected: void GetChangeNamespaceSubMenuForPin(UToolMenu* Menu, UEdGraphPin* InPin); void ChangeNamespaceForPin(UEdGraphPin* InPin, FNiagaraNamespaceMetadata NewNamespaceMetadata); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeWithDynamicPins.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeWithDynamicPins.cpp index 23355651b2e3..bbddf8690368 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeWithDynamicPins.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeWithDynamicPins.cpp @@ -277,7 +277,7 @@ void UNiagaraNodeWithDynamicPins::GetNodeContextMenuActions(UToolMenu* Menu, UGr void UNiagaraNodeWithDynamicPins::CollectAddPinActions(FGraphActionListBuilderBase& OutActions, bool& bOutCreateRemainingActions, UEdGraphPin* Pin) { - TArray Types = FNiagaraTypeRegistry::GetRegisteredTypes(); + TArray Types(FNiagaraTypeRegistry::GetRegisteredTypes()); Types.Sort([](const FNiagaraTypeDefinition& A, const FNiagaraTypeDefinition& B) { return (A.GetNameText().ToLower().ToString() < B.GetNameText().ToLower().ToString()); }); for (const FNiagaraTypeDefinition& RegisteredType : Types) @@ -290,10 +290,11 @@ void UNiagaraNodeWithDynamicPins::CollectAddPinActions(FGraphActionListBuilderBa FNiagaraVariable Var(RegisteredType, FName(*RegisteredType.GetName())); FNiagaraEditorUtilities::ResetVariableToDefaultValue(Var); + FText Category = FNiagaraEditorUtilities::GetVariableTypeCategory(Var); const FText DisplayName = RegisteredType.GetNameText(); const FText Tooltip = FText::Format(LOCTEXT("AddButtonTypeEntryToolTipFormat", "Add a new {0} pin"), RegisteredType.GetNameText()); TSharedPtr Action(new FNiagaraMenuAction( - FText::GetEmpty(), DisplayName, Tooltip, 0, FText::GetEmpty(), + Category, DisplayName, Tooltip, 0, FText::GetEmpty(), FNiagaraMenuAction::FOnExecuteStackAction::CreateUObject(this, &UNiagaraNodeWithDynamicPins::AddParameter, Var, (const UEdGraphPin*)Pin))); OutActions.AddAction(Action); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraParameterMapHistory.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraParameterMapHistory.cpp index 4aa45e5e62a3..72c1a3d05936 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraParameterMapHistory.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraParameterMapHistory.cpp @@ -1505,6 +1505,13 @@ bool FCompileConstantResolver::ResolveConstant(FNiagaraVariable& OutConstant) co OutConstant.SetValue(EnumValue); return true; } + if (Emitter && OutConstant == FNiagaraVariable(FNiagaraTypeDefinition::GetScriptContextEnum(), TEXT("Script.Context"))) + { + FNiagaraInt32 EnumValue; + EnumValue.Value = (uint8)ENiagaraScriptContextStaticSwitch::Particle; + OutConstant.SetValue(EnumValue); + return true; + } return false; } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.cpp index 6ee863a0499b..451b416dd4b9 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.cpp @@ -36,13 +36,12 @@ DECLARE_CYCLE_STAT(TEXT("Niagara - ScriptMergeManager - MergeEmitter"), STAT_Nia DECLARE_CYCLE_STAT(TEXT("Niagara - ScriptMergeManager - IsModuleInputDifferentFromBase"), STAT_NiagaraEditor_ScriptMergeManager_IsModuleInputDifferentFromBase, STATGROUP_NiagaraEditor); FNiagaraStackFunctionInputOverrideMergeAdapter::FNiagaraStackFunctionInputOverrideMergeAdapter( - FString InUniqueEmitterName, + const UNiagaraEmitter& InOwningEmitter, UNiagaraScript& InOwningScript, UNiagaraNodeFunctionCall& InOwningFunctionCallNode, UEdGraphPin& InOverridePin ) - : UniqueEmitterName(InUniqueEmitterName) - , OwningScript(&InOwningScript) + : OwningScript(&InOwningScript) , OwningFunctionCallNode(&InOwningFunctionCallNode) , OverridePin(&InOverridePin) , DataValueObject(nullptr) @@ -73,7 +72,7 @@ FNiagaraStackFunctionInputOverrideMergeAdapter::FNiagaraStackFunctionInputOverri } else if (OverridePin->LinkedTo[0]->GetOwningNode()->IsA()) { - DynamicValueFunction = MakeShared(UniqueEmitterName, *OwningScript.Get(), *CastChecked(OverridePin->LinkedTo[0]->GetOwningNode()), INDEX_NONE); + DynamicValueFunction = MakeShared(InOwningEmitter, *OwningScript.Get(), *CastChecked(OverridePin->LinkedTo[0]->GetOwningNode()), INDEX_NONE); } else { @@ -87,14 +86,12 @@ FNiagaraStackFunctionInputOverrideMergeAdapter::FNiagaraStackFunctionInputOverri } FNiagaraStackFunctionInputOverrideMergeAdapter::FNiagaraStackFunctionInputOverrideMergeAdapter( - FString InUniqueEmitterName, UNiagaraScript& InOwningScript, UNiagaraNodeFunctionCall& InOwningFunctionCallNode, FString InInputName, FNiagaraVariable InRapidIterationParameter ) - : UniqueEmitterName(InUniqueEmitterName) - , OwningScript(&InOwningScript) + : OwningScript(&InOwningScript) , OwningFunctionCallNode(&InOwningFunctionCallNode) , InputName(InInputName) , Type(InRapidIterationParameter.GetType()) @@ -186,13 +183,30 @@ TOptional FNiagaraStackFunctionInputOverrideMergeAdapter::GetStaticSwit return StaticSwitchValue; } -FNiagaraStackFunctionMergeAdapter::FNiagaraStackFunctionMergeAdapter(FString InEmitterUniqueName, UNiagaraScript& InOwningScript, UNiagaraNodeFunctionCall& InFunctionCallNode, int32 InStackIndex) +FNiagaraStackFunctionMergeAdapter::FNiagaraStackFunctionMergeAdapter(const UNiagaraEmitter& InOwningEmitter, UNiagaraScript& InOwningScript, UNiagaraNodeFunctionCall& InFunctionCallNode, int32 InStackIndex) { - UniqueEmitterName = InEmitterUniqueName; OwningScript = &InOwningScript; FunctionCallNode = &InFunctionCallNode; StackIndex = InStackIndex; + int32 EmitterScratchPadScriptIndex = InOwningEmitter.ScratchPadScripts.IndexOfByKey(FunctionCallNode->FunctionScript); + int32 ParentEmitterScratchPadScriptIndex = InOwningEmitter.ParentScratchPadScripts.IndexOfByKey(FunctionCallNode->FunctionScript); + if (EmitterScratchPadScriptIndex != INDEX_NONE) + { + ScratchPadScriptIndex = InOwningEmitter.ParentScratchPadScripts.Num() + EmitterScratchPadScriptIndex; + } + else if (ParentEmitterScratchPadScriptIndex != INDEX_NONE) + { + ScratchPadScriptIndex = ParentEmitterScratchPadScriptIndex; + } + else + { + ScratchPadScriptIndex = INDEX_NONE; + } + + FString UniqueEmitterName = InOwningEmitter.GetUniqueEmitterName(); + + // Collect explicit overrides set via parameter map set nodes. TSet AliasedInputsAdded; UNiagaraNodeParameterMapSet* OverrideNode = FNiagaraStackGraphUtilities::GetStackFunctionOverrideNode(*FunctionCallNode); if (OverrideNode != nullptr) @@ -207,13 +221,60 @@ FNiagaraStackFunctionMergeAdapter::FNiagaraStackFunctionMergeAdapter(FString InE FNiagaraParameterHandle InputHandle(OverridePin->PinName); if (InputHandle.GetNamespace().ToString() == FunctionCallNode->GetFunctionName()) { - InputOverrides.Add(MakeShared(UniqueEmitterName, *OwningScript.Get(), *FunctionCallNode.Get(), *OverridePin)); + InputOverrides.Add(MakeShared(InOwningEmitter, *OwningScript.Get(), *FunctionCallNode.Get(), *OverridePin)); AliasedInputsAdded.Add(OverridePin->PinName.ToString()); } } } } + // If we have a valid function script collect up the default values of the rapid iteration parameters so that default values in the parameter store + // can be ignored since they're not actually overrides. This is usually not an issue due to the PreparateRapidIterationParameters call in the emitter + // editor, but modifications to modules can cause inconsistency here in the emitter. + TArray RapidIterationInputDefaultValues; + const UEdGraphSchema_Niagara* NiagaraSchema = GetDefault(); + if (InFunctionCallNode.FunctionScript != nullptr) + { + TSet HiddenPins; + FCompileConstantResolver Resolver(&InOwningEmitter); + TArray FunctionInputPins; + FNiagaraStackGraphUtilities::GetStackFunctionInputPins(*FunctionCallNode, FunctionInputPins, HiddenPins, Resolver, FNiagaraStackGraphUtilities::ENiagaraGetStackFunctionInputPinsOptions::ModuleInputsOnly, false); + + for (const UEdGraphPin* FunctionInputPin : FunctionInputPins) + { + FNiagaraVariable FunctionInputVariable = NiagaraSchema->PinToNiagaraVariable(FunctionInputPin); + if (FunctionInputVariable.IsValid() && FNiagaraStackGraphUtilities::IsRapidIterationType(FunctionInputVariable.GetType())) + { + UEdGraphPin* FunctionInputDefaultPin = FunctionCallNode->FindParameterMapDefaultValuePin(FunctionInputPin->PinName, OwningScript->GetUsage()); + if (FunctionInputDefaultPin != nullptr) + { + // Try to get the default value from the default pin. + FNiagaraVariable FunctionInputDefaultVariable = NiagaraSchema->PinToNiagaraVariable(FunctionInputDefaultPin); + if (FunctionInputDefaultVariable.GetData() != nullptr) + { + FunctionInputVariable.SetData(FunctionInputDefaultVariable.GetData()); + } + } + + if (FunctionInputVariable.GetData() == nullptr) + { + // If the pin didn't have a default value then use the type default. + FNiagaraEditorUtilities::ResetVariableToDefaultValue(FunctionInputVariable); + } + + if (FunctionInputVariable.GetData() != nullptr) + { + FNiagaraParameterHandle AliasedFunctionInputHandle = FNiagaraParameterHandle::CreateAliasedModuleParameterHandle(FNiagaraParameterHandle(FunctionInputVariable.GetName()), &InFunctionCallNode); + FNiagaraVariable FunctionInputRapidIterationParameter = + FNiagaraStackGraphUtilities::CreateRapidIterationParameter(UniqueEmitterName, OwningScript->GetUsage(), AliasedFunctionInputHandle.GetParameterHandleString(), FunctionInputVariable.GetType()); + FunctionInputRapidIterationParameter.SetData(FunctionInputVariable.GetData()); + RapidIterationInputDefaultValues.Add(FunctionInputRapidIterationParameter); + } + } + } + } + + // Collect rapid iteration parameters which aren't at the function default values. FString RapidIterationParameterNamePrefix = TEXT("Constants." + UniqueEmitterName + "."); TArray RapidIterationParameters; OwningScript->RapidIterationParameters.GetParameters(RapidIterationParameters); @@ -237,7 +298,23 @@ FNiagaraStackFunctionMergeAdapter::FNiagaraStackFunctionMergeAdapter(FString InE if (AliasedInputsAdded.Contains(AliasedInputHandle.GetParameterHandleString().ToString()) == false) { - InputOverrides.Add(MakeShared(UniqueEmitterName, *OwningScript.Get(), *FunctionCallNode.Get(), AliasedInputHandle.GetName().ToString(), RapidIterationParameter)); + // Check if the input is at the current default and if so it can be skipped. + bool bMatchesDefault = false; + FNiagaraVariable* RapidIterationInputDefaultValue = RapidIterationInputDefaultValues.FindByPredicate([RapidIterationParameter](const FNiagaraVariable& DefaultValue) + { return DefaultValue.GetName() == RapidIterationParameter.GetName() && DefaultValue.GetType() == RapidIterationParameter.GetType(); }); + if (RapidIterationInputDefaultValue != nullptr) + { + const uint8* CurrentValueData = OwningScript->RapidIterationParameters.GetParameterData(RapidIterationParameter); + if (CurrentValueData != nullptr) + { + bMatchesDefault = FMemory::Memcmp(CurrentValueData, RapidIterationInputDefaultValue->GetData(), RapidIterationParameter.GetSizeInBytes()) == 0; + } + } + + if (bMatchesDefault == false) + { + InputOverrides.Add(MakeShared(*OwningScript.Get(), *FunctionCallNode.Get(), AliasedInputHandle.GetName().ToString(), RapidIterationParameter)); + } } } } @@ -263,6 +340,11 @@ int32 FNiagaraStackFunctionMergeAdapter::GetStackIndex() const return StackIndex; } +int32 FNiagaraStackFunctionMergeAdapter::GetScratchPadScriptIndex() const +{ + return ScratchPadScriptIndex; +} + const TArray>& FNiagaraStackFunctionMergeAdapter::GetInputOverrides() const { return InputOverrides; @@ -280,12 +362,12 @@ TSharedPtr FNiagaraStackFunction return TSharedPtr(); } -FNiagaraScriptStackMergeAdapter::FNiagaraScriptStackMergeAdapter(UNiagaraNodeOutput& InOutputNode, UNiagaraScript& InScript, FString InUniqueEmitterName) +FNiagaraScriptStackMergeAdapter::FNiagaraScriptStackMergeAdapter(const UNiagaraEmitter& InOwningEmitter, UNiagaraNodeOutput& InOutputNode, UNiagaraScript& InScript) { OutputNode = &InOutputNode; InputNode.Reset(); Script = &InScript; - UniqueEmitterName = InUniqueEmitterName; + UniqueEmitterName = InOwningEmitter.GetUniqueEmitterName(); TArray StackGroups; FNiagaraStackGraphUtilities::GetStackNodeGroups(*OutputNode, StackGroups); @@ -304,7 +386,7 @@ FNiagaraScriptStackMergeAdapter::FNiagaraScriptStackMergeAdapter(UNiagaraNodeOut { // The first stack node group is the input node, so we subtract one to get the index of the module. int32 StackIndex = i - 1; - ModuleFunctions.Add(MakeShared(UniqueEmitterName, *Script.Get(), *ModuleFunctionCallNode, StackIndex)); + ModuleFunctions.Add(MakeShared(InOwningEmitter, *Script.Get(), *ModuleFunctionCallNode, StackIndex)); } } } @@ -373,7 +455,7 @@ void FNiagaraEventHandlerMergeAdapter::Initialize(const UNiagaraEmitter& InEmitt if (EventScriptProperties != nullptr && OutputNode != nullptr) { - EventStack = MakeShared(*OutputNode.Get(), *EventScriptProperties->Script, Emitter->GetUniqueEmitterName()); + EventStack = MakeShared(*Emitter.Get(), *OutputNode.Get(), *EventScriptProperties->Script); InputNode = EventStack->GetInputNode(); } } @@ -446,7 +528,7 @@ void FNiagaraSimulationStageMergeAdapter::Initialize(const UNiagaraEmitter& InEm if (SimulationStage != nullptr && OutputNode != nullptr) { - SimulationStageStack = MakeShared(*OutputNode.Get(), *SimulationStage->Script, Emitter->GetUniqueEmitterName()); + SimulationStageStack = MakeShared(*Emitter.Get(), *OutputNode.Get(), *SimulationStage->Script); InputNode = SimulationStageStack->GetInputNode(); } } @@ -528,19 +610,19 @@ void FNiagaraEmitterMergeAdapter::Initialize(const UNiagaraEmitter& InEmitter, U { if (UNiagaraScript::IsEquivalentUsage(OutputNode->GetUsage(), ENiagaraScriptUsage::EmitterSpawnScript)) { - EmitterSpawnStack = MakeShared(*OutputNode, *Emitter->EmitterSpawnScriptProps.Script, Emitter->GetUniqueEmitterName()); + EmitterSpawnStack = MakeShared(*Emitter.Get(), *OutputNode, *Emitter->EmitterSpawnScriptProps.Script); } else if (UNiagaraScript::IsEquivalentUsage(OutputNode->GetUsage(), ENiagaraScriptUsage::EmitterUpdateScript)) { - EmitterUpdateStack = MakeShared(*OutputNode, *Emitter->EmitterUpdateScriptProps.Script, Emitter->GetUniqueEmitterName()); + EmitterUpdateStack = MakeShared(*Emitter.Get(), *OutputNode, *Emitter->EmitterUpdateScriptProps.Script); } else if (UNiagaraScript::IsEquivalentUsage(OutputNode->GetUsage(), ENiagaraScriptUsage::ParticleSpawnScript)) { - ParticleSpawnStack = MakeShared(*OutputNode, *Emitter->SpawnScriptProps.Script, Emitter->GetUniqueEmitterName()); + ParticleSpawnStack = MakeShared(*Emitter.Get(), *OutputNode, *Emitter->SpawnScriptProps.Script); } else if (UNiagaraScript::IsEquivalentUsage(OutputNode->GetUsage(), ENiagaraScriptUsage::ParticleUpdateScript)) { - ParticleUpdateStack = MakeShared(*OutputNode, *Emitter->UpdateScriptProps.Script, Emitter->GetUniqueEmitterName()); + ParticleUpdateStack = MakeShared(*Emitter.Get(), *OutputNode, *Emitter->UpdateScriptProps.Script); } else if(UNiagaraScript::IsEquivalentUsage(OutputNode->GetUsage(), ENiagaraScriptUsage::ParticleEventScript)) { @@ -687,6 +769,14 @@ TSharedPtr FNiagaraEmitterMergeAdapter::GetScri } } break; + case ENiagaraScriptUsage::ParticleSimulationStageScript: + for (TSharedPtr SimulationStage : SimulationStages) + { + if (SimulationStage->GetUsageId() == ScriptUsageId) + { + return SimulationStage->GetSimulationStageStack(); + } + } default: checkf(false, TEXT("Unsupported usage")); } @@ -1046,9 +1136,13 @@ INiagaraMergeManager::FMergeEmitterResults FNiagaraScriptMergeManager::MergeEmit FNiagaraEditorUtilities::GatherChangeIds(Instance, LastChangeIds, TEXT("Instance")); DiffChangeIds(SourceChangeIds, PreviousSourceChangeIds, LastChangeIds, ChangeIdsThatNeedToBeReset); - MergeResults.MergeResult = INiagaraMergeManager::EMergeEmitterResult::SucceededDifferencesApplied; + MergedInstance->ParentScratchPadScripts.Append(MergedInstance->ScratchPadScripts); + MergedInstance->ScratchPadScripts.Empty(); + TMap SourceToMergedScratchPadScriptMap; + CopyInstanceScratchPadScripts(*MergedInstance, Instance, SourceToMergedScratchPadScriptMap); - FApplyDiffResults EmitterSpawnResults = ApplyScriptStackDiff(MergedInstanceAdapter->GetEmitterSpawnStack().ToSharedRef(), DiffResults.EmitterSpawnDiffResults, bNoParentAtLastMerge); + MergeResults.MergeResult = INiagaraMergeManager::EMergeEmitterResult::SucceededDifferencesApplied; + FApplyDiffResults EmitterSpawnResults = ApplyScriptStackDiff(MergedInstanceAdapter->GetEmitterSpawnStack().ToSharedRef(), SourceToMergedScratchPadScriptMap, DiffResults.EmitterSpawnDiffResults, bNoParentAtLastMerge); if (EmitterSpawnResults.bSucceeded == false) { MergeResults.MergeResult = INiagaraMergeManager::EMergeEmitterResult::FailedToMerge; @@ -1056,7 +1150,7 @@ INiagaraMergeManager::FMergeEmitterResults FNiagaraScriptMergeManager::MergeEmit MergeResults.bModifiedGraph |= EmitterSpawnResults.bModifiedGraph; MergeResults.ErrorMessages.Append(EmitterSpawnResults.ErrorMessages); - FApplyDiffResults EmitterUpdateResults = ApplyScriptStackDiff(MergedInstanceAdapter->GetEmitterUpdateStack().ToSharedRef(), DiffResults.EmitterUpdateDiffResults, bNoParentAtLastMerge); + FApplyDiffResults EmitterUpdateResults = ApplyScriptStackDiff(MergedInstanceAdapter->GetEmitterUpdateStack().ToSharedRef(), SourceToMergedScratchPadScriptMap, DiffResults.EmitterUpdateDiffResults, bNoParentAtLastMerge); if (EmitterUpdateResults.bSucceeded == false) { MergeResults.MergeResult = INiagaraMergeManager::EMergeEmitterResult::FailedToMerge; @@ -1064,7 +1158,7 @@ INiagaraMergeManager::FMergeEmitterResults FNiagaraScriptMergeManager::MergeEmit MergeResults.bModifiedGraph |= EmitterUpdateResults.bModifiedGraph; MergeResults.ErrorMessages.Append(EmitterUpdateResults.ErrorMessages); - FApplyDiffResults ParticleSpawnResults = ApplyScriptStackDiff(MergedInstanceAdapter->GetParticleSpawnStack().ToSharedRef(), DiffResults.ParticleSpawnDiffResults, bNoParentAtLastMerge); + FApplyDiffResults ParticleSpawnResults = ApplyScriptStackDiff(MergedInstanceAdapter->GetParticleSpawnStack().ToSharedRef(), SourceToMergedScratchPadScriptMap, DiffResults.ParticleSpawnDiffResults, bNoParentAtLastMerge); if (ParticleSpawnResults.bSucceeded == false) { MergeResults.MergeResult = INiagaraMergeManager::EMergeEmitterResult::FailedToMerge; @@ -1072,7 +1166,7 @@ INiagaraMergeManager::FMergeEmitterResults FNiagaraScriptMergeManager::MergeEmit MergeResults.bModifiedGraph |= ParticleSpawnResults.bModifiedGraph; MergeResults.ErrorMessages.Append(ParticleSpawnResults.ErrorMessages); - FApplyDiffResults ParticleUpdateResults = ApplyScriptStackDiff(MergedInstanceAdapter->GetParticleUpdateStack().ToSharedRef(), DiffResults.ParticleUpdateDiffResults, bNoParentAtLastMerge); + FApplyDiffResults ParticleUpdateResults = ApplyScriptStackDiff(MergedInstanceAdapter->GetParticleUpdateStack().ToSharedRef(), SourceToMergedScratchPadScriptMap, DiffResults.ParticleUpdateDiffResults, bNoParentAtLastMerge); if (ParticleUpdateResults.bSucceeded == false) { MergeResults.MergeResult = INiagaraMergeManager::EMergeEmitterResult::FailedToMerge; @@ -1080,7 +1174,7 @@ INiagaraMergeManager::FMergeEmitterResults FNiagaraScriptMergeManager::MergeEmit MergeResults.bModifiedGraph |= ParticleUpdateResults.bModifiedGraph; MergeResults.ErrorMessages.Append(ParticleUpdateResults.ErrorMessages); - FApplyDiffResults EventHandlerResults = ApplyEventHandlerDiff(MergedInstanceAdapter, DiffResults, bNoParentAtLastMerge); + FApplyDiffResults EventHandlerResults = ApplyEventHandlerDiff(MergedInstanceAdapter, SourceToMergedScratchPadScriptMap, DiffResults, bNoParentAtLastMerge); if (EventHandlerResults.bSucceeded == false) { MergeResults.MergeResult = INiagaraMergeManager::EMergeEmitterResult::FailedToMerge; @@ -1088,7 +1182,7 @@ INiagaraMergeManager::FMergeEmitterResults FNiagaraScriptMergeManager::MergeEmit MergeResults.bModifiedGraph |= EventHandlerResults.bModifiedGraph; MergeResults.ErrorMessages.Append(EventHandlerResults.ErrorMessages); - FApplyDiffResults SimulationStageResults = ApplySimulationStageDiff(MergedInstanceAdapter, DiffResults, bNoParentAtLastMerge); + FApplyDiffResults SimulationStageResults = ApplySimulationStageDiff(MergedInstanceAdapter, SourceToMergedScratchPadScriptMap, DiffResults, bNoParentAtLastMerge); if (SimulationStageResults.bSucceeded == false) { MergeResults.MergeResult = INiagaraMergeManager::EMergeEmitterResult::FailedToMerge; @@ -1188,12 +1282,6 @@ INiagaraMergeManager::FMergeEmitterResults FNiagaraScriptMergeManager::MergeEmit FNiagaraEditorUtilities::GatherChangeIds(*MergedInstance, FinalChangeIds, TEXT("Final")); } - if(MergeResults.MergedInstance != nullptr) - { - MergeResults.MergedInstance->ParentScratchPadScripts.Append(MergeResults.MergedInstance->ScratchPadScripts); - MergeResults.MergedInstance->ScratchPadScripts.Empty(); - } - return MergeResults; } @@ -1203,7 +1291,8 @@ bool FNiagaraScriptMergeManager::IsMergeableScriptUsage(ENiagaraScriptUsage Scri ScriptUsage == ENiagaraScriptUsage::EmitterUpdateScript || ScriptUsage == ENiagaraScriptUsage::ParticleSpawnScript || ScriptUsage == ENiagaraScriptUsage::ParticleUpdateScript || - ScriptUsage == ENiagaraScriptUsage::ParticleEventScript; + ScriptUsage == ENiagaraScriptUsage::ParticleEventScript || + ScriptUsage == ENiagaraScriptUsage::ParticleSimulationStageScript; } bool FNiagaraScriptMergeManager::HasBaseModule(const UNiagaraEmitter& BaseEmitter, ENiagaraScriptUsage ScriptUsage, FGuid ScriptUsageId, FGuid ModuleId) @@ -1280,6 +1369,15 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ResetM FText::FromString(Emitter.GetPathName()), FText::FromString(InputName))); return Results; } + + if (Emitter.ParentScratchPadScripts.Num() != BaseEmitter.ParentScratchPadScripts.Num() + BaseEmitter.ScratchPadScripts.Num()) + { + FApplyDiffResults Results; + Results.bSucceeded = false; + Results.bModifiedGraph = false; + Results.ErrorMessages.Add(FText::Format(LOCTEXT("ResetFailedBecauseOfScratchPadScripts", "Failed to reset input back to it's base value. Its scratch pad scripts were out of sync. Emitter: {0} Input:{1}"), + FText::FromString(Emitter.GetPathName()), FText::FromString(InputName))); + } // Remove items from the diff which are not relevant to this input. ResetDiffResults.RemovedBaseModules.Empty(); @@ -1295,7 +1393,19 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ResetM ResetDiffResults.ModifiedBaseInputOverrides.RemoveAll(FindUnrelatedInputOverrides); ResetDiffResults.ModifiedOtherInputOverrides.RemoveAll(FindUnrelatedInputOverrides); - return ApplyScriptStackDiff(EmitterAdapter->GetScriptStack(ScriptUsage, ScriptUsageId).ToSharedRef(), ResetDiffResults, false); + TMap ScratchScriptMap; + for (int32 BaseParentScratchPadScriptIndex = 0; BaseParentScratchPadScriptIndex < BaseEmitter.ParentScratchPadScripts.Num(); BaseParentScratchPadScriptIndex++) + { + ScratchScriptMap.Add(BaseEmitter.ParentScratchPadScripts[BaseParentScratchPadScriptIndex], Emitter.ParentScratchPadScripts[BaseParentScratchPadScriptIndex]); + } + + int32 BaseParentScratchPadScriptCount = BaseEmitter.ParentScratchPadScripts.Num(); + for (int32 BaseScratchPadScriptIndex = 0; BaseScratchPadScriptIndex < BaseEmitter.ScratchPadScripts.Num(); BaseScratchPadScriptIndex++) + { + ScratchScriptMap.Add(BaseEmitter.ScratchPadScripts[BaseScratchPadScriptIndex], Emitter.ParentScratchPadScripts[BaseParentScratchPadScriptCount + BaseScratchPadScriptIndex]); + } + + return ApplyScriptStackDiff(EmitterAdapter->GetScriptStack(ScriptUsage, ScriptUsageId).ToSharedRef(), ScratchScriptMap, ResetDiffResults, false); } bool FNiagaraScriptMergeManager::HasBaseEventHandler(const UNiagaraEmitter& BaseEmitter, FGuid EventScriptUsageId) @@ -1894,23 +2004,48 @@ TOptional FNiagaraScriptMergeManager::DoFunctionInputOverridesMatch(TShare if (BaseFunctionInputAdapter->GetDynamicValueFunction().IsValid() && OtherFunctionInputAdapter->GetDynamicValueFunction().IsValid()) { - UNiagaraNodeCustomHlsl* BaseCustomHlsl = Cast(BaseFunctionInputAdapter->GetDynamicValueFunction()->GetFunctionCallNode()); - UNiagaraNodeCustomHlsl* OtherCustomHlsl = Cast(OtherFunctionInputAdapter->GetDynamicValueFunction()->GetFunctionCallNode()); + TSharedRef BaseDynamicValueFunction = BaseFunctionInputAdapter->GetDynamicValueFunction().ToSharedRef(); + TSharedRef OtherDynamicValueFunction = OtherFunctionInputAdapter->GetDynamicValueFunction().ToSharedRef(); - if (BaseCustomHlsl && OtherCustomHlsl) + UNiagaraNodeCustomHlsl* BaseCustomHlsl = Cast(BaseDynamicValueFunction->GetFunctionCallNode()); + UNiagaraNodeCustomHlsl* OtherCustomHlsl = Cast(OtherDynamicValueFunction->GetFunctionCallNode()); + if (BaseCustomHlsl != nullptr || OtherCustomHlsl != nullptr) { + if (((BaseCustomHlsl != nullptr) && (OtherCustomHlsl == nullptr)) || + ((BaseCustomHlsl == nullptr) && (OtherCustomHlsl != nullptr))) + { + return false; + } + if (BaseCustomHlsl->GetCustomHlsl() != OtherCustomHlsl->GetCustomHlsl() || BaseCustomHlsl->ScriptUsage != OtherCustomHlsl->ScriptUsage) { return false; } } - else if (BaseFunctionInputAdapter->GetDynamicValueFunction()->GetFunctionCallNode()->FunctionScript != OtherFunctionInputAdapter->GetDynamicValueFunction()->GetFunctionCallNode()->FunctionScript) + else if (BaseDynamicValueFunction->GetScratchPadScriptIndex() != INDEX_NONE || OtherDynamicValueFunction->GetScratchPadScriptIndex() != INDEX_NONE) + { + int32 BaseScratchPadScriptIndex = BaseDynamicValueFunction->GetScratchPadScriptIndex(); + int32 OtherScratchPadScriptIndex = OtherDynamicValueFunction->GetScratchPadScriptIndex(); + + if (((BaseScratchPadScriptIndex != INDEX_NONE) && (OtherScratchPadScriptIndex == INDEX_NONE)) || + ((BaseScratchPadScriptIndex == INDEX_NONE) && (OtherScratchPadScriptIndex != INDEX_NONE))) + { + return false; + } + + if (BaseScratchPadScriptIndex != OtherScratchPadScriptIndex) + { + return false; + } + } + else if (BaseDynamicValueFunction->GetFunctionCallNode()->FunctionScript != OtherDynamicValueFunction->GetFunctionCallNode()->FunctionScript) { return false; } FNiagaraScriptStackDiffResults FunctionDiffResults; - DiffFunctionInputs(BaseFunctionInputAdapter->GetDynamicValueFunction().ToSharedRef(), OtherFunctionInputAdapter->GetDynamicValueFunction().ToSharedRef(), FunctionDiffResults); + DiffFunctionInputs(BaseDynamicValueFunction, OtherDynamicValueFunction, FunctionDiffResults); + return FunctionDiffResults.RemovedBaseInputOverrides.Num() == 0 && FunctionDiffResults.AddedOtherInputOverrides.Num() == 0 && @@ -1926,7 +2061,12 @@ TOptional FNiagaraScriptMergeManager::DoFunctionInputOverridesMatch(TShare return TOptional(); } -FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddModule(FString UniqueEmitterName, UNiagaraScript& OwningScript, UNiagaraNodeOutput& TargetOutputNode, TSharedRef AddModule) const +FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddModule( + FString UniqueEmitterName, + UNiagaraScript& OwningScript, + UNiagaraNodeOutput& TargetOutputNode, + const TMap& SourceToMergedScratchPadScriptMap, + TSharedRef AddModule) const { FApplyDiffResults Results; @@ -1945,16 +2085,42 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddMod { if (AddModule->GetFunctionCallNode()->FunctionScript != nullptr) { - AddedModuleNode = FNiagaraStackGraphUtilities::AddScriptModuleToStack(AddModule->GetFunctionCallNode()->FunctionScript, TargetOutputNode, AddModule->GetStackIndex()); - AddedModuleNode->NodeGuid = AddModule->GetFunctionCallNode()->NodeGuid; // Synchronize the node Guid across runs so that the compile id's synch up. - Results.bModifiedGraph = true; + UNiagaraScript* FunctionScript = nullptr; + if (AddModule->GetScratchPadScriptIndex() != INDEX_NONE) + { + UNiagaraScript*const* ScratchScriptPtr = SourceToMergedScratchPadScriptMap.Find(AddModule->GetFunctionCallNode()->FunctionScript); + if (ScratchScriptPtr != nullptr) + { + FunctionScript = *ScratchScriptPtr; + } + else + { + Results.bSucceeded = false; + Results.ErrorMessages.Add(FText::Format( + LOCTEXT("MissingScratchPadScript", "Can not add module {0} from node {1} because its merged instance scratch pad script was missing."), + FText::FromString(AddModule->GetFunctionCallNode()->GetFunctionName()), + FText::FromString(AddModule->GetFunctionCallNode()->GetPathName()))); + } + } + else + { + FunctionScript = AddModule->GetFunctionCallNode()->FunctionScript; + } + + if (FunctionScript != nullptr) + { + AddedModuleNode = FNiagaraStackGraphUtilities::AddScriptModuleToStack(FunctionScript, TargetOutputNode, AddModule->GetStackIndex()); + AddedModuleNode->NodeGuid = AddModule->GetFunctionCallNode()->NodeGuid; // Synchronize the node Guid across runs so that the compile id's synch up. + Results.bModifiedGraph = true; + } } else { Results.bSucceeded = false; Results.ErrorMessages.Add(FText::Format( - LOCTEXT("AddModuleFailedDueToMissingModuleScriptFormat", "Can not add module {0} because it's script was missing."), - FText::FromString(AddModule->GetFunctionCallNode()->GetFunctionName()))); + LOCTEXT("AddModuleFailedDueToMissingModuleScriptFormat", "Can not add module {0} from node {1} because its script was missing."), + FText::FromString(AddModule->GetFunctionCallNode()->GetFunctionName()), + FText::FromString(AddModule->GetFunctionCallNode()->GetPathName()))); } } @@ -1965,7 +2131,7 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddMod AddedModuleNode->SetEnabledState(AddModule->GetFunctionCallNode()->GetDesiredEnabledState(), AddModule->GetFunctionCallNode()->HasUserSetTheEnabledState()); for (TSharedRef InputOverride : AddModule->GetInputOverrides()) { - FApplyDiffResults AddInputResults = AddInputOverride(UniqueEmitterName, OwningScript, *AddedModuleNode, InputOverride); + FApplyDiffResults AddInputResults = AddInputOverride(UniqueEmitterName, OwningScript, *AddedModuleNode, SourceToMergedScratchPadScriptMap, InputOverride); Results.bSucceeded &= AddInputResults.bSucceeded; Results.bModifiedGraph |= AddInputResults.bModifiedGraph; Results.ErrorMessages.Append(AddInputResults.ErrorMessages); @@ -2012,7 +2178,12 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::Remove return Results; } -FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddInputOverride(FString UniqueEmitterName, UNiagaraScript& OwningScript, UNiagaraNodeFunctionCall& TargetFunctionCall, TSharedRef OverrideToAdd) const +FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddInputOverride( + FString UniqueEmitterName, + UNiagaraScript& OwningScript, + UNiagaraNodeFunctionCall& TargetFunctionCall, + const TMap& SourceToMergedScratchPadScriptMap, + TSharedRef OverrideToAdd) const { FApplyDiffResults Results; @@ -2096,7 +2267,7 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddInp FNiagaraStackGraphUtilities::SetCustomExpressionForFunctionInput(InputOverridePin, CustomHlslFunction->GetCustomHlsl(), DynamicInputFunctionCall, OverrideToAdd->GetOverrideNodeId()); for (TSharedRef DynamicInputInputOverride : OverrideToAdd->GetDynamicValueFunction()->GetInputOverrides()) { - FApplyDiffResults AddResults = AddInputOverride(UniqueEmitterName, OwningScript, *((UNiagaraNodeFunctionCall*)DynamicInputFunctionCall), DynamicInputInputOverride); + FApplyDiffResults AddResults = AddInputOverride(UniqueEmitterName, OwningScript, *((UNiagaraNodeFunctionCall*)DynamicInputFunctionCall), SourceToMergedScratchPadScriptMap, DynamicInputInputOverride); Results.bSucceeded &= AddResults.bSucceeded; Results.bModifiedGraph |= AddResults.bModifiedGraph; Results.ErrorMessages.Append(AddResults.ErrorMessages); @@ -2104,15 +2275,40 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddInp } else if (OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()->FunctionScript != nullptr) { - UNiagaraNodeFunctionCall* DynamicInputFunctionCall; - FNiagaraStackGraphUtilities::SetDynamicInputForFunctionInput(InputOverridePin, OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()->FunctionScript, - DynamicInputFunctionCall, OverrideToAdd->GetOverrideNodeId(), OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()->GetFunctionName()); - for (TSharedRef DynamicInputInputOverride : OverrideToAdd->GetDynamicValueFunction()->GetInputOverrides()) + UNiagaraScript* FunctionScript = nullptr; + if (OverrideToAdd->GetDynamicValueFunction()->GetScratchPadScriptIndex() != INDEX_NONE) { - FApplyDiffResults AddResults = AddInputOverride(UniqueEmitterName, OwningScript, *DynamicInputFunctionCall, DynamicInputInputOverride); - Results.bSucceeded &= AddResults.bSucceeded; - Results.bModifiedGraph |= AddResults.bModifiedGraph; - Results.ErrorMessages.Append(AddResults.ErrorMessages); + UNiagaraScript*const* ScratchScriptPtr = SourceToMergedScratchPadScriptMap.Find(OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()->FunctionScript); + if (ScratchScriptPtr != nullptr) + { + FunctionScript = *ScratchScriptPtr; + } + else + { + Results.bSucceeded = false; + Results.ErrorMessages.Add(FText::Format( + LOCTEXT("MissingScratchPadScriptForDynamicInput", "Can not add dynamic input {0} from node {1} because its merged instance scratch pad script was missing."), + FText::FromString(OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()->GetFunctionName()), + FText::FromString(OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()->GetPathName()))); + } + } + else + { + FunctionScript = OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()->FunctionScript; + } + + if (FunctionScript != nullptr) + { + UNiagaraNodeFunctionCall* DynamicInputFunctionCall; + FNiagaraStackGraphUtilities::SetDynamicInputForFunctionInput(InputOverridePin, FunctionScript, + DynamicInputFunctionCall, OverrideToAdd->GetOverrideNodeId(), OverrideToAdd->GetDynamicValueFunction()->GetFunctionCallNode()->GetFunctionName()); + for (TSharedRef DynamicInputInputOverride : OverrideToAdd->GetDynamicValueFunction()->GetInputOverrides()) + { + FApplyDiffResults AddResults = AddInputOverride(UniqueEmitterName, OwningScript, *DynamicInputFunctionCall, SourceToMergedScratchPadScriptMap, DynamicInputInputOverride); + Results.bSucceeded &= AddResults.bSucceeded; + Results.bModifiedGraph |= AddResults.bModifiedGraph; + Results.ErrorMessages.Append(AddResults.ErrorMessages); + } } } else @@ -2187,7 +2383,22 @@ void FNiagaraScriptMergeManager::CopyPropertiesToBase(void* BaseDataAddress, con } } -FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyScriptStackDiff(TSharedRef BaseScriptStackAdapter, const FNiagaraScriptStackDiffResults& DiffResults, const bool bNoParentAtLastMerge) const +void FNiagaraScriptMergeManager::CopyInstanceScratchPadScripts(UNiagaraEmitter& MergedInstance, const UNiagaraEmitter& SourceInstance, TMap& OutSourceToMergedScratchPadScriptMap) const +{ + for (UNiagaraScript* SourceScratchPadScript : SourceInstance.ScratchPadScripts) + { + FName UniqueObjectName = FNiagaraEditorUtilities::GetUniqueObjectName(&MergedInstance, SourceScratchPadScript->GetName()); + UNiagaraScript* MergedInstanceScratchPadScript = CastChecked(StaticDuplicateObject(SourceScratchPadScript, &MergedInstance, UniqueObjectName)); + MergedInstance.ScratchPadScripts.Add(MergedInstanceScratchPadScript); + OutSourceToMergedScratchPadScriptMap.Add(SourceScratchPadScript, MergedInstanceScratchPadScript); + } +} + +FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyScriptStackDiff( + TSharedRef BaseScriptStackAdapter, + const TMap& SourceToMergedScratchPadScriptMap, + const FNiagaraScriptStackDiffResults& DiffResults, + const bool bNoParentAtLastMerge) const { FApplyDiffResults Results; @@ -2322,7 +2533,7 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyS for (TSharedRef AddModuleAdapter : AddModules) { - FApplyDiffResults AddModuleResults = AddModule(BaseScriptStackAdapter->GetUniqueEmitterName(), *BaseScriptStackAdapter->GetScript(), *BaseScriptStackAdapter->GetOutputNode(), AddModuleAdapter); + FApplyDiffResults AddModuleResults = AddModule(BaseScriptStackAdapter->GetUniqueEmitterName(), *BaseScriptStackAdapter->GetScript(), *BaseScriptStackAdapter->GetOutputNode(), SourceToMergedScratchPadScriptMap, AddModuleAdapter); Results.bSucceeded &= AddModuleResults.bSucceeded; Results.bModifiedGraph |= AddModuleResults.bModifiedGraph; Results.ErrorMessages.Append(AddModuleResults.ErrorMessages); @@ -2338,7 +2549,8 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyS for (const FAddInputOverrideActionData& AddInputOverrideActionData : AddInputOverrideActionDatas) { - FApplyDiffResults AddInputOverrideResults = AddInputOverride(BaseScriptStackAdapter->GetUniqueEmitterName(), *BaseScriptStackAdapter->GetScript(), *AddInputOverrideActionData.TargetFunctionCall, AddInputOverrideActionData.OverrideToAdd.ToSharedRef()); + FApplyDiffResults AddInputOverrideResults = AddInputOverride(BaseScriptStackAdapter->GetUniqueEmitterName(), *BaseScriptStackAdapter->GetScript(), + *AddInputOverrideActionData.TargetFunctionCall, SourceToMergedScratchPadScriptMap, AddInputOverrideActionData.OverrideToAdd.ToSharedRef()); Results.bSucceeded &= AddInputOverrideResults.bSucceeded; Results.bModifiedGraph |= AddInputOverrideResults.bModifiedGraph; Results.ErrorMessages.Append(AddInputOverrideResults.ErrorMessages); @@ -2357,7 +2569,11 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyS return Results; } -FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyEventHandlerDiff(TSharedRef BaseEmitterAdapter, const FNiagaraEmitterDiffResults& DiffResults, const bool bNoParentAtLastMerge) const +FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyEventHandlerDiff( + TSharedRef BaseEmitterAdapter, + const TMap& SourceToMergedScratchPadScriptMap, + const FNiagaraEmitterDiffResults& DiffResults, + const bool bNoParentAtLastMerge) const { FApplyDiffResults Results; if (DiffResults.RemovedBaseEventHandlers.Num() > 0) @@ -2397,7 +2613,8 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyE } if (ModifiedEventHandler.ScriptDiffResults.IsEmpty() == false) { - FApplyDiffResults ApplyEventHandlerStackDiffResults = ApplyScriptStackDiff(MatchingBaseEventHandlerAdapter->GetEventStack().ToSharedRef(), ModifiedEventHandler.ScriptDiffResults, bNoParentAtLastMerge); + FApplyDiffResults ApplyEventHandlerStackDiffResults = ApplyScriptStackDiff(MatchingBaseEventHandlerAdapter->GetEventStack().ToSharedRef(), + SourceToMergedScratchPadScriptMap, ModifiedEventHandler.ScriptDiffResults, bNoParentAtLastMerge); Results.bSucceeded &= ApplyEventHandlerStackDiffResults.bSucceeded; Results.bModifiedGraph |= ApplyEventHandlerStackDiffResults.bModifiedGraph; Results.ErrorMessages.Append(ApplyEventHandlerStackDiffResults.ErrorMessages); @@ -2439,7 +2656,7 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyE UNiagaraNodeOutput* EventOutputNode = FNiagaraStackGraphUtilities::ResetGraphForOutput(*EmitterGraph, ENiagaraScriptUsage::ParticleEventScript, AddedEventScriptProperties.Script->GetUsageId(), PreferredOutputNodeGuid, PreferredInputNodeGuid); for (TSharedRef ModuleAdapter : AddedEventHandler->GetEventStack()->GetModuleFunctions()) { - FApplyDiffResults AddModuleResults = AddModule(BaseEmitter->GetUniqueEmitterName(), *AddedEventScriptProperties.Script, *EventOutputNode, ModuleAdapter); + FApplyDiffResults AddModuleResults = AddModule(BaseEmitter->GetUniqueEmitterName(), *AddedEventScriptProperties.Script, *EventOutputNode, SourceToMergedScratchPadScriptMap, ModuleAdapter); Results.bSucceeded &= AddModuleResults.bSucceeded; Results.ErrorMessages.Append(AddModuleResults.ErrorMessages); } @@ -2458,7 +2675,11 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyE return Results; } -FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplySimulationStageDiff(TSharedRef BaseEmitterAdapter, const FNiagaraEmitterDiffResults& DiffResults, const bool bNoParentAtLastMerge) const +FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplySimulationStageDiff( + TSharedRef BaseEmitterAdapter, + const TMap& SourceToMergedScratchPadScriptMap, + const FNiagaraEmitterDiffResults& DiffResults, + const bool bNoParentAtLastMerge) const { FApplyDiffResults Results; if (DiffResults.RemovedBaseSimulationStages.Num() > 0) @@ -2497,7 +2718,7 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyS } if (ModifiedSimulationStage.ScriptDiffResults.IsEmpty() == false) { - FApplyDiffResults ApplySimulationStageStackDiffResults = ApplyScriptStackDiff(MatchingBaseSimulationStageAdapter->GetSimulationStageStack().ToSharedRef(), ModifiedSimulationStage.ScriptDiffResults, bNoParentAtLastMerge); + FApplyDiffResults ApplySimulationStageStackDiffResults = ApplyScriptStackDiff(MatchingBaseSimulationStageAdapter->GetSimulationStageStack().ToSharedRef(), SourceToMergedScratchPadScriptMap, ModifiedSimulationStage.ScriptDiffResults, bNoParentAtLastMerge); Results.bSucceeded &= ApplySimulationStageStackDiffResults.bSucceeded; Results.bModifiedGraph |= ApplySimulationStageStackDiffResults.bModifiedGraph; Results.ErrorMessages.Append(ApplySimulationStageStackDiffResults.ErrorMessages); @@ -2539,7 +2760,7 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyS UNiagaraNodeOutput* SimulationStageOutputNode = FNiagaraStackGraphUtilities::ResetGraphForOutput(*EmitterGraph, ENiagaraScriptUsage::ParticleSimulationStageScript, AddedSimulationStage->Script->GetUsageId(), PreferredOutputNodeGuid, PreferredInputNodeGuid); for (TSharedRef ModuleAdapter : AddedOtherSimulationStage->GetSimulationStageStack()->GetModuleFunctions()) { - FApplyDiffResults AddModuleResults = AddModule(BaseEmitter->GetUniqueEmitterName(), *AddedSimulationStage->Script, *SimulationStageOutputNode, ModuleAdapter); + FApplyDiffResults AddModuleResults = AddModule(BaseEmitter->GetUniqueEmitterName(), *AddedSimulationStage->Script, *SimulationStageOutputNode, SourceToMergedScratchPadScriptMap, ModuleAdapter); Results.bSucceeded &= AddModuleResults.bSucceeded; Results.ErrorMessages.Append(AddModuleResults.ErrorMessages); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.h index 522fbe98c89a..1fded321eb56 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.h @@ -33,13 +33,12 @@ class FNiagaraStackFunctionInputOverrideMergeAdapter { public: FNiagaraStackFunctionInputOverrideMergeAdapter( - FString InUniqueEmitterName, + const UNiagaraEmitter& InOwningEmitter, UNiagaraScript& InOwningScript, UNiagaraNodeFunctionCall& InOwningFunctionCallNode, UEdGraphPin& InOverridePin); FNiagaraStackFunctionInputOverrideMergeAdapter( - FString InUniqueEmitterName, UNiagaraScript& InOwningScript, UNiagaraNodeFunctionCall& InOwningFunctionCallNode, FString InInputName, @@ -64,7 +63,6 @@ public: TOptional GetStaticSwitchValue() const; private: - FString UniqueEmitterName; TWeakObjectPtr OwningScript; TWeakObjectPtr OwningFunctionCallNode; FString InputName; @@ -87,21 +85,23 @@ private: class FNiagaraStackFunctionMergeAdapter { public: - FNiagaraStackFunctionMergeAdapter(FString InUniqueEmitterName, UNiagaraScript& InOwningScript, UNiagaraNodeFunctionCall& InFunctionCallNode, int32 InStackIndex); + FNiagaraStackFunctionMergeAdapter(const UNiagaraEmitter& InOwningEmitter, UNiagaraScript& InOwningScript, UNiagaraNodeFunctionCall& InFunctionCallNode, int32 InStackIndex); UNiagaraNodeFunctionCall* GetFunctionCallNode() const; int32 GetStackIndex() const; + int32 GetScratchPadScriptIndex() const; + const TArray>& GetInputOverrides() const; TSharedPtr GetInputOverrideByInputName(FString InputName) const; private: - FString UniqueEmitterName; TWeakObjectPtr OwningScript; TWeakObjectPtr FunctionCallNode; int32 StackIndex; + int32 ScratchPadScriptIndex; TArray> InputOverrides; }; @@ -109,7 +109,7 @@ private: class FNiagaraScriptStackMergeAdapter { public: - FNiagaraScriptStackMergeAdapter(UNiagaraNodeOutput& InOutputNode, UNiagaraScript& InScript, FString InUniqueEmitterName); + FNiagaraScriptStackMergeAdapter(const UNiagaraEmitter& InOwningEmitter, UNiagaraNodeOutput& InOutputNode, UNiagaraScript& InScript); UNiagaraNodeOutput* GetOutputNode() const; UNiagaraNodeInput* GetInputNode() const; @@ -402,23 +402,46 @@ public: private: TOptional DoFunctionInputOverridesMatch(TSharedRef BaseFunctionInputAdapter, TSharedRef OtherFunctionInputAdapter) const; - FApplyDiffResults ApplyScriptStackDiff(TSharedRef BaseScriptStackAdapter, const FNiagaraScriptStackDiffResults& DiffResults, const bool bNoParentAtLastMerge) const; + void CopyInstanceScratchPadScripts(UNiagaraEmitter& MergedInstance, const UNiagaraEmitter& SourceInstance, TMap& OutSourceToMergedScratchPadScriptMap) const; - FApplyDiffResults ApplyEventHandlerDiff(TSharedRef BaseEmitterAdapter, const FNiagaraEmitterDiffResults& DiffResults, const bool bNoParentAtLastMerge) const; + FApplyDiffResults ApplyScriptStackDiff( + TSharedRef BaseScriptStackAdapter, + const TMap& SourceToMergedScratchPadScriptMap, + const FNiagaraScriptStackDiffResults& DiffResults, + const bool bNoParentAtLastMerge) const; - FApplyDiffResults ApplySimulationStageDiff(TSharedRef BaseEmitterAdapter, const FNiagaraEmitterDiffResults& DiffResults, const bool bNoParentAtLastMerge) const; + FApplyDiffResults ApplyEventHandlerDiff( + TSharedRef BaseEmitterAdapter, + const TMap& SourceToMergedScratchPadScriptMap, + const FNiagaraEmitterDiffResults& DiffResults, + const bool bNoParentAtLastMerge) const; + + FApplyDiffResults ApplySimulationStageDiff( + TSharedRef BaseEmitterAdapter, + const TMap& SourceToMergedScratchPadScriptMap, + const FNiagaraEmitterDiffResults& DiffResults, + const bool bNoParentAtLastMerge) const; FApplyDiffResults ApplyRendererDiff(UNiagaraEmitter& BaseEmitter, const FNiagaraEmitterDiffResults& DiffResults, const bool bNoParentAtLastMerge) const; FApplyDiffResults ApplyStackEntryDisplayNameDiffs(UNiagaraEmitter& Emitter, const FNiagaraEmitterDiffResults& DiffResults) const; - FApplyDiffResults AddModule(FString UniqueEmitterName, UNiagaraScript& OwningScript, UNiagaraNodeOutput& TargetOutputNode, TSharedRef AddModule) const; + FApplyDiffResults AddModule( + FString UniqueEmitterName, + UNiagaraScript& OwningScript, + UNiagaraNodeOutput& TargetOutputNode, + const TMap& SourceToMergedScratchPadScriptMap, + TSharedRef AddModule) const; FApplyDiffResults RemoveInputOverride(UNiagaraScript& OwningScript, TSharedRef OverrideToRemove) const; - FApplyDiffResults AddInputOverride(FString UniqueEmitterName, UNiagaraScript& OwningScript, UNiagaraNodeFunctionCall& TargetFunctionCall, TSharedRef OverrideToAdd) const; + FApplyDiffResults AddInputOverride( + FString UniqueEmitterName, + UNiagaraScript& OwningScript, + UNiagaraNodeFunctionCall& TargetFunctionCall, + const TMap& SourceToMergedScratchPadScriptMap, + TSharedRef OverrideToAdd) const; -private: TSharedRef GetEmitterMergeAdapterUsingCache(const UNiagaraEmitter& Emitter); TSharedRef GetEmitterMergeAdapterUsingCache(UNiagaraEmitter& Emitter); @@ -426,7 +449,6 @@ private: void DiffChangeIds(const TMap& InSourceChangeIds, const TMap& InLastMergedChangeIds, const TMap& InInstanceChangeIds, TMap& OutChangeIdsToKeepOnInstance) const; FApplyDiffResults ResolveChangeIds(TSharedRef MergedInstanceAdapter, UNiagaraEmitter& OriginalEmitterInstance, const TMap& ChangeIdsThatNeedToBeReset) const; - private: struct FCachedMergeAdapter { diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraColorTypeEditorUtilities.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraColorTypeEditorUtilities.cpp index da04164863c6..42a2f63499aa 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraColorTypeEditorUtilities.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraColorTypeEditorUtilities.cpp @@ -235,3 +235,9 @@ FText FNiagaraEditorColorTypeUtilities::GetSearchTextFromValue(const FNiagaraVar { return FText::FromString(GetPinDefaultStringFromValue(AllocatedVariable)); } + +FText FNiagaraEditorColorTypeUtilities::GetStackDisplayText(FNiagaraVariable& Variable) const +{ + FLinearColor Value = Variable.GetValue(); + return FText::Format(FText::FromString("({0}, {1}, {2}, {3})"), Value.R, Value.G, Value.B, Value.A); +} diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraColorTypeEditorUtilities.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraColorTypeEditorUtilities.h index 09bc78d428b2..0c8f55243e90 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraColorTypeEditorUtilities.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraColorTypeEditorUtilities.h @@ -19,4 +19,5 @@ public: virtual FString GetPinDefaultStringFromValue(const FNiagaraVariable& AllocatedVariable) const override; virtual bool SetValueFromPinDefaultString(const FString& StringValue, FNiagaraVariable& Variable) const override; virtual FText GetSearchTextFromValue(const FNiagaraVariable& AllocatedVariable) const override; + virtual FText GetStackDisplayText(FNiagaraVariable& Variable) const override; }; \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraEnumTypeEditorUtilities.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraEnumTypeEditorUtilities.cpp index 259a4b8fe48f..71393a358125 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraEnumTypeEditorUtilities.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraEnumTypeEditorUtilities.cpp @@ -164,3 +164,8 @@ FText FNiagaraEditorEnumTypeUtilities::GetSearchTextFromValue(const FNiagaraVari const int32 EnumNameIndex = Enum->GetIndexByValue(AllocatedVariable.GetValue()); return Enum->GetDisplayNameTextByIndex(EnumNameIndex); } + +FText FNiagaraEditorEnumTypeUtilities::GetStackDisplayText(FNiagaraVariable& Variable) const +{ + return Variable.GetType().GetEnum()->GetDisplayNameTextByIndex(Variable.GetValue()); +} diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraEnumTypeEditorUtilities.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraEnumTypeEditorUtilities.h index d9998f838724..e5d95aaee2d8 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraEnumTypeEditorUtilities.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraEnumTypeEditorUtilities.h @@ -20,4 +20,5 @@ public: virtual bool CanSetValueFromDisplayName() const override; virtual bool SetValueFromDisplayName(const FText& TextValue, FNiagaraVariable& Variable) const override; virtual FText GetSearchTextFromValue(const FNiagaraVariable& AllocatedVariable) const override; + virtual FText GetStackDisplayText(FNiagaraVariable& Variable) const override; }; \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraFloatTypeEditorUtilities.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraFloatTypeEditorUtilities.h index 1d0fcc9bc0c7..ef66605bb5b2 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraFloatTypeEditorUtilities.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraFloatTypeEditorUtilities.h @@ -3,6 +3,7 @@ #pragma once #include "INiagaraEditorTypeUtilities.h" +#include "NiagaraTypes.h" class SNiagaraParameterEditor; @@ -17,4 +18,5 @@ public: virtual FString GetPinDefaultStringFromValue(const FNiagaraVariable& AllocatedVariable) const override; virtual bool SetValueFromPinDefaultString(const FString& StringValue, FNiagaraVariable& Variable) const override; virtual FText GetSearchTextFromValue(const FNiagaraVariable& AllocatedVariable) const override; + virtual FText GetStackDisplayText(FNiagaraVariable& Variable) const override { return FText::AsNumber(Variable.GetValue()); }; }; \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraVectorTypeEditorUtilities.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraVectorTypeEditorUtilities.cpp index 8f2709dd965f..cb8eace3390c 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraVectorTypeEditorUtilities.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraVectorTypeEditorUtilities.cpp @@ -181,6 +181,12 @@ FText FNiagaraEditorVector2TypeUtilities::GetSearchTextFromValue(const FNiagaraV return FText::FromString(GetPinDefaultStringFromValue(AllocatedVariable)); } +FText FNiagaraEditorVector2TypeUtilities::GetStackDisplayText(FNiagaraVariable& Variable) const +{ + FVector2D Value = Variable.GetValue(); + return FText::Format(FText::FromString("({0}, {1})"), Value.X, Value.Y); +} + class SNiagaraVector3ParameterEditor : public SNiagaraVectorParameterEditorBase { public: @@ -257,6 +263,12 @@ FText FNiagaraEditorVector3TypeUtilities::GetSearchTextFromValue(const FNiagaraV return FText::FromString(GetPinDefaultStringFromValue(AllocatedVariable)); } +FText FNiagaraEditorVector3TypeUtilities::GetStackDisplayText(FNiagaraVariable& Variable) const +{ + FVector Value = Variable.GetValue(); + return FText::Format(FText::FromString("({0}, {1}, {2})"), Value.X, Value.Y, Value.Z); +} + class SNiagaraVector4ParameterEditor : public SNiagaraVectorParameterEditorBase { public: @@ -333,6 +345,12 @@ FText FNiagaraEditorVector4TypeUtilities::GetSearchTextFromValue(const FNiagaraV return FText::FromString(GetPinDefaultStringFromValue(AllocatedVariable)); } +FText FNiagaraEditorVector4TypeUtilities::GetStackDisplayText(FNiagaraVariable& Variable) const +{ + FVector4 Value = Variable.GetValue(); + return FText::Format(FText::FromString("({0}, {1}, {2}, {3})"), Value.X, Value.Y, Value.Z, Value.W); +} + class SNiagaraQuatParameterEditor : public SNiagaraVectorParameterEditorBase { public: @@ -438,8 +456,53 @@ FText FNiagaraEditorQuatTypeUtilities::GetSearchTextFromValue(const FNiagaraVari return FText::FromString(GetPinDefaultStringFromValue(AllocatedVariable)); } +FText FNiagaraEditorQuatTypeUtilities::GetStackDisplayText(FNiagaraVariable& Variable) const +{ + FQuat Value = Variable.GetValue(); + return FText::Format(FText::FromString("({0}, {1}, {2}, {3})"), Value.X, Value.Y, Value.Z, Value.W); +} + void FNiagaraEditorQuatTypeUtilities::UpdateVariableWithDefaultValue(FNiagaraVariable& Variable) const { checkf(Variable.GetType().GetStruct() == FNiagaraTypeDefinition::GetQuatStruct(), TEXT("Struct type not supported.")); Variable.SetValue(FQuat(0, 0, 0, 1)); } + +bool FNiagaraEditorNiagaraIDTypeUtilities::CanHandlePinDefaults() const +{ + return true; +} + +FString FNiagaraEditorNiagaraIDTypeUtilities::GetPinDefaultStringFromValue(const FNiagaraVariable& AllocatedVariable) const +{ + checkf(AllocatedVariable.IsDataAllocated(), TEXT("Can not generate a default value string for an unallocated variable.")); + FNiagaraID Value = AllocatedVariable.GetValue(); + return FString::Printf(TEXT("%i,%i"), Value.Index, Value.AcquireTag); +} + +bool FNiagaraEditorNiagaraIDTypeUtilities::SetValueFromPinDefaultString(const FString& StringValue, FNiagaraVariable& Variable) const +{ + if (StringValue == TEXT("0")) + { + // Special case handling of 0 default which is specified in niagara constants and is already present in assets. + Variable.SetValue(FNiagaraID()); + return true; + } + else + { + TArray ValueParts; + StringValue.ParseIntoArray(ValueParts, TEXT(",")); + if (ValueParts.Num() == 2) + { + int32 Index; + int32 AcquireTag; + if (LexTryParseString(Index, *ValueParts[0]) && LexTryParseString(AcquireTag, *ValueParts[1])) + { + FNiagaraID Value(Index, AcquireTag); + Variable.SetValue(Value); + return true; + } + } + } + return false; +} diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraVectorTypeEditorUtilities.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraVectorTypeEditorUtilities.h index 912c728d3ec4..39147d827835 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraVectorTypeEditorUtilities.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraVectorTypeEditorUtilities.h @@ -17,6 +17,7 @@ public: virtual FString GetPinDefaultStringFromValue(const FNiagaraVariable& AllocatedVariable) const override; virtual bool SetValueFromPinDefaultString(const FString& StringValue, FNiagaraVariable& Variable) const override; virtual FText GetSearchTextFromValue(const FNiagaraVariable& AllocatedVariable) const override; + virtual FText GetStackDisplayText(FNiagaraVariable& Variable) const override; }; /** Niagara editor utilities for the FVector type. */ @@ -30,6 +31,7 @@ public: virtual FString GetPinDefaultStringFromValue(const FNiagaraVariable& AllocatedVariable) const override; virtual bool SetValueFromPinDefaultString(const FString& StringValue, FNiagaraVariable& Variable) const override; virtual FText GetSearchTextFromValue(const FNiagaraVariable& AllocatedVariable) const override; + virtual FText GetStackDisplayText(FNiagaraVariable& Variable) const override; }; /** Niagara editor utilities for the FVector4 type. */ @@ -43,6 +45,7 @@ public: virtual FString GetPinDefaultStringFromValue(const FNiagaraVariable& AllocatedVariable) const override; virtual bool SetValueFromPinDefaultString(const FString& StringValue, FNiagaraVariable& Variable) const override; virtual FText GetSearchTextFromValue(const FNiagaraVariable& AllocatedVariable) const override; + virtual FText GetStackDisplayText(FNiagaraVariable& Variable) const override; }; /** Niagara editor utilities for the FVector4 type. */ @@ -58,4 +61,15 @@ public: virtual FString GetPinDefaultStringFromValue(const FNiagaraVariable& AllocatedVariable) const override; virtual bool SetValueFromPinDefaultString(const FString& StringValue, FNiagaraVariable& Variable) const override; virtual FText GetSearchTextFromValue(const FNiagaraVariable& AllocatedVariable) const override; + virtual FText GetStackDisplayText(FNiagaraVariable& Variable) const override; +}; + +/** Niagara editor utilities for the FNiagaraID type. */ +class FNiagaraEditorNiagaraIDTypeUtilities : public FNiagaraEditorTypeUtilities +{ +public: + //~ INiagaraEditorTypeUtilities interface. + virtual bool CanHandlePinDefaults() const override; + virtual FString GetPinDefaultStringFromValue(const FNiagaraVariable& AllocatedVariable) const override; + virtual bool SetValueFromPinDefaultString(const FString& StringValue, FNiagaraVariable& Variable) const override; }; \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraParameterCollectionViewModel.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraParameterCollectionViewModel.cpp index 68f70a9dfe55..41cdc81b63c8 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraParameterCollectionViewModel.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraParameterCollectionViewModel.cpp @@ -26,6 +26,17 @@ void INiagaraParameterCollectionViewModel::SortViewModels(TArray> INiagaraParameterCollectionViewModel::GetAvailableTypesSorted() +{ + TArray> AvailableTypes = GetAvailableTypes(); + FText::FSortPredicate SortPredicate; + AvailableTypes.Sort([SortPredicate](const TSharedPtr& A, const TSharedPtr& B) + { + return SortPredicate(A->GetNameText(), B->GetNameText()); + }); + return AvailableTypes; +} + FNiagaraParameterCollectionViewModel::FNiagaraParameterCollectionViewModel(ENiagaraParameterEditMode InParameterEditMode) : ParameterEditMode(InParameterEditMode) , bNeedsRefresh(false) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraParameterCollectionViewModel.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraParameterCollectionViewModel.h index 4d4bab31fbc0..969d9de157c4 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraParameterCollectionViewModel.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraParameterCollectionViewModel.h @@ -55,6 +55,9 @@ public: /** Gets the available types which can be used with the parameter view models. */ virtual const TArray>& GetAvailableTypes() = 0; + /** Gets the available types which can be used with the parameter view models, sorted ascending by name. */ + TArray> GetAvailableTypesSorted(); + /** Gets the display name for the provided type. */ virtual FText GetTypeDisplayName(TSharedPtr Type) const = 0; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraScratchPadUtilities.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraScratchPadUtilities.cpp index 7166fbfae540..1228317ab112 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraScratchPadUtilities.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraScratchPadUtilities.cpp @@ -9,121 +9,6 @@ #include "NiagaraNodeAssignment.h" #include "NiagaraEditorUtilities.h" -// TODO - Remove these duplicated functions and their includes. -#include "NiagaraNodeParameterMapBase.h" -#include "NiagaraNodeParameterMapGet.h" -#include "NiagaraNodeParameterMapSet.h" -#include "NiagaraNodeOutput.h" -#include "ViewModels/Stack/NiagaraStackGraphUtilities.h" - -// This function duplicated here from FNiagaraStackGraphUtilities because its signature needs to be changed to pass the system by pointer, but can't be changed in a point release. -void FNiagaraStackGraphUtilities_FindAffectedScripts(UNiagaraSystem* System, UNiagaraEmitter* Emitter, UNiagaraNodeFunctionCall& ModuleNode, TArray>& OutAffectedScripts) -{ - UNiagaraNodeOutput* OutputNode = FNiagaraStackGraphUtilities::GetEmitterOutputNodeForStackNode(ModuleNode); - - if (OutputNode) - { - TArray Scripts; - if (Emitter != nullptr) - { - Emitter->GetScripts(Scripts, false); - } - - if (System != nullptr) - { - OutAffectedScripts.Add(System->GetSystemSpawnScript()); - OutAffectedScripts.Add(System->GetSystemUpdateScript()); - } - - for (UNiagaraScript* Script : Scripts) - { - if (OutputNode->GetUsage() == ENiagaraScriptUsage::ParticleEventScript) - { - if (Script->GetUsage() == ENiagaraScriptUsage::ParticleEventScript && Script->GetUsageId() == OutputNode->GetUsageId()) - { - OutAffectedScripts.Add(Script); - break; - } - } - else if (Script->ContainsUsage(OutputNode->GetUsage())) - { - OutAffectedScripts.Add(Script); - } - } - } -} - -// This function duplicated here from FNiagaraStackGraphUtilities because its signature needs to be changed to pass the system by pointer, but can't be changed in a point release. -void FNiagaraStackGraphUtilities_RenameReferencingParameters(UNiagaraSystem* System, UNiagaraEmitter* Emitter, UNiagaraNodeFunctionCall& FunctionCallNode, const FString& OldModuleName, const FString& NewModuleName) -{ - TMap OldNameToNewNameMap; - FNiagaraStackGraphUtilities::GatherRenamedStackFunctionInputAndOutputVariableNames(Emitter, FunctionCallNode, OldModuleName, NewModuleName, OldNameToNewNameMap); - - // local function to rename pins referencing the given module - auto RenamePinsReferencingModule = [&OldNameToNewNameMap](UNiagaraNodeParameterMapBase* Node) - { - for (UEdGraphPin* Pin : Node->Pins) - { - FName* NewName = OldNameToNewNameMap.Find(Pin->PinName); - if (NewName != nullptr) - { - Node->SetPinName(Pin, *NewName); - } - } - }; - - UNiagaraNodeParameterMapSet* ParameterMapSet = FNiagaraStackGraphUtilities::GetStackFunctionOverrideNode(FunctionCallNode); - if (ParameterMapSet != nullptr) - { - RenamePinsReferencingModule(ParameterMapSet); - } - - TArray> Scripts; - FNiagaraStackGraphUtilities_FindAffectedScripts(System, Emitter, FunctionCallNode, Scripts); - - const UNiagaraNodeOutput* OutputNode = FNiagaraStackGraphUtilities::GetEmitterOutputNodeForStackNode(FunctionCallNode); - UNiagaraGraph* OwningGraph = FunctionCallNode.GetNiagaraGraph(); - - FString OwningEmitterName = Emitter != nullptr ? Emitter->GetUniqueEmitterName() : FString(); - - for (TWeakObjectPtr Script : Scripts) - { - if (!Script.IsValid(false)) - { - continue; - } - - TArray RapidIterationVariables; - Script->RapidIterationParameters.GetParameters(RapidIterationVariables); - - for (FNiagaraVariable& Variable : RapidIterationVariables) - { - FString EmitterName, FunctionCallName, InputName; - if (FNiagaraParameterMapHistory::SplitRapidIterationParameterName(Variable, EmitterName, FunctionCallName, InputName)) - { - if (EmitterName == OwningEmitterName && FunctionCallName == OldModuleName) - { - FName NewParameterName(*(NewModuleName + TEXT(".") + InputName)); - FNiagaraVariable NewParameter = FNiagaraStackGraphUtilities::CreateRapidIterationParameter(EmitterName, Script->GetUsage(), NewParameterName, Variable.GetType()); - Script->RapidIterationParameters.RenameParameter(Variable, NewParameter.GetName()); - } - } - } - - if (UNiagaraScriptSource* ScriptSource = Cast(Script->GetSource())) - { - // rename all parameter map get nodes that use the parameter name - TArray ParameterGetNodes; - ScriptSource->NodeGraph->GetNodesOfClass(ParameterGetNodes); - - for (UNiagaraNodeParameterMapGet* Node : ParameterGetNodes) - { - RenamePinsReferencingModule(Node); - } - } - } -} - void FNiagaraScratchPadUtilities::FixFunctionInputsFromFunctionScriptRename(UNiagaraNodeFunctionCall& FunctionCallNode, FName NewScriptName) { FString OldFunctionName = FunctionCallNode.GetFunctionName(); @@ -137,7 +22,7 @@ void FNiagaraScratchPadUtilities::FixFunctionInputsFromFunctionScriptRename(UNia const FString NewFunctionName = FunctionCallNode.GetFunctionName(); UNiagaraSystem* System = FunctionCallNode.GetTypedOuter(); UNiagaraEmitter* Emitter = FunctionCallNode.GetTypedOuter(); - FNiagaraStackGraphUtilities_RenameReferencingParameters(System, Emitter, FunctionCallNode, OldFunctionName, NewFunctionName); + FNiagaraStackGraphUtilities::RenameReferencingParameters(System, Emitter, FunctionCallNode, OldFunctionName, NewFunctionName); } void FNiagaraScratchPadUtilities::FixExternalScratchPadScriptsForEmitter(UNiagaraSystem& SourceSystem, UNiagaraEmitter& TargetEmitter) @@ -223,4 +108,4 @@ void FNiagaraScratchPadUtilities::FixExternalScratchPadScriptsForEmitter(UNiagar } } } -} \ No newline at end of file +} diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraSystemViewModel.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraSystemViewModel.cpp index cf58b60828f6..664d617ec2ed 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraSystemViewModel.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraSystemViewModel.cpp @@ -27,6 +27,7 @@ #include "NiagaraEditorModule.h" #include "NiagaraNodeFunctionCall.h" #include "EdGraphSchema_NiagaraSystemOverview.h" +#include "EdGraphUtilities.h" #include "ViewModels/NiagaraScratchPadUtilities.h" #include "Editor.h" diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInput.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInput.cpp index 47b638940114..bc79ece2b21e 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInput.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInput.cpp @@ -22,7 +22,6 @@ #include "NiagaraScriptGraphViewModel.h" #include "NiagaraStackEditorData.h" #include "NiagaraComponent.h" -#include "NiagaraScriptSource.h" #include "NiagaraConstants.h" #include "ViewModels/Stack/NiagaraStackGraphUtilities.h" #include "NiagaraScriptMergeManager.h" @@ -43,7 +42,6 @@ #include "Editor.h" #include "UObject/StructOnScope.h" #include "AssetRegistryModule.h" -#include "ARFilter.h" #include "EdGraph/EdGraphPin.h" #include "INiagaraEditorTypeUtilities.h" #include "ViewModels/Stack/NiagaraStackInputCategory.h" @@ -130,7 +128,7 @@ void UNiagaraStackFunctionInput::Initialize( UNiagaraSystem& ParentSystem = GetSystemViewModel()->GetSystem(); UNiagaraEmitter* ParentEmitter = GetEmitterViewModel().IsValid() ? GetEmitterViewModel()->GetEmitter() : nullptr; - FNiagaraStackGraphUtilities::FindAffectedScripts(ParentSystem, ParentEmitter, *OwningModuleNode.Get(), AffectedScripts); + FNiagaraStackGraphUtilities::FindAffectedScripts(&ParentSystem, ParentEmitter, *OwningModuleNode.Get(), AffectedScripts); UNiagaraNodeOutput* OutputNode = FNiagaraStackGraphUtilities::GetEmitterOutputNodeForStackNode(*OwningModuleNode.Get()); for (TWeakObjectPtr AffectedScript : AffectedScripts) @@ -347,6 +345,73 @@ void UNiagaraStackFunctionInput::Paste(const UNiagaraClipboardContent* Clipboard } } +TArray UNiagaraStackFunctionInput::GetChildInputs() const +{ + TArray DynamicInputCollections; + GetUnfilteredChildrenOfType(DynamicInputCollections); + TArray ChildInputs; + for (UNiagaraStackFunctionInputCollection* DynamicInputCollection : DynamicInputCollections) + { + DynamicInputCollection->GetChildInputs(ChildInputs); + } + + return ChildInputs; +} + +FText UNiagaraStackFunctionInput::GetCollapsedStateText() const +{ + if (IsFinalized()) + { + return FText(); + } + + if (CollapsedTextCache.IsSet() == false) + { + switch (InputValues.Mode) + { + case EValueMode::Local: + { + FNiagaraEditorModule& EditorModule = FNiagaraEditorModule::Get(); + auto TypeUtilityValue = EditorModule.GetTypeUtilities(InputType); + FNiagaraVariable Var(InputType, NAME_None); + Var.SetData(InputValues.LocalStruct->GetStructMemory()); + CollapsedTextCache = TypeUtilityValue->GetStackDisplayText(Var); + } + break; + case EValueMode::Dynamic: + if (InputValues.DynamicNode->FunctionScript != nullptr) + { + FFormatOrderedArguments Arguments; + for (const auto& Child : GetChildInputs()) + { + FText ChildText; + if (Child) + { + ChildText = Child->GetCollapsedStateText(); + } + if (ChildText.IsEmptyOrWhitespace()) + { + ChildText = FText::FromString("[?]"); + } + Arguments.Add(ChildText); + } + CollapsedTextCache = FText::Format(InputValues.DynamicNode->FunctionScript->CollapsedViewFormat, Arguments); + } + break; + case EValueMode::Linked: + CollapsedTextCache = FText::FromString(InputValues.LinkedHandle.GetParameterHandleString().ToString()); + break; + case EValueMode::Expression: + CollapsedTextCache = FText::Format(FText::FromString("({0})"), FText::FromString(InputValues.ExpressionNode->GetCustomHlsl())); + break; + default: + CollapsedTextCache = FText(); + break; + } + } + return CollapsedTextCache.GetValue(); +} + FText UNiagaraStackFunctionInput::GetValueToolTip() const { if (IsFinalized()) @@ -770,6 +835,7 @@ void UNiagaraStackFunctionInput::RefreshValues() bCanResetToBaseCache.Reset(); ValueToolTipCache.Reset(); bIsScratchDynamicInputCache.Reset(); + CollapsedTextCache.Reset(); ValueChangedDelegate.Broadcast(); } @@ -1678,7 +1744,7 @@ void UNiagaraStackFunctionInput::ReassignDynamicInputScript(UNiagaraScript* Dyna const FString NewName = InputValues.DynamicNode->GetFunctionName(); UNiagaraSystem& System = GetSystemViewModel()->GetSystem(); UNiagaraEmitter* Emitter = GetEmitterViewModel().IsValid() ? GetEmitterViewModel()->GetEmitter() : nullptr; - FNiagaraStackGraphUtilities::RenameReferencingParameters(System, Emitter, *InputValues.DynamicNode.Get(), OldName, NewName); + FNiagaraStackGraphUtilities::RenameReferencingParameters(&System, Emitter, *InputValues.DynamicNode.Get(), OldName, NewName); InputValues.DynamicNode->RefreshFromExternalChanges(); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInputCollection.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInputCollection.cpp index 084f07074688..0ef0d3b5ca28 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInputCollection.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInputCollection.cpp @@ -109,6 +109,16 @@ void UNiagaraStackFunctionInputCollection::SetValuesFromClipboardFunctionInputs( } } +void UNiagaraStackFunctionInputCollection::GetChildInputs(TArray& OutResult) const +{ + TArray ChildCategories; + GetUnfilteredChildrenOfType(ChildCategories); + for (UNiagaraStackInputCategory* ChildCategory : ChildCategories) + { + ChildCategory->GetUnfilteredChildrenOfType(OutResult); + } +} + void UNiagaraStackFunctionInputCollection::RefreshChildrenInternal(const TArray& CurrentChildren, TArray& NewChildren, TArray& NewIssues) { TSet HiddenPins; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackGraphUtilities.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackGraphUtilities.cpp index 69f6b0df596b..2b725385c514 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackGraphUtilities.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackGraphUtilities.cpp @@ -3,7 +3,6 @@ #include "ViewModels/Stack/NiagaraStackGraphUtilities.h" #include "NiagaraParameterMapHistory.h" #include "ViewModels/NiagaraSystemViewModel.h" -#include "NiagaraSystemScriptViewModel.h" #include "NiagaraGraph.h" #include "NiagaraNode.h" #include "NiagaraNodeOutput.h" @@ -33,6 +32,7 @@ #include "EdGraph/EdGraphPin.h" #include "ViewModels/NiagaraEmitterViewModel.h" #include "AssetRegistryModule.h" +#include "EdGraphUtilities.h" #include "ObjectTools.h" #include "NiagaraMessageManager.h" @@ -2395,7 +2395,7 @@ void FNiagaraStackGraphUtilities::RebuildEmitterNodes(UNiagaraSystem& System) RelayoutGraph(*SystemGraph); } -void FNiagaraStackGraphUtilities::FindAffectedScripts(UNiagaraSystem& System, UNiagaraEmitter* Emitter, UNiagaraNodeFunctionCall& ModuleNode, TArray>& OutAffectedScripts) +void FNiagaraStackGraphUtilities::FindAffectedScripts(UNiagaraSystem* System, UNiagaraEmitter* Emitter, UNiagaraNodeFunctionCall& ModuleNode, TArray>& OutAffectedScripts) { UNiagaraNodeOutput* OutputNode = FNiagaraStackGraphUtilities::GetEmitterOutputNodeForStackNode(ModuleNode); @@ -2407,8 +2407,8 @@ void FNiagaraStackGraphUtilities::FindAffectedScripts(UNiagaraSystem& System, UN Emitter->GetScripts(Scripts, false); } - OutAffectedScripts.Add(System.GetSystemSpawnScript()); - OutAffectedScripts.Add(System.GetSystemUpdateScript()); + OutAffectedScripts.Add(System->GetSystemSpawnScript()); + OutAffectedScripts.Add(System->GetSystemUpdateScript()); for (UNiagaraScript* Script : Scripts) { @@ -2496,7 +2496,7 @@ void FNiagaraStackGraphUtilities::GatherRenamedStackFunctionInputAndOutputVariab } } -void FNiagaraStackGraphUtilities::RenameReferencingParameters(UNiagaraSystem& System, UNiagaraEmitter* Emitter, UNiagaraNodeFunctionCall& FunctionCallNode, const FString& OldModuleName, const FString& NewModuleName) +void FNiagaraStackGraphUtilities::RenameReferencingParameters(UNiagaraSystem* System, UNiagaraEmitter* Emitter, UNiagaraNodeFunctionCall& FunctionCallNode, const FString& OldModuleName, const FString& NewModuleName) { TMap OldNameToNewNameMap; FNiagaraStackGraphUtilities::GatherRenamedStackFunctionInputAndOutputVariableNames(Emitter, FunctionCallNode, OldModuleName, NewModuleName, OldNameToNewNameMap); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItem.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItem.cpp index 8cfda77b4d6c..f0c758010fb3 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItem.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItem.cpp @@ -288,19 +288,26 @@ void UNiagaraStackModuleItem::RefreshChildrenInternal(const TArray UNiagaraStackModuleItem::CanDropInternal(const FDropRequest& DropRequest) { - if (DropRequest.DragDropOperation->IsOfType() && DropRequest.DropOptions != UNiagaraStackEntry::EDropOptions::Overview && FunctionCallNode->IsA()) + if ((DropRequest.DropOptions != UNiagaraStackEntry::EDropOptions::Overview || DropRequest.DropZone == EItemDropZone::OntoItem) && + DropRequest.DragDropOperation->IsOfType() && + FunctionCallNode->IsA()) { TSharedRef ParameterDragDropOp = StaticCastSharedRef(DropRequest.DragDropOperation); + UNiagaraNodeAssignment* AssignmentNode = CastChecked(FunctionCallNode); TSharedPtr ParameterAction = StaticCastSharedPtr(ParameterDragDropOp->GetSourceAction()); if (ParameterAction.IsValid()) { - if (FNiagaraStackGraphUtilities::CanWriteParameterFromUsage(ParameterAction->GetParameter(), OutputNode->GetUsage())) + if (AssignmentNode->GetAssignmentTargets().Contains(ParameterAction->GetParameter())) { - return FDropRequestResponse(EItemDropZone::OntoItem, LOCTEXT("DropParameterToAdd", "Add this parameter to this 'Set Variables' node.")); + return FDropRequestResponse(TOptional(), LOCTEXT("CantDropDuplicateParameter", "Can not drop this parameter here because\nit's already set by this module.")); + } + else if (FNiagaraStackGraphUtilities::CanWriteParameterFromUsage(ParameterAction->GetParameter(), OutputNode->GetUsage()) == false) + { + return FDropRequestResponse(TOptional(), LOCTEXT("CantDropParameterByUsage", "Can not drop this parameter here because\nit can't be written in this usage context.")); } else { - return FDropRequestResponse(TOptional(), LOCTEXT("CantDropParameterByUsage", "Can not drop this parameter here because\nit can't be written in this usage context.")); + return FDropRequestResponse(EItemDropZone::OntoItem, LOCTEXT("DropParameterToAdd", "Add this parameter to this 'Set Parameters' node.")); } } } @@ -309,11 +316,20 @@ TOptional UNiagaraStackModuleItem::Can TOptional UNiagaraStackModuleItem::DropInternal(const FDropRequest& DropRequest) { - // If the drop was allowed from the can drop just return the drop zone here since it will be handled by the drop target in the stack control. - // TODO: Unify the drop target dropping with the existing drag/drop code. - if (DropRequest.DropOptions != UNiagaraStackEntry::EDropOptions::Overview) + if ((DropRequest.DropOptions != UNiagaraStackEntry::EDropOptions::Overview || DropRequest.DropZone == EItemDropZone::OntoItem) && + DropRequest.DragDropOperation->IsOfType() && + FunctionCallNode->IsA()) { - return FDropRequestResponse(DropRequest.DropZone); + TSharedRef ParameterDragDropOp = StaticCastSharedRef(DropRequest.DragDropOperation); + UNiagaraNodeAssignment* AssignmentNode = CastChecked(FunctionCallNode); + TSharedPtr ParameterAction = StaticCastSharedPtr(ParameterDragDropOp->GetSourceAction()); + if (ParameterAction.IsValid() && + AssignmentNode->GetAssignmentTargets().Contains(ParameterAction->GetParameter()) == false && + FNiagaraStackGraphUtilities::CanWriteParameterFromUsage(ParameterAction->GetParameter(), OutputNode->GetUsage())) + { + AddInput(ParameterAction->GetParameter()); + return FDropRequestResponse(DropRequest.DropZone); + } } return TOptional(); } @@ -515,7 +531,7 @@ void UNiagaraStackModuleItem::RefreshIssues(TArray& NewIssues) FStackIssue DuplicateAssignmentTargetError( EStackIssueSeverity::Error, LOCTEXT("DuplicateAssignmentTargetErrorSummary", "Duplicate variables detected."), - LOCTEXT("DuplicateAssignmentTargetError", "This 'Set Variables' module is attempting to set the same variable more than once, which is unsupported."), + LOCTEXT("DuplicateAssignmentTargetError", "This 'Set Parameters' module is attempting to set the same variable more than once, which is unsupported."), GetStackEditorDataKey(), false, RemoveDuplicateFix); @@ -1034,7 +1050,7 @@ void UNiagaraStackModuleItem::ReassignModuleScript(UNiagaraScript* ModuleScript) { UNiagaraSystem& System = GetSystemViewModel()->GetSystem(); UNiagaraEmitter* Emitter = GetEmitterViewModel().IsValid() ? GetEmitterViewModel()->GetEmitter() : nullptr; - FNiagaraStackGraphUtilities::RenameReferencingParameters(System, Emitter, *FunctionCallNode, OldName, NewName); + FNiagaraStackGraphUtilities::RenameReferencingParameters(&System, Emitter, *FunctionCallNode, OldName, NewName); FunctionCallNode->RefreshFromExternalChanges(); FunctionCallNode->MarkNodeRequiresSynchronization(TEXT("Module script reassigned."), true); RefreshChildren(); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackObject.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackObject.cpp index abd38d88f2ac..5ae31c2adb6d 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackObject.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackObject.cpp @@ -104,6 +104,21 @@ void UNiagaraStackObject::RefreshChildrenInternal(const TArrayOnRowsRefreshed().AddUObject(this, &UNiagaraStackObject::PropertyRowsRefreshed); } + if (!Object->HasAllFlags(EObjectFlags::RF_Transactional)) + { + NewIssues.Add(FStackIssue( + EStackIssueSeverity::Warning, + NSLOCTEXT("StackObject", "ObjectNotTransactionalShort", "Object is not transctional, undo won't work for it!"), + NSLOCTEXT("StackObject", "ObjectNotTransactionalLong", "Object is not transctional, undo won't work for it! Please report this to the Niagara dev team."), + GetStackEditorDataKey(), + false, + { + FStackIssueFix( + NSLOCTEXT("StackObject","TransactionalFix", "Fix transactional status."), + FStackIssueFixDelegate::CreateLambda([this]() { Object->SetFlags(RF_Transactional); })), + })); + } + // TODO: Handle this in a more generic way. Maybe add error apis to UNiagaraMergable, or use a UObject interface, or create a // data interface specific implementation of UNiagaraStackObject. UNiagaraDataInterface* DataInterfaceObject = Cast(Object); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackScriptItemGroup.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackScriptItemGroup.cpp index 1cbc31104f34..b5ba51e13273 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackScriptItemGroup.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackScriptItemGroup.cpp @@ -69,8 +69,9 @@ public: Category = LOCTEXT("ModuleNotCategorized", "Uncategorized Modules"); } - FText DisplayName = FNiagaraEditorUtilities::FormatScriptName(ModuleScript->GetFName(), ModuleScript->bExposeToLibrary); - FText Description = FNiagaraEditorUtilities::FormatScriptDescription(ModuleScript->Description, *ModuleScript->GetPathName(), ModuleScript->bExposeToLibrary); + bool bIsInLibrary = ModuleScript->LibraryVisibility == ENiagaraScriptLibraryVisibility::Library; + FText DisplayName = FNiagaraEditorUtilities::FormatScriptName(ModuleScript->GetFName(), bIsInLibrary); + FText Description = FNiagaraEditorUtilities::FormatScriptDescription(ModuleScript->Description, *ModuleScript->GetPathName(), bIsInLibrary); FText Keywords = ModuleScript->Keywords; return MakeShareable(new FScriptGroupAddAction(Category, DisplayName, Description, Keywords, FNiagaraVariable(), false, FAssetData(), ModuleScript, false, false)); @@ -907,7 +908,7 @@ TOptional UNiagaraStackScriptItemGroup { // Only allow dropping in the overview stacks. return FDropRequestResponse(TOptional(), LOCTEXT("CantDropParameterOnStack", - "Parameters can only be dropped onto 'Set Variables' modules, or correctly\ntyped inputs in the selection view. If you want to add a new 'Set Variables' module for\n this parameter, you can drop it into one of the nodes in the System Overview graph.")); + "Parameters can only be dropped onto 'Set Parameters' modules, or correctly\ntyped inputs in the selection view. If you want to add a new 'Set Parameters' module for\n this parameter, you can drop it into one of the nodes in the System Overview graph.")); } if (ParameterAction.IsValid()) { diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackSimulationStageGroup.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackSimulationStageGroup.cpp index de30da3c7f48..56be842f92a7 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackSimulationStageGroup.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackSimulationStageGroup.cpp @@ -155,6 +155,29 @@ UNiagaraSimulationStageBase* UNiagaraStackSimulationStageGroup::GetSimulationSta return SimulationStage.Get(); } +bool UNiagaraStackSimulationStageGroup::GetIsEnabled() const +{ + bool bEnabled = true; + if (UNiagaraSimulationStageBase* SimStage = SimulationStage.Get()) + { + bEnabled &= SimStage->bEnabled; + } + bEnabled &= Super::GetIsEnabled(); + return bEnabled; +} + +void UNiagaraStackSimulationStageGroup::SetIsEnabled(bool bEnabled) +{ + if (UNiagaraSimulationStageBase* SimStage = SimulationStage.Get()) + { + static FText TEXT_Enabled(LOCTEXT("Enabled", "Enabled")); + static FText TEXT_Disabled(LOCTEXT("Disabled", "Disabled")); + FScopedTransaction Transaction(FText::Format(LOCTEXT("SetSimulationStageEnable", "Set Simulation Stage {1} {0}"), bEnabled ? TEXT_Enabled : TEXT_Disabled, GetDisplayName())); + SimStage->Modify(); + SimStage->SetEnabled(bEnabled); + } +} + void UNiagaraStackSimulationStageGroup::FinalizeInternal() { if (SimulationStage.IsValid()) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackSystemSettingsGroup.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackSystemSettingsGroup.cpp index c496340cbb91..6b64ac2df5ff 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackSystemSettingsGroup.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackSystemSettingsGroup.cpp @@ -2,22 +2,17 @@ #include "ViewModels/Stack/NiagaraStackSystemSettingsGroup.h" #include "ViewModels/Stack/NiagaraStackModuleItem.h" -#include "ViewModels/NiagaraScriptViewModel.h" #include "NiagaraScriptGraphViewModel.h" -#include "NiagaraGraph.h" -#include "NiagaraNodeOutput.h" #include "NiagaraNodeFunctionCall.h" -#include "EdGraphSchema_Niagara.h" #include "ViewModels/Stack/NiagaraStackErrorItem.h" #include "ViewModels/Stack/NiagaraStackParameterStoreEntry.h" #include "Internationalization/Internationalization.h" #include "NiagaraStackEditorData.h" -#include "ScopedTransaction.h" #include "ViewModels/Stack/NiagaraStackGraphUtilities.h" #include "ViewModels/Stack/NiagaraStackSystemPropertiesItem.h" #include "NiagaraConstants.h" #include "ViewModels/NiagaraSystemViewModel.h" -#include "NiagaraSystem.h" +#include "NiagaraEditorUtilities.h" #define LOCTEXT_NAMESPACE "UNiagaraStackParameterStoreGroup" @@ -37,7 +32,7 @@ public: virtual FText GetCategory() const override { - return LOCTEXT("CreateNewParameterCategory", "Parameter Types"); + return FNiagaraEditorUtilities::GetVariableTypeCategory(GetNewParameterVariable()); } virtual FText GetDisplayName() const override diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGraphParameterMapGetNode.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGraphParameterMapGetNode.cpp index d95e65f6a821..276b6306ff34 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGraphParameterMapGetNode.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGraphParameterMapGetNode.cpp @@ -200,18 +200,18 @@ FReply SNiagaraGraphParameterMapGetNode::OnBorderMouseButtonDown(const FGeometry FReply SNiagaraGraphParameterMapGetNode::OnDroppedOnTarget(TSharedPtr DropOperation) { + UNiagaraNodeParameterMapBase* MapNode = Cast(GraphNode); + if (MapNode != nullptr && MapNode->HandleDropOperation(DropOperation)) + { + return FReply::Handled(); + } return FReply::Unhandled(); } bool SNiagaraGraphParameterMapGetNode::OnAllowDrop(TSharedPtr DragDropOperation) { UNiagaraNodeParameterMapBase* MapNode = Cast(GraphNode); - if (MapNode - && DragDropOperation->IsOfType() - && StaticCastSharedPtr(DragDropOperation)->IsCurrentlyHoveringNode(GraphNode)) - { - return MapNode->OnAllowDrop(DragDropOperation); - } - return false; + return MapNode != nullptr && MapNode->CanHandleDropOperation(DragDropOperation); } + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGraphParameterMapSetNode.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGraphParameterMapSetNode.cpp index a2fedc9623be..1a88b8d2188a 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGraphParameterMapSetNode.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGraphParameterMapSetNode.cpp @@ -51,18 +51,18 @@ TSharedRef SNiagaraGraphParameterMapSetNode::CreateNodeContentArea() FReply SNiagaraGraphParameterMapSetNode::OnDroppedOnTarget(TSharedPtr DropOperation) { + UNiagaraNodeParameterMapBase* MapNode = Cast(GraphNode); + if (MapNode != nullptr && MapNode->HandleDropOperation(DropOperation)) + { + return FReply::Handled(); + } return FReply::Unhandled(); } bool SNiagaraGraphParameterMapSetNode::OnAllowDrop(TSharedPtr DragDropOperation) { UNiagaraNodeParameterMapBase* MapNode = Cast(GraphNode); - if (MapNode - && DragDropOperation->IsOfType() - && StaticCastSharedPtr(DragDropOperation)->IsCurrentlyHoveringNode(GraphNode)) - { - return MapNode->OnAllowDrop(DragDropOperation); - } - return false; + return MapNode != nullptr && MapNode->CanHandleDropOperation(DragDropOperation); } + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterCollection.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterCollection.cpp index c3403d58ba78..1c43aafe967c 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterCollection.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterCollection.cpp @@ -12,7 +12,6 @@ #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SExpandableArea.h" #include "Widgets/Text/SInlineEditableTextBlock.h" -#include "Widgets/Input/SComboBox.h" #include "Widgets/Images/SImage.h" #include "Widgets/Layout/SSplitter.h" #include "Framework/MultiBox/MultiBoxBuilder.h" @@ -22,7 +21,6 @@ #include "Framework/Commands/GenericCommands.h" #include "Editor/PropertyEditor/Public/PropertyEditorModule.h" #include "IDetailsView.h" -#include "Widgets/Input/SButton.h" #include "Widgets/SNullWidget.h" #include "Widgets/Input/SSpinBox.h" #include "ScopedTransaction.h" @@ -376,21 +374,45 @@ EVisibility SNiagaraParameterCollection::GetAddButtonTextVisibility() const TSharedRef SNiagaraParameterCollection::GetAddMenuContent() { FMenuBuilder AddMenuBuilder(true, nullptr); - for (TSharedPtr AvailableType : Collection->GetAvailableTypes()) + TSortedMap>> SubmenusToAdd; + for (TSharedPtr AvailableType : Collection->GetAvailableTypesSorted()) { if (AvailableType->GetStruct() != nullptr || AvailableType->GetEnum() != nullptr) { - const FText DisplayName = AvailableType->GetEnum() != nullptr ? - FText::FromName(AvailableType->GetEnum()->GetFName()) : AvailableType->GetStruct()->GetDisplayNameText(); - AddMenuBuilder.AddMenuEntry - ( - DisplayName, - FText(), - FSlateIcon(), - FUIAction(FExecuteAction::CreateSP(Collection.ToSharedRef(), &INiagaraParameterCollectionViewModel::AddParameter, AvailableType)) - ); + FText SubmenuText = FNiagaraEditorUtilities::GetTypeDefinitionCategory(*AvailableType); + if (SubmenuText.IsEmptyOrWhitespace()) + { + AddMenuBuilder.AddMenuEntry + ( + AvailableType->GetNameText(), + FText(), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(Collection.ToSharedRef(), &INiagaraParameterCollectionViewModel::AddParameter, AvailableType)) + ); + } + else + { + SubmenusToAdd.FindOrAdd(SubmenuText.ToString()).Add(AvailableType); + } } } + for (const auto& Entry : SubmenusToAdd) + { + TArray> SubmenuEntries = Entry.Value; + AddMenuBuilder.AddSubMenu(FText::FromString(Entry.Key), FText(), FNewMenuDelegate::CreateLambda([SubmenuEntries, this](FMenuBuilder& InSubMenuBuilder) + { + for (TSharedPtr AvailableType : SubmenuEntries) + { + InSubMenuBuilder.AddMenuEntry + ( + AvailableType->GetNameText(), + FText(), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(Collection.ToSharedRef(), &INiagaraParameterCollectionViewModel::AddParameter, AvailableType)) + ); + } + })); + } return AddMenuBuilder.MakeWidget(); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.cpp index de16deba8f46..8b66daf275df 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.cpp @@ -1702,6 +1702,7 @@ void SNiagaraParameterMapView::RenameParameter(TSharedPtrGetExposedParameters().IndexOf(Parameter) != INDEX_NONE) { @@ -1726,44 +1727,44 @@ void SNiagaraParameterMapView::RenameParameter(TSharedPtrRenameParameter(Parameter, NewName); } - bSuccess = true; + bParameterStoreRename = true; } - if (bSuccess) + // Look for set parameters nodes or linked inputs which reference this parameter. + bool bAssignmentNodeRename = false; + for (FNiagaraGraphParameterReferenceCollection& ReferenceCollection : ParameterAction->ReferenceCollection) { - // Look for set variables nodes or linked inputs which reference this parameter. - for (FNiagaraGraphParameterReferenceCollection& ReferenceCollection : ParameterAction->ReferenceCollection) + for (FNiagaraGraphParameterReference& ParameterReference : ReferenceCollection.ParameterReferences) { - for (FNiagaraGraphParameterReference& ParameterReference : ReferenceCollection.ParameterReferences) + UNiagaraNode* ReferenceNode = Cast(ParameterReference.Value); + if (ReferenceNode != nullptr) { - UNiagaraNode* ReferenceNode = Cast(ParameterReference.Value); - if (ReferenceNode != nullptr) + UNiagaraNodeAssignment* OwningAssignmentNode = ReferenceNode->GetTypedOuter(); + if (OwningAssignmentNode != nullptr) { - UNiagaraNodeAssignment* OwningAssignmentNode = ReferenceNode->GetTypedOuter(); - if (OwningAssignmentNode != nullptr) + // If this is owned by a set variables node and it's not locked, update the assignment target on the assignment node. + bAssignmentNodeRename |= FNiagaraStackGraphUtilities::TryRenameAssignmentTarget(*OwningAssignmentNode, Parameter, NewName); + } + else + { + // Otherwise if the reference node is a get node it's for a linked input so we can just update pin name. + UNiagaraNodeParameterMapGet* ReferenceGetNode = Cast(ReferenceNode); + if (ReferenceGetNode != nullptr) { - // If this is owned by a set variables node and it's not locked, update the assignment target on the assignment node. - FNiagaraStackGraphUtilities::TryRenameAssignmentTarget(*OwningAssignmentNode, Parameter, NewName); - } - else - { - // Otherwise if the reference node is a get node it's for a linked input so we can just update pin name. - UNiagaraNodeParameterMapGet* ReferenceGetNode = Cast(ReferenceNode); - if (ReferenceGetNode != nullptr) + UEdGraphPin** LinkedInputPinPtr = ReferenceGetNode->Pins.FindByPredicate([&ParameterReference](UEdGraphPin* Pin) { return Pin->PersistentGuid == ParameterReference.Key; }); + if (LinkedInputPinPtr != nullptr) { - UEdGraphPin** LinkedInputPinPtr = ReferenceGetNode->Pins.FindByPredicate([&ParameterReference](UEdGraphPin* Pin) { return Pin->PersistentGuid == ParameterReference.Key; }); - if (LinkedInputPinPtr != nullptr) - { - UEdGraphPin* LinkedInputPin = *LinkedInputPinPtr; - LinkedInputPin->Modify(); - LinkedInputPin->PinName = NewName; - } + UEdGraphPin* LinkedInputPin = *LinkedInputPinPtr; + LinkedInputPin->Modify(); + LinkedInputPin->PinName = NewName; } } } } } } + + bSuccess = bParameterStoreRename | bAssignmentNodeRename; } } @@ -2140,23 +2141,7 @@ void SNiagaraAddParameterMenu::AddParameterGroup( Tooltip = VariableStruct->GetToolTipText(true); } - FText SubCategory = FText::GetEmpty(); - if (Variable.GetType().IsDataInterface()) - { - SubCategory = LOCTEXT("NiagaraParameterMenuGroupDI", "Data Interface"); - } - else if (Variable.GetType().IsEnum()) - { - SubCategory = LOCTEXT("NiagaraParameterMenuGroupEnum", "Enum"); - } - else if (Variable.GetType().IsUObject()) - { - SubCategory = LOCTEXT("NiagaraParameterMenuGroupObject", "Object"); - } - else if (Variable.GetName().ToString().Contains("event")) - { - SubCategory = LOCTEXT("NiagaraParameterMenuGroupEvent", "Event"); - } + FText SubCategory = FNiagaraEditorUtilities::GetVariableTypeCategory(Variable); FText FullCategory = SubCategory.IsEmpty() ? Category : FText::Format(FText::FromString("{0}|{1}"), Category, SubCategory); TSharedPtr Action(new FNiagaraMenuAction(FullCategory, DisplayName, Tooltip, 0, FText(), FNiagaraMenuAction::FOnExecuteStackAction::CreateSP(this, &SNiagaraAddParameterMenu::AddParameterSelected, Variable, bCustomName, InSection))); @@ -2203,8 +2188,7 @@ void SNiagaraAddParameterMenu::CollectMakeNew(FGraphActionListBuilderBase& OutAc } TArray Variables; - TArray Types = FNiagaraTypeRegistry::GetRegisteredTypes(); - for (const FNiagaraTypeDefinition& RegisteredType : Types) + for (const FNiagaraTypeDefinition& RegisteredType : FNiagaraTypeRegistry::GetRegisteredTypes()) { bool bAllowType = true; if (OnAllowMakeType.IsBound()) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterPanel.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterPanel.cpp index 1d2af64e16a7..a499f6fae23c 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterPanel.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterPanel.cpp @@ -565,8 +565,7 @@ void SNiagaraAddParameterMenu2::CollectAllActions(FGraphActionListBuilderBase& O // Create actions to create new FNiagaraVariables of every allowed FNiagaraTypeDefinition type. TArray NewParameterMenuActionInfos; - TArray Types = FNiagaraTypeRegistry::GetRegisteredTypes(); - for (const FNiagaraTypeDefinition& RegisteredType : Types) + for (const FNiagaraTypeDefinition& RegisteredType : FNiagaraTypeRegistry::GetRegisteredTypes()) { bool bAllowType = true; if (OnAllowMakeType.IsBound()) @@ -577,7 +576,6 @@ void SNiagaraAddParameterMenu2::CollectAllActions(FGraphActionListBuilderBase& O if (bAllowType) { FNiagaraMenuActionInfo NewMenuActionInfo = FNiagaraMenuActionInfo(); - NewMenuActionInfo.CategoryText = LOCTEXT("NiagaraCreateNewParameterMenu", "Create New Parameter"); FText RegisteredTypeNameText = RegisteredType.GetNameText(); NewMenuActionInfo.DisplayNameText = RegisteredTypeNameText; @@ -594,6 +592,10 @@ void SNiagaraAddParameterMenu2::CollectAllActions(FGraphActionListBuilderBase& O FNiagaraEditorUtilities::ResetVariableToDefaultValue(NewVar); NewMenuActionInfo.NewVariable = NewVar; + const FText Category = LOCTEXT("NiagaraCreateNewParameterMenu", "Create New Parameter"); + FText SubCategory = FNiagaraEditorUtilities::GetVariableTypeCategory(NewVar); + NewMenuActionInfo.CategoryText = SubCategory.IsEmpty() ? Category : FText::Format(FText::FromString("{0}|{1}"), Category, SubCategory); + if (NewVar.IsDataInterface()) { if (const UClass* DataInterfaceClass = NewVar.GetType().GetClass()) @@ -630,13 +632,15 @@ void SNiagaraAddParameterMenu2::CollectAllActions(FGraphActionListBuilderBase& O for (TTuple& GraphParameter : GraphParameters) { + FNiagaraVariable Variable = GraphParameter.Key; const FText Category = LOCTEXT("NiagaraAddExistingParameterMenu", "Add Existing Parameter"); + FText SubCategory = FNiagaraEditorUtilities::GetVariableTypeCategory(Variable); + FText FullCategory = SubCategory.IsEmpty() ? Category : FText::Format(FText::FromString("{0}|{1}"), Category, SubCategory); const FText DisplayName = FText::FromName(GraphParameter.Key.GetName()); const FText Tooltip = GraphParameter.Value->Metadata.Description; - FNiagaraVariable Variable = GraphParameter.Key; TSharedPtr Action(new FNiagaraMenuAction( - Category, DisplayName, Tooltip, 0, FText::GetEmpty(), + FullCategory, DisplayName, Tooltip, 0, FText::GetEmpty(), FNiagaraMenuAction::FOnExecuteStackAction::CreateSP(this, &SNiagaraAddParameterMenu2::AddParameterSelected, Variable))); Action->SetParamterVariable(Variable); @@ -699,11 +703,13 @@ void SNiagaraAddParameterMenu2::CollectAllActions(FGraphActionListBuilderBase& O for (const FNiagaraVariable& Var : Vars) { const FText CategoryText = LOCTEXT("NiagaraAddCommParticleParameterMenu", "Common Particle Attributes"); + FText SubCategory = FNiagaraEditorUtilities::GetVariableTypeCategory(Var); + FText FullCategory = SubCategory.IsEmpty() ? CategoryText : FText::Format(FText::FromString("{0}|{1}"), CategoryText, SubCategory); const FText DisplayName = FText::FromName(Var.GetName()); const FText Tooltip = FNiagaraConstants::GetAttributeDescription(Var); TSharedPtr Action(new FNiagaraMenuAction( - CategoryText, DisplayName, Tooltip, 0, FText::GetEmpty(), + FullCategory, DisplayName, Tooltip, 0, FText::GetEmpty(), FNiagaraMenuAction::FOnExecuteStackAction::CreateSP(this, &SNiagaraAddParameterMenu2::AddParameterSelected, Var))); Action->SetParamterVariable(Var); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp index 300d343590c9..1f530f0a9cef 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp @@ -1045,7 +1045,19 @@ void SNiagaraSpreadsheetView::HandleTimeChange() FName EntryName = NAME_None; if (i != UISystemUpdate) { - EntryName = SelectedEmitterHandle->GetEmitterHandle()->GetIdName(); + auto EmitterInstances = TargetComponent->GetSystemInstance()->GetEmitters(); + for (auto EmitterInstance : EmitterInstances) + { + if (SelectedEmitterHandle->GetEmitterHandle()->GetInstance() == EmitterInstance->GetCachedEmitter()) + { + EntryName = EmitterInstance->GetCachedIDName(); + } + } + + if (EntryName.IsNone()) + { + EntryName = SelectedEmitterHandle->GetEmitterHandle()->GetIdName(); + } } else //if (i == UISystemUpdate) { diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSystemViewport.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSystemViewport.cpp index cb5ac6d3685f..1fb058f8bb2f 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSystemViewport.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSystemViewport.cpp @@ -189,24 +189,48 @@ void FNiagaraSystemViewportClient::DrawInstructionCounts(UNiagaraSystem* Particl for (UNiagaraScript* Script : EmitterScripts) { - uint32 NumInstructions = 0; if (Script->GetUsage() == ENiagaraScriptUsage::ParticleGPUComputeScript) { - FNiagaraShaderRef Shader = Script->GetRenderThreadScript()->GetShaderGameThread(); - if (Shader.IsValid()) + FNiagaraShaderScript* ShaderScript = Script->GetRenderThreadScript(); + if (ShaderScript != nullptr && ShaderScript->GetBaseVMScript() != nullptr) { - NumInstructions = Shader->GetNumInstructions(); + TConstArrayView SimulationStageMetaData = ShaderScript->GetBaseVMScript()->GetSimulationStageMetaData(); + + for (int32 iPermutation=0; iPermutation < ShaderScript->GetNumPermutations(); ++iPermutation) + { + FNiagaraShaderRef Shader = ShaderScript->GetShaderGameThread(iPermutation); + if (Shader.IsValid()) + { + FColor DisplayColor = FColor(196, 196, 196); + FString StageName = TEXT("Particles"); + int32 MinStage = 0; + int32 MaxStage = 1; + + const int32 ShaderStage = iPermutation - 1; + if (SimulationStageMetaData.IsValidIndex(ShaderStage)) + { + if (!SimulationStageMetaData[ShaderStage].SimulationStageName.IsNone()) + { + StageName = SimulationStageMetaData[ShaderStage].SimulationStageName.ToString(); + } + MinStage = SimulationStageMetaData[ShaderStage].MinStage; + MaxStage = SimulationStageMetaData[ShaderStage].MaxStage; + } + + Canvas->DrawShadowedString(CurrentX + 20.0f, CurrentY, *FString::Printf(TEXT("GPU StageName(%s) Stages(%d - %d) = %u"), *StageName, MinStage, MaxStage, Shader->GetNumInstructions()), Font, DisplayColor); + CurrentY += FontHeight; + } + } } } else { - NumInstructions = Script->GetVMExecutableData().LastOpCount; - } - - if (NumInstructions > 0) - { - Canvas->DrawShadowedString(CurrentX + 20.0f, CurrentY, *FString::Printf(TEXT("%s = %u"), *Script->GetName(), NumInstructions), Font, FLinearColor::White); - CurrentY += FontHeight; + const uint32 NumInstructions = Script->GetVMExecutableData().LastOpCount; + if (NumInstructions > 0) + { + Canvas->DrawShadowedString(CurrentX + 20.0f, CurrentY, *FString::Printf(TEXT("%s = %u"), *Script->GetName(), NumInstructions), Font, FLinearColor::White); + CurrentY += FontHeight; + } } } } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/INiagaraEditorTypeUtilities.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/INiagaraEditorTypeUtilities.h index 011924fbb728..04a4fddf13ab 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/INiagaraEditorTypeUtilities.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/INiagaraEditorTypeUtilities.h @@ -41,6 +41,8 @@ public: virtual bool SetValueFromDisplayName(const FText& TextValue, FNiagaraVariable& Variable) const = 0; virtual FText GetSearchTextFromValue(const FNiagaraVariable& AllocatedVariable) const = 0; + + virtual FText GetStackDisplayText(FNiagaraVariable& Variable) const = 0; }; class FNiagaraEditorTypeUtilities : public INiagaraEditorTypeUtilities, public TSharedFromThis @@ -61,4 +63,9 @@ public: virtual bool CanSetValueFromDisplayName() const override { return false; } virtual bool SetValueFromDisplayName(const FText& TextValue, FNiagaraVariable& Variable) const override { return false; } virtual FText GetSearchTextFromValue(const FNiagaraVariable& AllocatedVariable) const override { return FText(); } + virtual FText GetStackDisplayText(FNiagaraVariable& Variable) const override + { + FString DefaultString = GetPinDefaultStringFromValue(Variable); + return FText::FromString(DefaultString.IsEmpty() ? "[?]" : DefaultString); + } }; \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraClipboard.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraClipboard.h index 5d4a505536e1..cc9ef405b192 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraClipboard.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraClipboard.h @@ -175,6 +175,9 @@ public: UFUNCTION(BlueprintPure, Category = "Input") static UNiagaraClipboardFunctionInput* CreateFloatLocalValueInput(UObject* InOuter, FName InInputName, bool bInHasEditCondition, bool bInEditConditionValue, float InLocalValue); + UFUNCTION(BlueprintPure, Category = "Input") + static UNiagaraClipboardFunctionInput* CreateVec2LocalValueInput(UObject* InOuter, FName InInputName, bool bInHasEditCondition, bool bInEditConditionValue, FVector2D InVec2Value); + UFUNCTION(BlueprintPure, Category = "Input") static UNiagaraClipboardFunctionInput* CreateVec3LocalValueInput(UObject* InOuter, FName InInputName, bool bInHasEditCondition, bool bInEditConditionValue, FVector InVec3Value); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorModule.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorModule.h index ed867733e11e..a737919f7c7e 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorModule.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorModule.h @@ -29,6 +29,7 @@ struct FNiagaraScriptHighlight; class FNiagaraClipboard; class UNiagaraScratchPadViewModel; class FHlslNiagaraCompiler; +class FNiagaraComponentBroker; DECLARE_STATS_GROUP(TEXT("Niagara Editor"), STATGROUP_NiagaraEditor, STATCAT_Advanced); @@ -158,7 +159,7 @@ private: }; void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef Action); - void OnNiagaraSettingsChangedEvent(const FString& PropertyName, const UNiagaraSettings* Settings); + void OnNiagaraSettingsChangedEvent(const FName& PropertyName, const UNiagaraSettings* Settings); void OnPreGarbageCollection(); void OnExecParticleInvoked(const TCHAR* InStr); void OnPostEngineInit(); @@ -219,6 +220,8 @@ private: TSharedPtr EditorOnlyDataUtilities; + TSharedPtr NiagaraComponentBroker; + TMap TypeToParameterTrackCreatorMap; IConsoleCommand* TestCompileScriptCommand; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorUtilities.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorUtilities.h index df6bf1f9e09e..4e36a51c2e1d 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorUtilities.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorUtilities.h @@ -144,6 +144,10 @@ namespace FNiagaraEditorUtilities bool IsCompilableAssetClass(UClass* AssetClass); + FText GetVariableTypeCategory(const FNiagaraVariable& Variable); + + FText GetTypeDefinitionCategory(const FNiagaraTypeDefinition& TypeDefinition); + void MarkDependentCompilableAssetsDirty(TArray InObjects); void ResolveNumerics(UNiagaraGraph* SourceGraph, bool bForceParametersToResolveNumerics, TArray& ChangedNumericParams); @@ -198,6 +202,8 @@ namespace FNiagaraEditorUtilities */ const FNiagaraEmitterHandle* GetEmitterHandleForEmitter(UNiagaraSystem& System, UNiagaraEmitter& Emitter); + NIAGARAEDITOR_API ENiagaraScriptLibraryVisibility GetScriptAssetVisibility(const FAssetData& ScriptAssetData); + NIAGARAEDITOR_API bool IsScriptAssetInLibrary(const FAssetData& ScriptAssetData); NIAGARAEDITOR_API FText FormatScriptName(FName Name, bool bIsInLibrary); @@ -291,7 +297,7 @@ namespace FNiagaraEditorUtilities void GetParameterMetaDataFromName(const FName& InVarNameToken, FNiagaraVariableMetaData& OutMetaData); - FString GetNamespacelessVariableNameString(const FName& InVarName); + FString NIAGARAEDITOR_API GetNamespacelessVariableNameString(const FName& InVarName); void GetReferencingFunctionCallNodes(UNiagaraScript* Script, TArray& OutReferencingFunctionCallNodes); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraGraph.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraGraph.h index e3d1a4a7d9cb..a775b7b7a04c 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraGraph.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraGraph.h @@ -386,6 +386,9 @@ class UNiagaraGraph : public UEdGraph static FName StandardizeName(FName Name, ENiagaraScriptUsage Usage, bool bIsGet, bool bIsSet); + /** Helper to get a map of variables to all input/output pins with the same name. */ + const TMap NIAGARAEDITOR_API CollectVarsToInOutPinsMap() const; + protected: void RebuildNumericCache(); bool bNeedNumericCacheRebuilt; @@ -405,9 +408,6 @@ private: /** When a new variable is added to the VariableToScriptVariableMap, generate appropriate scope and usage. */ void GenerateMetaDataForScriptVariable(UNiagaraScriptVariable* InScriptVariable) const; - /** Helper to get a map of variables to all input/output pins with the same name. */ - const TMap CollectVarsToInOutPinsMap() const; - /** * Set the usage of a script variable depending on input/output pins with same name. * @param VarToPinsMap Mapping of Pins to the associated UNiagaraScriptVariable. diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInput.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInput.h index ade604ff561e..7e8b077bb66f 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInput.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInput.h @@ -111,6 +111,9 @@ public: /** Gets the tooltip that should be shown for the value of this input. */ FText GetValueToolTip() const; + /** Gets the tooltip that should be shown for the value of this input. */ + FText GetCollapsedStateText() const; + /** Gets the path of parameter handles from the owning module to the function call which owns this input. */ const TArray& GetInputParameterHandlePath() const; @@ -338,6 +341,8 @@ private: /** Handles the message manager refreshing messages. */ void OnMessageManagerRefresh(const TArray>& NewMessages); + TArray GetChildInputs() const; + private: /** The module function call which owns this input entry. NOTE: This input might not be an input to the module function call, it may be an input to a dynamic input function call which is owned by the module. */ @@ -406,6 +411,9 @@ private: /** A tooltip to show for the value of this input. */ mutable TOptional ValueToolTipCache; + /** Text to display on a collapsed node. */ + mutable TOptional CollapsedTextCache; + mutable TOptional bIsScratchDynamicInputCache; /** A flag to prevent handling graph changes when it's being updated directly by this object. */ diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInputCollection.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInputCollection.h index 29ff408fdbeb..3fd59acfd4b8 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInputCollection.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInputCollection.h @@ -40,6 +40,8 @@ public: void SetValuesFromClipboardFunctionInputs(const TArray& ClipboardFunctionInputs); + void GetChildInputs(TArray& OutResult) const; + protected: virtual void FinalizeInternal() override; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackGraphUtilities.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackGraphUtilities.h index 206cc1700493..daf153b3ebc8 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackGraphUtilities.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackGraphUtilities.h @@ -187,9 +187,9 @@ namespace FNiagaraStackGraphUtilities void RebuildEmitterNodes(UNiagaraSystem& System); - void FindAffectedScripts(UNiagaraSystem& System, UNiagaraEmitter* Emitter, UNiagaraNodeFunctionCall& ModuleNode, TArray>& OutAffectedScripts); + void FindAffectedScripts(UNiagaraSystem* System, UNiagaraEmitter* Emitter, UNiagaraNodeFunctionCall& ModuleNode, TArray>& OutAffectedScripts); - void RenameReferencingParameters(UNiagaraSystem& System, UNiagaraEmitter* Emitter, UNiagaraNodeFunctionCall& FunctionCallNode, const FString& OldName, const FString& NewName); + void RenameReferencingParameters(UNiagaraSystem* System, UNiagaraEmitter* Emitter, UNiagaraNodeFunctionCall& FunctionCallNode, const FString& OldName, const FString& NewName); void GatherRenamedStackFunctionOutputVariableNames(UNiagaraEmitter* Emitter, UNiagaraNodeFunctionCall& FunctionCallNode, const FString& OldFunctionName, const FString& NewFunctionName, TMap& OutOldToNewNameMap); void GatherRenamedStackFunctionInputAndOutputVariableNames(UNiagaraEmitter* Emitter, UNiagaraNodeFunctionCall& FunctionCallNode, const FString& OldFunctionName, const FString& NewFunctionName, TMap& OutOldToNewNameMap); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackItemGroup.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackItemGroup.h index b107ee28f576..1cce7e479b7b 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackItemGroup.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackItemGroup.h @@ -24,6 +24,8 @@ public: virtual FText GetTooltipText() const override; virtual bool GetIsEnabled() const override; + virtual void SetIsEnabled(bool bEnabled) {} + virtual bool SupportsChangeEnabled() const { return false; } INiagaraStackItemGroupAddUtilities* GetAddUtilities() const; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackSimulationStageGroup.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackSimulationStageGroup.h index 7d8f08ca29b8..e94bf1e2fa36 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackSimulationStageGroup.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackSimulationStageGroup.h @@ -73,6 +73,10 @@ public: virtual bool CanDrag() const override { return true; } + virtual bool GetIsEnabled() const override; + virtual void SetIsEnabled(bool bEnabled) override; + virtual bool SupportsChangeEnabled() const override { return true; } + protected: virtual void FinalizeInternal() override; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/DetailCustomizations/NiagaraDataInterfaceSkeletalMeshDetails.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/DetailCustomizations/NiagaraDataInterfaceSkeletalMeshDetails.cpp index 673bf39db729..ed4ce5d5e86a 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/DetailCustomizations/NiagaraDataInterfaceSkeletalMeshDetails.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/DetailCustomizations/NiagaraDataInterfaceSkeletalMeshDetails.cpp @@ -11,92 +11,95 @@ #include "Engine/SkeletalMeshSocket.h" #include "SNiagaraNamePropertySelector.h" - #define LOCTEXT_NAMESPACE "FNiagaraDataInterfaceSkeletalMeshDetails" +#define LOCTEXT_NAMESPACE "FNiagaraDataInterfaceSkeletalMeshDetails" void FNiagaraDataInterfaceSkeletalMeshDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { - LayoutBuilder = &DetailBuilder; - static const FName MeshCategoryName = TEXT("Mesh"); - static const FName SkelCategoryName = TEXT("Skeleton"); + LayoutBuilder = &DetailBuilder; + static const FName MeshCategoryName = TEXT("Mesh"); + static const FName SkelCategoryName = TEXT("Skeleton"); - TArray> SelectedObjects; - DetailBuilder.GetObjectsBeingCustomized(SelectedObjects); - if(SelectedObjects.Num() != 1 || SelectedObjects[0]->IsA() == false) - { - return; - } + TArray> SelectedObjects; + DetailBuilder.GetObjectsBeingCustomized(SelectedObjects); + if(SelectedObjects.Num() != 1 || SelectedObjects[0]->IsA() == false) + { + return; + } - MeshInterface = CastChecked(SelectedObjects[0].Get()); - MeshInterface->OnChanged().RemoveAll(this); - MeshInterface->OnChanged().AddSP(this, &FNiagaraDataInterfaceSkeletalMeshDetails::OnInterfaceChanged); + UNiagaraDataInterfaceSkeletalMesh* Interface = CastChecked(SelectedObjects[0].Get()); + MeshInterface = Interface; + + Interface->OnChanged().RemoveAll(this); + Interface->OnChanged().AddSP(this, &FNiagaraDataInterfaceSkeletalMeshDetails::OnInterfaceChanged); - TWeakObjectPtr SceneComponent; - USkeletalMeshComponent* FoundSkelComp = nullptr; - UNiagaraDataInterfaceSkeletalMesh* Interface = MeshInterface.Get(); - MeshObject = Interface->GetSkeletalMesh(Cast(MeshInterface->GetOuter()), SceneComponent, FoundSkelComp); - if (MeshObject.IsValid()) - { - MeshObject->GetOnMeshChanged().RemoveAll(this); - MeshObject->GetOnMeshChanged().AddSP(this, &FNiagaraDataInterfaceSkeletalMeshDetails::OnDataChanged); - } + UNiagaraComponent* NiagaraComponent = Cast(Interface->GetOuter()); + FNiagaraSystemInstance* SystemInstance = NiagaraComponent ? NiagaraComponent->GetSystemInstance() : nullptr; + TWeakObjectPtr SceneComponent; + USkeletalMeshComponent* FoundSkelComp = nullptr; + MeshObject = Interface->GetSkeletalMesh(SystemInstance, SceneComponent, FoundSkelComp); + if (MeshObject.IsValid()) + { + MeshObject->GetOnMeshChanged().RemoveAll(this); + MeshObject->GetOnMeshChanged().AddSP(this, &FNiagaraDataInterfaceSkeletalMeshDetails::OnDataChanged); + } - MeshCategory = &DetailBuilder.EditCategory(MeshCategoryName, LOCTEXT("Mesh", "Mesh")); - { - TArray> MeshProperties; - MeshCategory->GetDefaultProperties(MeshProperties, true, true); + MeshCategory = &DetailBuilder.EditCategory(MeshCategoryName, LOCTEXT("Mesh", "Mesh")); + { + TArray> MeshProperties; + MeshCategory->GetDefaultProperties(MeshProperties, true, true); - TSharedPtr RegionsProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UNiagaraDataInterfaceSkeletalMesh, SamplingRegions)); + TSharedPtr RegionsProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UNiagaraDataInterfaceSkeletalMesh, SamplingRegions)); - for (TSharedPtr Property : MeshProperties) - { - FProperty* PropertyPtr = Property->GetProperty(); - TArray> PossibleNames; - if (PropertyPtr == RegionsProperty->GetProperty()) - { - GenerateRegionsArray(PossibleNames); - RegionsBuilder = TSharedPtr(new FNiagaraDetailSourcedArrayBuilder(Property.ToSharedRef(), PossibleNames)); - MeshCategory->AddCustomBuilder(RegionsBuilder.ToSharedRef()); - } - else - { - MeshCategory->AddProperty(Property); - } - } - } + for (TSharedPtr Property : MeshProperties) + { + FProperty* PropertyPtr = Property->GetProperty(); + TArray> PossibleNames; + if (PropertyPtr == RegionsProperty->GetProperty()) + { + GenerateRegionsArray(PossibleNames); + RegionsBuilder = TSharedPtr(new FNiagaraDetailSourcedArrayBuilder(Property.ToSharedRef(), PossibleNames)); + MeshCategory->AddCustomBuilder(RegionsBuilder.ToSharedRef()); + } + else + { + MeshCategory->AddProperty(Property); + } + } + } - SkelCategory = &DetailBuilder.EditCategory(SkelCategoryName, LOCTEXT("SkeletonCat", "Skeleton")); - { - TArray> SkelProperties; - SkelCategory->GetDefaultProperties(SkelProperties, true, true); + SkelCategory = &DetailBuilder.EditCategory(SkelCategoryName, LOCTEXT("SkeletonCat", "Skeleton")); + { + TArray> SkelProperties; + SkelCategory->GetDefaultProperties(SkelProperties, true, true); - TSharedPtr BonesProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UNiagaraDataInterfaceSkeletalMesh, FilteredBones)); - TSharedPtr SocketsProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UNiagaraDataInterfaceSkeletalMesh, FilteredSockets)); - TSharedPtr ExcludeBoneProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UNiagaraDataInterfaceSkeletalMesh, ExcludeBoneName)); + TSharedPtr BonesProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UNiagaraDataInterfaceSkeletalMesh, FilteredBones)); + TSharedPtr SocketsProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UNiagaraDataInterfaceSkeletalMesh, FilteredSockets)); + TSharedPtr ExcludeBoneProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UNiagaraDataInterfaceSkeletalMesh, ExcludeBoneName)); - for (TSharedPtr Property : SkelProperties) - { - FProperty* PropertyPtr = Property->GetProperty(); - TArray> PossibleNames; + for (TSharedPtr Property : SkelProperties) + { + FProperty* PropertyPtr = Property->GetProperty(); + TArray> PossibleNames; - if (PropertyPtr == BonesProperty->GetProperty()) - { + if (PropertyPtr == BonesProperty->GetProperty()) + { GenerateBonesArray(PossibleNames); BonesBuilder = TSharedPtr(new FNiagaraDetailSourcedArrayBuilder(Property.ToSharedRef(), PossibleNames)); SkelCategory->AddCustomBuilder(BonesBuilder.ToSharedRef()); - } - else if (PropertyPtr == SocketsProperty->GetProperty()) - { - GenerateSocketsArray(PossibleNames); - SocketsBuilder = TSharedPtr(new FNiagaraDetailSourcedArrayBuilder(Property.ToSharedRef(), PossibleNames)); - SkelCategory->AddCustomBuilder(SocketsBuilder.ToSharedRef()); - } - else if (PropertyPtr == ExcludeBoneProperty->GetProperty()) - { - GenerateBonesArray(PossibleNames); - ExcludeBoneWidget = SNew(SNiagaraNamePropertySelector, Property.ToSharedRef(), PossibleNames); + } + else if (PropertyPtr == SocketsProperty->GetProperty()) + { + GenerateSocketsArray(PossibleNames); + SocketsBuilder = TSharedPtr(new FNiagaraDetailSourcedArrayBuilder(Property.ToSharedRef(), PossibleNames)); + SkelCategory->AddCustomBuilder(SocketsBuilder.ToSharedRef()); + } + else if (PropertyPtr == ExcludeBoneProperty->GetProperty()) + { + GenerateBonesArray(PossibleNames); + ExcludeBoneWidget = SNew(SNiagaraNamePropertySelector, Property.ToSharedRef(), PossibleNames); - IDetailPropertyRow& ExcludeBoneRow = SkelCategory->AddProperty(Property); - ExcludeBoneRow.CustomWidget(false) + IDetailPropertyRow& ExcludeBoneRow = SkelCategory->AddProperty(Property); + ExcludeBoneRow.CustomWidget(false) .NameContent() [ Property->CreatePropertyNameWidget() @@ -106,40 +109,44 @@ void FNiagaraDataInterfaceSkeletalMeshDetails::CustomizeDetails(IDetailLayoutBui [ ExcludeBoneWidget.ToSharedRef() ]; - } - else - { - SkelCategory->AddProperty(Property); - } - } - } + } + else + { + SkelCategory->AddProperty(Property); + } + } + } } - TSharedRef FNiagaraDataInterfaceSkeletalMeshDetails::MakeInstance() - { - return MakeShared(); - } +TSharedRef FNiagaraDataInterfaceSkeletalMeshDetails::MakeInstance() +{ + return MakeShared(); +} - void FNiagaraDataInterfaceSkeletalMeshDetails::OnInterfaceChanged() - { - // Rebuild the data changed listener - TWeakObjectPtr SceneComponent; - USkeletalMeshComponent* FoundSkelComp = nullptr; - if (MeshObject.IsValid()) - { - MeshObject->GetOnMeshChanged().RemoveAll(this); - } - UNiagaraDataInterfaceSkeletalMesh* Interface = MeshInterface.Get(); - MeshObject = Interface->GetSkeletalMesh(Cast(MeshInterface->GetOuter()), SceneComponent, FoundSkelComp); - if (MeshObject.IsValid()) - { - MeshObject->GetOnMeshChanged().AddSP(this, &FNiagaraDataInterfaceSkeletalMeshDetails::OnDataChanged); - } - OnDataChanged(); - } +void FNiagaraDataInterfaceSkeletalMeshDetails::OnInterfaceChanged() +{ + // Rebuild the data changed listener + if (MeshObject.IsValid()) + { + MeshObject->GetOnMeshChanged().RemoveAll(this); + } + + UNiagaraDataInterfaceSkeletalMesh* Interface = MeshInterface.Get(); + UNiagaraComponent* NiagaraComponent = Cast(Interface->GetOuter()); + FNiagaraSystemInstance* SystemInstance = NiagaraComponent ? NiagaraComponent->GetSystemInstance() : nullptr; + TWeakObjectPtr SceneComponent; + USkeletalMeshComponent* FoundSkelComp = nullptr; + MeshObject = Interface->GetSkeletalMesh(SystemInstance, SceneComponent, FoundSkelComp); + if (MeshObject.IsValid()) + { + MeshObject->GetOnMeshChanged().AddSP(this, &FNiagaraDataInterfaceSkeletalMeshDetails::OnDataChanged); + } - void FNiagaraDataInterfaceSkeletalMeshDetails::OnDataChanged() - { + OnDataChanged(); +} + +void FNiagaraDataInterfaceSkeletalMeshDetails::OnDataChanged() +{ if (RegionsBuilder) { TArray> PossibleNames; @@ -167,79 +174,77 @@ void FNiagaraDataInterfaceSkeletalMeshDetails::CustomizeDetails(IDetailLayoutBui GenerateBonesArray(PossibleNames); ExcludeBoneWidget->SetSourceArray(PossibleNames); } - } +} void FNiagaraDataInterfaceSkeletalMeshDetails::GenerateRegionsArray(TArray>& SourceArray) - { +{ SourceArray.Reset(); - if (MeshInterface.IsValid()) - { - TWeakObjectPtr SceneComponent; - USkeletalMeshComponent* FoundSkelComp = nullptr; - UNiagaraDataInterfaceSkeletalMesh* Interface = MeshInterface.Get(); - USkeletalMesh* Mesh = Interface->GetSkeletalMesh(Cast(MeshInterface->GetOuter()), SceneComponent, FoundSkelComp); - - if (Mesh != nullptr) - { - for (FSkeletalMeshSamplingRegion Region : Mesh->GetSamplingInfo().Regions) - { - SourceArray.Add(MakeShared(Region.Name)); - } - } - } - } + if (UNiagaraDataInterfaceSkeletalMesh* Interface = MeshInterface.Get()) + { + UNiagaraComponent* NiagaraComponent = Cast(Interface->GetOuter()); + FNiagaraSystemInstance* SystemInstance = NiagaraComponent ? NiagaraComponent->GetSystemInstance() : nullptr; + TWeakObjectPtr SceneComponent; + USkeletalMeshComponent* FoundSkelComp = nullptr; + if (USkeletalMesh* Mesh = Interface->GetSkeletalMesh(SystemInstance, SceneComponent, FoundSkelComp)) + { + for (FSkeletalMeshSamplingRegion Region : Mesh->GetSamplingInfo().Regions) + { + SourceArray.Add(MakeShared(Region.Name)); + } + } + } +} void FNiagaraDataInterfaceSkeletalMeshDetails::GenerateBonesArray(TArray>& SourceArray) { - SourceArray.Reset(); - if (MeshInterface.IsValid()) - { - TWeakObjectPtr SceneComponent; - USkeletalMeshComponent* FoundSkelComp = nullptr; - UNiagaraDataInterfaceSkeletalMesh* Interface = MeshInterface.Get(); - USkeletalMesh* Mesh = Interface->GetSkeletalMesh(Cast(MeshInterface->GetOuter()), SceneComponent, FoundSkelComp); - - if (Mesh != nullptr) - { - for (const FMeshBoneInfo& Bone : Mesh->RefSkeleton.GetRefBoneInfo()) - { - SourceArray.Add(MakeShared(Bone.Name)); - } - } - } - } + SourceArray.Reset(); + if (UNiagaraDataInterfaceSkeletalMesh* Interface = MeshInterface.Get()) + { + UNiagaraComponent* NiagaraComponent = Cast(Interface->GetOuter()); + FNiagaraSystemInstance* SystemInstance = NiagaraComponent ? NiagaraComponent->GetSystemInstance() : nullptr; + TWeakObjectPtr SceneComponent; + USkeletalMeshComponent* FoundSkelComp = nullptr; + if (USkeletalMesh* Mesh = Interface->GetSkeletalMesh(SystemInstance, SceneComponent, FoundSkelComp)) + { + for (const FMeshBoneInfo& Bone : Mesh->RefSkeleton.GetRefBoneInfo()) + { + SourceArray.Add(MakeShared(Bone.Name)); + } + } + } +} void FNiagaraDataInterfaceSkeletalMeshDetails::GenerateSocketsArray(TArray>& SourceArray) - { +{ SourceArray.Reset(); - if (MeshInterface.IsValid()) - { - TWeakObjectPtr SceneComponent; - USkeletalMeshComponent* FoundSkelComp = nullptr; - UNiagaraDataInterfaceSkeletalMesh* Interface = MeshInterface.Get(); - USkeletalMesh* Mesh = Interface->GetSkeletalMesh(Cast(MeshInterface->GetOuter()), SceneComponent, FoundSkelComp); - - if (Mesh != nullptr) - { - for (int32 SocketIdx = 0; SocketIdx < Mesh->NumSockets(); ++SocketIdx) - { - const USkeletalMeshSocket* SocketInfo = Mesh->GetSocketByIndex(SocketIdx); - SourceArray.Add(MakeShared(SocketInfo->SocketName)); - } - } - } + if (MeshInterface.IsValid()) + { + UNiagaraDataInterfaceSkeletalMesh* Interface = MeshInterface.Get(); + UNiagaraComponent* NiagaraComponent = Cast(Interface->GetOuter()); + FNiagaraSystemInstance* SystemInstance = NiagaraComponent ? NiagaraComponent->GetSystemInstance() : nullptr; + TWeakObjectPtr SceneComponent; + USkeletalMeshComponent* FoundSkelComp = nullptr; + if (USkeletalMesh* Mesh = Interface->GetSkeletalMesh(SystemInstance, SceneComponent, FoundSkelComp)) + { + for (int32 SocketIdx = 0; SocketIdx < Mesh->NumSockets(); ++SocketIdx) + { + const USkeletalMeshSocket* SocketInfo = Mesh->GetSocketByIndex(SocketIdx); + SourceArray.Add(MakeShared(SocketInfo->SocketName)); + } + } + } } - FNiagaraDataInterfaceSkeletalMeshDetails::~FNiagaraDataInterfaceSkeletalMeshDetails() - { - if (MeshInterface.IsValid()) - { - MeshInterface->OnChanged().RemoveAll(this); - } - if (MeshObject.IsValid()) - { +FNiagaraDataInterfaceSkeletalMeshDetails::~FNiagaraDataInterfaceSkeletalMeshDetails() +{ + if (MeshInterface.IsValid()) + { + MeshInterface->OnChanged().RemoveAll(this); + } + if (MeshObject.IsValid()) + { MeshObject->GetOnMeshChanged().RemoveAll(this); - } - } + } +} #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackFunctionInputValue.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackFunctionInputValue.cpp index 66cac3b3c78f..51bf20fb492e 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackFunctionInputValue.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackFunctionInputValue.cpp @@ -20,7 +20,6 @@ #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Images/SImage.h" -#include "Widgets/Input/SCheckBox.h" #include "Editor/PropertyEditor/Public/PropertyEditorModule.h" #include "Framework/Application/SlateApplication.h" #include "IStructureDetailsView.h" @@ -391,6 +390,14 @@ FText SNiagaraStackFunctionInputValue::GetDynamicValueText() const { if (FunctionInput->GetDynamicInputNode() != nullptr) { + if (!FunctionInput->GetIsExpanded()) + { + FText CollapsedText = FunctionInput->GetCollapsedStateText(); + if (!CollapsedText.IsEmptyOrWhitespace()) + { + return CollapsedText; + } + } return FText::FromString(FName::NameToDisplayString(FunctionInput->GetDynamicInputNode()->GetFunctionName(), false)); } else @@ -563,8 +570,9 @@ void SNiagaraStackFunctionInputValue::CollectAllActions(FGraphActionListBuilderB FunctionInput->GetAvailableDynamicInputs(DynamicInputScripts, bLibraryOnly == false); for (UNiagaraScript* DynamicInputScript : DynamicInputScripts) { - const FText DynamicInputText = FNiagaraEditorUtilities::FormatScriptName(DynamicInputScript->GetFName(), DynamicInputScript->bExposeToLibrary); - const FText Tooltip = FNiagaraEditorUtilities::FormatScriptDescription(DynamicInputScript->Description, *DynamicInputScript->GetPathName(), DynamicInputScript->bExposeToLibrary); + bool bIsInLibrary = DynamicInputScript->LibraryVisibility == ENiagaraScriptLibraryVisibility::Library; + const FText DynamicInputText = FNiagaraEditorUtilities::FormatScriptName(DynamicInputScript->GetFName(), bIsInLibrary); + const FText Tooltip = FNiagaraEditorUtilities::FormatScriptDescription(DynamicInputScript->Description, *DynamicInputScript->GetPathName(), bIsInLibrary); TSharedPtr DynamicInputAction(new FNiagaraMenuAction(CategoryName, DynamicInputText, Tooltip, 0, DynamicInputScript->Keywords, FNiagaraMenuAction::FOnExecuteStackAction::CreateSP(this, &SNiagaraStackFunctionInputValue::DynamicInputScriptSelected, DynamicInputScript))); @@ -860,8 +868,9 @@ void SNiagaraStackFunctionInputValue::CollectDynamicInputActionsForReassign(FGra FunctionInput->GetAvailableDynamicInputs(DynamicInputScripts, bLibraryOnly == false); for (UNiagaraScript* DynamicInputScript : DynamicInputScripts) { - const FText DynamicInputText = FNiagaraEditorUtilities::FormatScriptName(DynamicInputScript->GetFName(), DynamicInputScript->bExposeToLibrary); - const FText Tooltip = FNiagaraEditorUtilities::FormatScriptDescription(DynamicInputScript->Description, *DynamicInputScript->GetPathName(), DynamicInputScript->bExposeToLibrary); + bool bIsInLibrary = DynamicInputScript->LibraryVisibility == ENiagaraScriptLibraryVisibility::Library; + const FText DynamicInputText = FNiagaraEditorUtilities::FormatScriptName(DynamicInputScript->GetFName(), bIsInLibrary); + const FText Tooltip = FNiagaraEditorUtilities::FormatScriptDescription(DynamicInputScript->Description, *DynamicInputScript->GetPathName(), bIsInLibrary); TSharedPtr DynamicInputAction(new FNiagaraMenuAction(CategoryName, DynamicInputText, Tooltip, 0, DynamicInputScript->Keywords, FNiagaraMenuAction::FOnExecuteStackAction::CreateStatic(&ReassignDynamicInputScript, FunctionInput, DynamicInputScript))); DynamicInputActions.AddAction(DynamicInputAction); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackItemGroup.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackItemGroup.cpp index 3974dc66634f..40160d25bfed 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackItemGroup.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackItemGroup.cpp @@ -8,7 +8,8 @@ #include "ViewModels/Stack/NiagaraStackItemGroup.h" #include "ViewModels/Stack/NiagaraStackViewModel.h" #include "Widgets/SBoxPanel.h" - #include "Widgets/Input/SButton.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" #include "Widgets/Text/STextBlock.h" #define LOCTEXT_NAMESPACE "NiagaraStackItemGroup" @@ -20,44 +21,64 @@ void SNiagaraStackItemGroup::Construct(const FArguments& InArgs, UNiagaraStackIt StackEntryItem = Group; StackViewModel = InStackViewModel; + TSharedRef RowBox = SNew(SHorizontalBox); + + // Name + RowBox->AddSlot() + .VAlign(VAlign_Center) + .Padding(2, 0, 0, 0) + [ + SNew(SNiagaraStackDisplayName, InGroup, *InStackViewModel) + .NameStyle(FNiagaraEditorWidgetsStyle::Get(), "NiagaraEditor.Stack.GroupText") + ]; + + // Delete group button + RowBox->AddSlot() + .AutoWidth() + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") + .IsFocusable(false) + .ForegroundColor(FNiagaraEditorWidgetsStyle::Get().GetColor("NiagaraEditor.Stack.ForegroundColor")) + .ToolTipText(this, &SNiagaraStackItemGroup::GetDeleteButtonToolTip) + .OnClicked(this, &SNiagaraStackItemGroup::DeleteClicked) + .IsEnabled(this, &SNiagaraStackItemGroup::GetDeleteButtonIsEnabled) + .Visibility(this, &SNiagaraStackItemGroup::GetDeleteButtonVisibility) + .Content() + [ + SNew(STextBlock) + .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.10")) + .Text(FText::FromString(FString(TEXT("\xf1f8")))) + ] + ]; + + // Enabled button + if (Group->SupportsChangeEnabled()) + { + RowBox->AddSlot() + .VAlign(VAlign_Center) + .AutoWidth() + .Padding(0, 0, 0, 0) + [ + SNew(SCheckBox) + .IsChecked(this, &SNiagaraStackItemGroup::CheckEnabledStatus) + .OnCheckStateChanged(this, &SNiagaraStackItemGroup::OnCheckStateChanged) + .IsEnabled(this, &SNiagaraStackItemGroup::GetEnabledCheckBoxEnabled) + ]; + } + + // Add button + RowBox->AddSlot() + .AutoWidth() + .HAlign(HAlign_Right) + .Padding(2, 0, 0, 0) + [ + ConstructAddButton() + ]; + ChildSlot [ - SNew(SHorizontalBox) - // Name - + SHorizontalBox::Slot() - .VAlign(VAlign_Center) - .Padding(2, 0, 0, 0) - [ - SNew(SNiagaraStackDisplayName, InGroup, *InStackViewModel) - .NameStyle(FNiagaraEditorWidgetsStyle::Get(), "NiagaraEditor.Stack.GroupText") - ] - // Delete group button - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") - .IsFocusable(false) - .ForegroundColor(FNiagaraEditorWidgetsStyle::Get().GetColor("NiagaraEditor.Stack.ForegroundColor")) - .ToolTipText(this, &SNiagaraStackItemGroup::GetDeleteButtonToolTip) - .OnClicked(this, &SNiagaraStackItemGroup::DeleteClicked) - .IsEnabled(this, &SNiagaraStackItemGroup::GetDeleteButtonIsEnabled) - .Visibility(this, &SNiagaraStackItemGroup::GetDeleteButtonVisibility) - .Content() - [ - SNew(STextBlock) - .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.10")) - .Text(FText::FromString(FString(TEXT("\xf1f8")))) - ] - ] - // Add button - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - .Padding(2, 0, 0, 0) - [ - ConstructAddButton() - ] + RowBox ]; } @@ -95,4 +116,19 @@ FReply SNiagaraStackItemGroup::DeleteClicked() return FReply::Handled(); } -#undef LOCTEXT_NAMESPACE \ No newline at end of file +void SNiagaraStackItemGroup::OnCheckStateChanged(ECheckBoxState InCheckState) +{ + Group->SetIsEnabled(InCheckState == ECheckBoxState::Checked); +} + +ECheckBoxState SNiagaraStackItemGroup::CheckEnabledStatus() const +{ + return Group->GetIsEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +bool SNiagaraStackItemGroup::GetEnabledCheckBoxEnabled() const +{ + return Group->GetOwnerIsEnabled(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackItemGroup.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackItemGroup.h index 917a31ad1491..11d83d359d03 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackItemGroup.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackItemGroup.h @@ -27,6 +27,10 @@ private: FReply DeleteClicked(); + void OnCheckStateChanged(ECheckBoxState InCheckState); + ECheckBoxState CheckEnabledStatus() const; + bool GetEnabledCheckBoxEnabled() const; + private: UNiagaraStackItemGroup* Group; }; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackModuleItem.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackModuleItem.cpp index 816e959b8af6..833781b7d55f 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackModuleItem.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackModuleItem.cpp @@ -249,30 +249,16 @@ FReply SNiagaraStackModuleItem::RefreshClicked() FReply SNiagaraStackModuleItem::OnModuleItemDrop(TSharedPtr DragDropOperation) { - if (DragDropOperation->IsOfType()) - { - TSharedPtr InputDragDropOperation = StaticCastSharedPtr(DragDropOperation); - TSharedPtr Action = StaticCastSharedPtr(InputDragDropOperation->GetSourceAction()); - if (Action.IsValid() && ModuleItem->CanAddInput(Action->GetParameter())) - { - ModuleItem->AddInput(Action->GetParameter()); - return FReply::Handled(); - } - } - - return FReply::Unhandled(); + UNiagaraStackEntry::FDropRequest DropRequest(DragDropOperation.ToSharedRef(), EItemDropZone::OntoItem, UNiagaraStackEntry::EDragOptions::None, UNiagaraStackEntry::EDropOptions::None); + TOptional DropResponse = ModuleItem->Drop(DropRequest); + return DropResponse.IsSet() && DropResponse->DropZone == EItemDropZone::OntoItem ? FReply::Handled() : FReply::Unhandled(); } bool SNiagaraStackModuleItem::OnModuleItemAllowDrop(TSharedPtr DragDropOperation) { - if (DragDropOperation->IsOfType()) - { - TSharedPtr InputDragDropOperation = StaticCastSharedPtr(DragDropOperation); - TSharedPtr Action = StaticCastSharedPtr(InputDragDropOperation->GetSourceAction()); - return Action.IsValid() && ModuleItem->CanAddInput(Action->GetParameter()); - } - - return false; + UNiagaraStackEntry::FDropRequest AllowDropRequest(DragDropOperation.ToSharedRef(), EItemDropZone::OntoItem, UNiagaraStackEntry::EDragOptions::None, UNiagaraStackEntry::EDropOptions::None); + TOptional AllowDropResponse = ModuleItem->CanDrop(AllowDropRequest); + return AllowDropResponse.IsSet() && AllowDropResponse->DropZone == EItemDropZone::OntoItem; } void ReassignModuleScript(UNiagaraStackModuleItem* ModuleItem, FAssetData NewModuleScriptAsset) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackParameterStoreEntryValue.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackParameterStoreEntryValue.cpp index 91bd2d83a029..390c668211be 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackParameterStoreEntryValue.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackParameterStoreEntryValue.cpp @@ -119,10 +119,10 @@ FReply SNiagaraStackParameterStoreEntryValue::DeleteClicked() return FReply::Handled(); } -void SNiagaraStackParameterStoreEntryValue::OnAssetSelectedFromPicker(const FAssetData& InAssetData) +void SNiagaraStackParameterStoreEntryValue::OnAssetSelectedFromPicker(const FAssetData& InAssetData, UClass* InClass) { - UMaterialInterface* Mat = Cast(InAssetData.GetAsset()); - StackEntry->ReplaceValueObject(Mat); + if ( !InAssetData.GetAsset() || (InAssetData.GetAsset() && InAssetData.GetAsset()->IsA(InClass))) + StackEntry->ReplaceValueObject(InAssetData.GetAsset()); } FString SNiagaraStackParameterStoreEntryValue::GetCurrentAssetPath() const @@ -186,7 +186,23 @@ TSharedRef SNiagaraStackParameterStoreEntryValue::ConstructValueStructW return SNew(SObjectPropertyEntryBox) .ObjectPath_Raw(this, &SNiagaraStackParameterStoreEntryValue::GetCurrentAssetPath) .AllowedClass(UMaterialInterface::StaticClass()) - .OnObjectChanged_Raw(this, &SNiagaraStackParameterStoreEntryValue::OnAssetSelectedFromPicker) + .OnObjectChanged_Raw(this, &SNiagaraStackParameterStoreEntryValue::OnAssetSelectedFromPicker, UMaterialInterface::StaticClass()) + .AllowClear(false) + .DisplayUseSelected(true) + .DisplayBrowse(true) + .DisplayThumbnail(true) + .NewAssetFactories(TArray()); + + } + else if (StackEntry->GetInputType().GetClass()->IsChildOf(UTexture::StaticClass())) + { + TArray AllowedClasses; + AllowedClasses.Add(UTexture::StaticClass()); + + return SNew(SObjectPropertyEntryBox) + .ObjectPath_Raw(this, &SNiagaraStackParameterStoreEntryValue::GetCurrentAssetPath) + .AllowedClass(UTexture::StaticClass()) + .OnObjectChanged_Raw(this, &SNiagaraStackParameterStoreEntryValue::OnAssetSelectedFromPicker, UTexture::StaticClass()) .AllowClear(false) .DisplayUseSelected(true) .DisplayBrowse(true) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackParameterStoreEntryValue.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackParameterStoreEntryValue.h index f1e443d85b18..ead87e34a472 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackParameterStoreEntryValue.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackParameterStoreEntryValue.h @@ -49,7 +49,7 @@ private: FSlateColor GetInputIconColor() const; - void OnAssetSelectedFromPicker(const FAssetData& InAssetData); + void OnAssetSelectedFromPicker(const FAssetData& InAssetData, UClass* InClass); FString GetCurrentAssetPath() const; private: diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraScriptBase.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraScriptBase.cpp new file mode 100644 index 000000000000..1410afce5984 --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraScriptBase.cpp @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "NiagaraScriptBase.h" + +UNiagaraScriptBase::UNiagaraScriptBase(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShader.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShader.cpp index 24c29046598e..8108b35c66c3 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShader.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShader.cpp @@ -28,7 +28,6 @@ IMPLEMENT_SHADER_TYPE(, FNiagaraShader, TEXT("/Plugin/FX/Niagara/Private/NiagaraEmitterInstanceShader.usf"),TEXT("SimulateMain"), SF_Compute) - int32 GCreateNiagaraShadersOnLoad = 0; static FAutoConsoleVariableRef CVarCreateNiagaraShadersOnLoad( TEXT("niagara.CreateShadersOnLoad"), @@ -138,72 +137,6 @@ void FNiagaraShaderMapPointerTable::LoadFromArchive(FArchive& Ar, void* FrozenCo } } -#if 0 -void FNiagaraShaderMapId::Serialize(FArchive& Ar) -{ - // Handle niagara custom version and the compiler version id. - int32 NiagaraVer; - if (Ar.IsLoading()) - { - FGuid FirstGuid; - Ar << FirstGuid; - if (FirstGuid == FNiagaraCustomVersion::GUID) - { - // If the first guid matches the niagara custom version identifier, than this was serialized with versioning so read the current version and then read the compiler version which was next. - Ar << NiagaraVer; - Ar << CompilerVersionID; - } - else - { - // If the first guid was not the niagara custom version identifier it was saved before versioning so the first guid was the compiler version. - NiagaraVer = FNiagaraCustomVersion::UseHashesToIdentifyCompileStateOfTopLevelScripts - 1; - CompilerVersionID = FirstGuid; - } - } - else // Saving - { - // When saving, the custom version is the latest version so serialize the niagara custom version identifier, the current version, and the compiler version. - NiagaraVer = FNiagaraCustomVersion::LatestVersion; - FGuid NiagaraCustomVersionGuid = FNiagaraCustomVersion::GUID; - Ar << NiagaraCustomVersionGuid; - Ar << NiagaraVer; - Ar << CompilerVersionID; - } - - Ar << BaseScriptID_DEPRECATED; - Ar << (int32&)FeatureLevel; - - if (Ar.IsLoading() && NiagaraVer < FNiagaraCustomVersion::RemoveGraphUsageCompileIds) - { - // These values are no longer used, but need to be loaded from old files to keep the archive valid. - TArray ReferencedDependencyIds; - Ar << ReferencedDependencyIds; - } - - if (NiagaraVer >= FNiagaraCustomVersion::UseHashesToIdentifyCompileStateOfTopLevelScripts) - { - Ar << BaseCompileHash; - Ar << ReferencedCompileHashes; - } - - if (NiagaraVer >= FNiagaraCustomVersion::AddAdditionalDefinesProperty) - { - Ar << AdditionalDefines; - } - - if (NiagaraVer >= FNiagaraCustomVersion::AddRIAndDetailLevel) - { - if (NiagaraVer < FNiagaraCustomVersion::PlatformScalingRefactor) - { - int32 Dummy; - Ar << Dummy; - } - Ar << bUsesRapidIterationParams; - } - - //ParameterSet.Serialize(Ar); // NIAGARATODO: at some point we'll need stuff for static switches here -} -#endif /** Hashes the script-specific part of this shader map Id. */ void FNiagaraShaderMapId::GetScriptHash(FSHAHash& OutHash) const @@ -244,6 +177,7 @@ bool FNiagaraShaderMapId::operator==(const FNiagaraShaderMapId& ReferenceSet) co || FeatureLevel != ReferenceSet.FeatureLevel || CompilerVersionID != ReferenceSet.CompilerVersionID || bUsesRapidIterationParams != ReferenceSet.bUsesRapidIterationParams + || bUseShaderPermutations != ReferenceSet.bUseShaderPermutations || LayoutParams != ReferenceSet.LayoutParams) { return false; @@ -347,6 +281,15 @@ void FNiagaraShaderMapId::AppendKeyString(FString& KeyString) const KeyString += TEXT("NORI_"); } + if (bUseShaderPermutations) + { + KeyString += TEXT("USEPERM_"); + } + else + { + KeyString += TEXT("NOPERM_"); + } + // Add additional defines for (int32 DefinesIndex = 0; DefinesIndex < AdditionalDefines.Num(); DefinesIndex++) { @@ -391,6 +334,7 @@ TMap > FNiag */ TSharedRef FNiagaraShaderType::BeginCompileShader( uint32 ShaderMapId, + int32 PermutationId, const FNiagaraShaderScript* Script, FShaderCompilerEnvironment* CompilationEnvironment, EShaderPlatform Platform, @@ -399,7 +343,7 @@ TSharedRef FNiagaraShaderType::Beg TArray& InDIParamInfo ) { - FShaderCompileJob* NewJob = new FShaderCompileJob(ShaderMapId, nullptr, this, /* PermutationId = */ 0); + FShaderCompileJob* NewJob = new FShaderCompileJob(ShaderMapId, nullptr, this, PermutationId); //NewJob->DIParamInfo = InDIParamInfo; // from hlsl translation and need to be passed to the FNiagaraShader on completion @@ -412,8 +356,19 @@ TSharedRef FNiagaraShaderType::Beg NewJob->Input.Environment.SetDefine(TEXT("NIAGARA_MAX_GPU_SPAWN_INFOS"), NIAGARA_MAX_GPU_SPAWN_INFOS); NewJob->Input.Environment.SetDefine(TEXT("DISKELMESH_BONE_INFLUENCES"), 0); NewJob->Input.Environment.IncludeVirtualPathToContentsMap.Add(TEXT("/Engine/Generated/NiagaraEmitterInstance.ush"), Script->HlslOutput); + NewJob->Input.Environment.SetDefine(TEXT("SHADER_STAGE_PERMUTATION"), PermutationId); + + if (Script->GetUseShaderPermutations()) + { + NewJob->Input.Environment.SetDefine(TEXT("NIAGARA_SHADER_PERMUTATIONS"), 1); + NewJob->Input.Environment.SetDefine(TEXT("DefaultSimulationStageIndex"), 0); + NewJob->Input.Environment.SetDefine(TEXT("SimulationStageIndex"), Script->PermutationIdToShaderStageIndex(PermutationId)); + } + else + { + NewJob->Input.Environment.SetDefine(TEXT("NIAGARA_SHADER_PERMUTATIONS"), 0); + } - NewJob->Input.Environment.SetDefine(TEXT("USE_SIMULATION_STAGES"), Script->GetUseSimStagesDefine()); AddReferencedUniformBufferIncludes(NewJob->Input.Environment, NewJob->Input.SourceFilePrefix, (EShaderPlatform)Target.Platform); @@ -520,15 +475,13 @@ FShader* FNiagaraShaderType::FinishCompileShader( { check(CurrentJob.bSucceeded); - const int32 PermutationId = 0; - //CurrentJob.Id TArray DIParamInfo; if (!ExtraParamInfo.RemoveAndCopyValue(&CurrentJob, DIParamInfo)) { check(false); } - FShader* Shader = ConstructCompiled(FNiagaraShaderType::CompiledShaderInitializerType(this, PermutationId, CurrentJob.Output, ShaderMapHash, InDebugDescription, DIParamInfo)); + FShader* Shader = ConstructCompiled(FNiagaraShaderType::CompiledShaderInitializerType(this, CurrentJob.PermutationId, CurrentJob.Output, ShaderMapHash, InDebugDescription, DIParamInfo)); //CurrentJob.Output.ParameterMap.VerifyBindingsAreComplete(GetName(), CurrentJob.Output.Target, nullptr); // b/c we don't bind data interfaces yet... return Shader; @@ -703,7 +656,7 @@ void FNiagaraShaderMap::Compile( { // Make sure we are operating on a referenced shader map or the below Find will cause this shader map to be deleted, // Since it creates a temporary ref counted pointer. - check(GetNumRefs() > 0); + check(NumRefs > 0); //All access to NiagaraShaderMapsBeingCompiled must be done on the game thread! check(IsInGameThread()); @@ -751,22 +704,25 @@ void FNiagaraShaderMap::Compile( // Compile this niagara shader . TArray ShaderErrors; - // Only compile the shader if we don't already have it - if (!NewContent->HasShader(ShaderType, /* PermutationId = */ 0)) + for (int32 PermutationId = 0; PermutationId < Script->GetNumPermutations(); ++PermutationId) { - TSharedRef Job = ShaderType->BeginCompileShader( - CompilingId, - Script, - CompilationEnvironment, - InPlatform, - NewJobs, - FShaderTarget(ShaderType->GetFrequency(), InPlatform), - Script->GetDataInterfaceParamInfo() + // Only compile the shader if we don't already have it + if (!NewContent->HasShader(ShaderType, PermutationId)) + { + TSharedRef Job = ShaderType->BeginCompileShader( + CompilingId, + PermutationId, + Script, + CompilationEnvironment, + InPlatform, + NewJobs, + FShaderTarget(ShaderType->GetFrequency(), InPlatform), + Script->GetDataInterfaceParamInfo() ); - check(!SharedShaderJobs.Find(ShaderType)); - SharedShaderJobs.Add(ShaderType, Job); + SharedShaderJobs.Add(ShaderType, Job); + } + NumShaders++; } - NumShaders++; } else if (ShaderType) { @@ -826,8 +782,8 @@ FShader* FNiagaraShaderMap::ProcessCompilationResultsForSingleJob(TSharedRefGetCodeSize() > 0); - check(!GetContent()->HasShader(NiagaraShaderType, /* PermutationId = */ 0)); - return GetMutableContent()->FindOrAddShader(NiagaraShaderType->GetHashedName(), 0, Shader); + check(!GetContent()->HasShader(NiagaraShaderType, CurrentJob->PermutationId)); + return GetMutableContent()->FindOrAddShader(NiagaraShaderType->GetHashedName(), CurrentJob->PermutationId, Shader); } bool FNiagaraShaderMap::ProcessCompilationResults(const TArray>& InCompilationResults, int32& InOutJobIndex, float& TimeBudget) @@ -868,7 +824,7 @@ bool FNiagaraShaderMap::ProcessCompilationResults(const TArray 0); + check(NumRefs > 0); //All access to NiagaraShaderMapsBeingCompiled must be done on the game thread! check(IsInGameThread()); TArray* CorrespondingScripts = FNiagaraShaderMap::NiagaraShaderMapsBeingCompiled.Find(this); @@ -891,13 +847,19 @@ bool FNiagaraShaderMap::TryToAddToExistingCompilationTask(FNiagaraShaderScript* bool FNiagaraShaderMap::IsNiagaraShaderComplete(const FNiagaraShaderScript* Script, const FNiagaraShaderType* ShaderType, bool bSilent) { // If we should cache this script, it's incomplete if the shader is missing - if (ShouldCacheNiagaraShader(ShaderType, GetShaderPlatform(), Script) && !GetContent()->HasShader((FShaderType*)ShaderType, /* PermutationId = */ 0)) + if (ShouldCacheNiagaraShader(ShaderType, GetShaderPlatform(), Script)) { - if (!bSilent) + for (int32 PermutationId = 0; PermutationId < Script->GetNumPermutations(); ++PermutationId) { - UE_LOG(LogShaders, Warning, TEXT("Incomplete shader %s, missing FNiagaraShader %s."), *Script->GetFriendlyName(), ShaderType->GetName()); + if (!GetContent()->HasShader((FShaderType*)ShaderType, PermutationId)) + { + if (!bSilent) + { + UE_LOG(LogShaders, Warning, TEXT("Incomplete shader %s, missing FNiagaraShader %s."), *Script->GetFriendlyName(), ShaderType->GetName()); + } + return false; + } } - return false; } return true; @@ -908,7 +870,7 @@ bool FNiagaraShaderMap::IsComplete(const FNiagaraShaderScript* Script, bool bSil check(!GIsThreadedRendering || !IsInRenderingThread()); // Make sure we are operating on a referenced shader map or the below Find will cause this shader map to be deleted, // Since it creates a temporary ref counted pointer. - check(GetNumRefs() > 0); + check(NumRefs > 0); //All access to NiagaraShaderMapsBeingCompiled must be done on the game thread! check(IsInGameThread()); const TArray* CorrespondingScripts = FNiagaraShaderMap::NiagaraShaderMapsBeingCompiled.Find(this); @@ -999,21 +961,32 @@ void FNiagaraShaderMap::Register(EShaderPlatform InShaderPlatform) } } +void FNiagaraShaderMap::AddRef() +{ + FScopeLock ScopeLock(&GIdToNiagaraShaderMapCS); + check(!bDeletedThroughDeferredCleanup); + ++NumRefs; +} -void FNiagaraShaderMap::OnReleased() +void FNiagaraShaderMap::Release() { { FScopeLock ScopeLock(&GIdToNiagaraShaderMapCS); - if (bRegistered) + + check(NumRefs > 0); + if (--NumRefs == 0) { - DEC_DWORD_STAT(STAT_Shaders_NumShaderMaps); + if (bRegistered) + { + DEC_DWORD_STAT(STAT_Shaders_NumShaderMaps); - GIdToNiagaraShaderMap[GetShaderPlatform()].Remove(GetContent()->ShaderMapId); - bRegistered = false; + GIdToNiagaraShaderMap[GetShaderPlatform()].Remove(GetContent()->ShaderMapId); + bRegistered = false; + } + + check(!bDeletedThroughDeferredCleanup); + bDeletedThroughDeferredCleanup = true; } - - check(!bDeletedThroughDeferredCleanup); - bDeletedThroughDeferredCleanup = true; } if (bDeletedThroughDeferredCleanup) { @@ -1023,6 +996,7 @@ void FNiagaraShaderMap::OnReleased() FNiagaraShaderMap::FNiagaraShaderMap() : CompilingId(1), + NumRefs(0), bDeletedThroughDeferredCleanup(false), bRegistered(false), bCompilationFinalized(true), @@ -1049,12 +1023,13 @@ void FNiagaraShaderMap::FlushShadersByShaderType(const FShaderType* ShaderType) { if (ShaderType->GetNiagaraShaderType()) { - GetMutableContent()->RemoveShaderTypePermutaion(ShaderType->GetNiagaraShaderType(), /* PermutationId = */ 0); + for (int32 PermutationId = 0; PermutationId < ShaderType->GetPermutationCount(); ++PermutationId) + { + GetMutableContent()->RemoveShaderTypePermutaion(ShaderType->GetNiagaraShaderType(), PermutationId); + } } } - - bool FNiagaraShaderMap::Serialize(FArchive& Ar, bool bInlineShaderResources, bool bLoadedByCookedMaterial) { // Note: This is saved to the DDC, not into packages (except when cooked) @@ -1075,7 +1050,6 @@ bool FNiagaraShaderMap::RemovePendingScript(FNiagaraShaderScript* Script) if (Result) { Script->RemoveOutstandingCompileId(It.Key()->CompilingId); - Script->NotifyCompilationFinished(); // Can't call NotifyCompilationFinished() when post-loading. // This normally happens when compiled in-sync for which the callback is not required. if (!FUObjectThreadContext::Get().IsRoutingPostLoad) @@ -1179,7 +1153,9 @@ void FNiagaraShader::BindParams(const TArray& UpdateStartInstanceParam.Bind(ParameterMap, TEXT("UpdateStartInstance")); DefaultSimulationStageIndexParam.Bind(ParameterMap, TEXT("DefaultSimulationStageIndex")); SimulationStageIndexParam.Bind(ParameterMap, TEXT("SimulationStageIndex")); - IterationInterfaceCount.Bind(ParameterMap, TEXT("IterationInterfaceCount")); + + SimulationStageIterationInfoParam.Bind(ParameterMap, TEXT("SimulationStageIterationInfo")); + SimulationStageNormalizedIterationIndexParam.Bind(ParameterMap, TEXT("SimulationStageNormalizedIterationIndex")); ComponentBufferSizeReadParam.Bind(ParameterMap, TEXT("ComponentBufferSizeRead")); ComponentBufferSizeWriteParam.Bind(ParameterMap, TEXT("ComponentBufferSizeWrite")); @@ -1228,14 +1204,8 @@ void FNiagaraShader::BindParams(const TArray& FNiagaraDataInterfaceParamRef& ParamRef = DataInterfaceParameters.AddDefaulted_GetRef(); ParamRef.Bind(DIParamInfo, ParameterMap); } - - ensure(HalfOutputBufferParam.IsBound() || FloatOutputBufferParam.IsBound() || IntOutputBufferParam.IsBound()); // we should have at least one output buffer we're writing to - ensure(InstanceCountsParam.IsBound()); - ensure(UpdateStartInstanceParam.IsBound()); - ensure(NumSpawnedInstancesParam.IsBound()); } - ////////////////////////////////////////////////////////////////////////// FNiagaraDataInterfaceParamRef::FNiagaraDataInterfaceParamRef() { diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderCompilationManager.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderCompilationManager.cpp index 8b3a12b693c4..7bef6aab0a10 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderCompilationManager.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderCompilationManager.cpp @@ -63,19 +63,6 @@ void FNiagaraShaderCompilationManager::ProcessAsyncResults() { check(IsInGameThread()); - TArray FinalizedShaderMapIDs; - for (int32 JobIndex = JobQueue.Num() - 1; JobIndex >= 0; JobIndex--) - { - TSharedRef Job = StaticCastSharedRef(JobQueue[JobIndex]); - if (Job->bFinalized) - { - FinalizedShaderMapIDs.Add(Job->Id); - } - } - - // We do this because the finalization flag is set by another thread, so the manager might not have had a chance to fully process the result. - GShaderCompilingManager->FinishCompilation(NULL, FinalizedShaderMapIDs); - // Process the results from the shader compile worker for (int32 JobIndex = JobQueue.Num() - 1; JobIndex >= 0; JobIndex--) { diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderDerivedDataVersion.h b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderDerivedDataVersion.h index 54806777d54a..6477c6bb833f 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderDerivedDataVersion.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderDerivedDataVersion.h @@ -11,4 +11,4 @@ NiagaraShaderDerivedDataVersion.h: Shader derived data version for Niagara. // In case of merge conflicts with DDC versions, you MUST generate a new GUID and set this new // guid as version -#define NIAGARASHADERMAP_DERIVEDDATA_VER TEXT("25DAC13A-34B5-4AE0-AC39-A7A6D551D015") +#define NIAGARASHADERMAP_DERIVEDDATA_VER TEXT("32A25C36-2BBE-4B57-98F8-ECB9278AAB3D") diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShared.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShared.cpp index 244ecae14026..7890a5f3b5b2 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShared.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShared.cpp @@ -8,7 +8,8 @@ #include "NiagaraShaderModule.h" #include "NiagaraShaderType.h" #include "NiagaraShader.h" -#include "NiagaraScript.h" +#include "NiagaraScriptBase.h" +#include "NiagaraScript.h" //-TODO: This should be fixed so we are not reading structures from modules we do not depend on #include "Stats/StatsMisc.h" #include "UObject/CoreObjectVersion.h" #include "Misc/App.h" @@ -72,9 +73,10 @@ NIAGARASHADER_API uint32 FNiagaraShaderScript::GetUseSimStagesDefine() const } } - NIAGARASHADER_API void FNiagaraShaderScript::NotifyCompilationFinished() { + UpdateCachedData_PostCompile(); + OnCompilationCompleteDelegate.Broadcast(); } @@ -143,10 +145,56 @@ bool FNiagaraShaderScript::IsSame(const FNiagaraShaderMapId& InId) const InId.FeatureLevel == FeatureLevel &&/* InId.BaseScriptID == BaseScriptId &&*/ InId.bUsesRapidIterationParams == bUsesRapidIterationParams && + InId.bUseShaderPermutations == bUseShaderPermutations && InId.BaseCompileHash == BaseCompileHash && InId.CompilerVersionID == CompilerVersionId; } +int32 FNiagaraShaderScript::PermutationIdToShaderStageIndex(int32 PermutationId) const +{ + return bUseShaderPermutations ? ShaderStageToPermutation[PermutationId].Key : 0; +} + +bool FNiagaraShaderScript::IsShaderMapComplete() const +{ + if (GameThreadShaderMap == nullptr) + { + return false; + } + + if (FNiagaraShaderMap::GetShaderMapBeingCompiled(this) != nullptr) + { + return false; + } + + for (int i=0; i < GetNumPermutations(); ++i) + { + if (GameThreadShaderMap->GetShader(i).IsNull()) + { + return false; + } + } + return true; +} + +int32 FNiagaraShaderScript::ShaderStageIndexToPermutationId_RenderThread(int32 ShaderStageIndex) const +{ + check(IsInRenderingThread()); + if (CachedData_RenderThread.NumPermutations > 1) + { + for (int32 i = 0; i < CachedData_RenderThread.ShaderStageToPermutation.Num(); ++i) + { + const TPair MinMaxStage = CachedData_RenderThread.ShaderStageToPermutation[i]; + if ((ShaderStageIndex >= MinMaxStage.Key) && (ShaderStageIndex < MinMaxStage.Value)) + { + return i; + } + } + UE_LOG(LogShaders, Fatal, TEXT("FNiagaraShaderScript::ShaderStageIndexToPermutationId_RenderThread: Failed to map from simulation stage(%d) to permutation id."), ShaderStageIndex); + } + + return 0; +} void FNiagaraShaderScript::GetDependentShaderTypes(EShaderPlatform Platform, TArray& OutShaderTypes) const { @@ -178,6 +226,7 @@ NIAGARASHADER_API void FNiagaraShaderScript::GetShaderMapId(EShaderPlatform Plat OutId.FeatureLevel = GetFeatureLevel();/* OutId.BaseScriptID = BaseScriptId;*/ OutId.bUsesRapidIterationParams = bUsesRapidIterationParams; + OutId.bUseShaderPermutations = bUseShaderPermutations; BaseCompileHash.ToSHAHash(OutId.BaseCompileHash); OutId.CompilerVersionID = FNiagaraCustomVersion::LatestScriptCompileVersion; @@ -228,12 +277,17 @@ void FNiagaraShaderScript::ReleaseShaderMap() { GameThreadShaderMap = nullptr; - FNiagaraShaderScript* Script = this; - ENQUEUE_RENDER_COMMAND(ReleaseShaderMap)( - [Script](FRHICommandListImmediate& RHICmdList) - { - Script->SetRenderingThreadShaderMap(nullptr); - }); + if (!bQueuedForRelease) + { + FNiagaraShaderScript* Script = this; + ENQUEUE_RENDER_COMMAND(ReleaseShaderMap)( + [Script](FRHICommandListImmediate& RHICmdList) + { + Script->SetRenderingThreadShaderMap(nullptr); + }); + } + + UpdateCachedData_All(); } } @@ -241,6 +295,8 @@ void FNiagaraShaderScript::SerializeShaderMap(FArchive& Ar) { bool bCooked = Ar.IsCooking(); Ar << bCooked; + Ar << NumPermutations; + Ar << ShaderStageToPermutation; if (Ar.IsLoading()) { @@ -294,6 +350,8 @@ void FNiagaraShaderScript::SerializeShaderMap(FArchive& Ar) if (FApp::CanEverRender() && bLoaded) { GameThreadShaderMap = RenderingThreadShaderMap = LoadedShaderMap; + + UpdateCachedData_PostCompile(true); } else { @@ -304,9 +362,9 @@ void FNiagaraShaderScript::SerializeShaderMap(FArchive& Ar) } } -void FNiagaraShaderScript::SetScript(UNiagaraScript* InScript, ERHIFeatureLevel::Type InFeatureLevel, EShaderPlatform InShaderPlatform, const FGuid& InCompilerVersionID, const TArray& InAdditionalDefines, +void FNiagaraShaderScript::SetScript(UNiagaraScriptBase* InScript, ERHIFeatureLevel::Type InFeatureLevel, EShaderPlatform InShaderPlatform, const FGuid& InCompilerVersionID, const TArray& InAdditionalDefines, const FNiagaraCompileHash& InBaseCompileHash, const TArray& InReferencedCompileHashes, - bool bInUsesRapidIterationParams, FString InFriendlyName) + bool bInUsesRapidIterationParams, bool bInUseShaderPermutations, FString InFriendlyName) { checkf(InBaseCompileHash.IsValid(), TEXT("Invalid base compile hash. Script caching will fail.")) BaseVMScript = InScript; @@ -314,11 +372,20 @@ void FNiagaraShaderScript::SetScript(UNiagaraScript* InScript, ERHIFeatureLevel: //BaseScriptId = InBaseScriptID; AdditionalDefines = InAdditionalDefines; bUsesRapidIterationParams = bInUsesRapidIterationParams; + bUseShaderPermutations = bInUseShaderPermutations; BaseCompileHash = InBaseCompileHash; ReferencedCompileHashes = InReferencedCompileHashes; FriendlyName = InFriendlyName; SetFeatureLevel(InFeatureLevel); ShaderPlatform = InShaderPlatform; + + // We don't support old shader stages with permutations + if (AdditionalDefines.Contains(TEXT("Emitter.UseOldShaderStages"))) + { + bUseShaderPermutations = false; + } + + UpdateCachedData_All(); } #if WITH_EDITOR @@ -327,6 +394,7 @@ bool FNiagaraShaderScript::MatchesScript(ERHIFeatureLevel::Type InFeatureLevel, return CompilerVersionId == ScriptId.CompilerVersionID && AdditionalDefines == ScriptId.AdditionalDefines && bUsesRapidIterationParams == ScriptId.bUsesRapidIterationParams + && bUseShaderPermutations == ScriptId.bUseShaderPermutations && BaseCompileHash == ScriptId.BaseScriptCompileHash && ReferencedCompileHashes == ScriptId.ReferencedCompileHashes && FeatureLevel == InFeatureLevel @@ -351,6 +419,119 @@ NIAGARASHADER_API bool FNiagaraShaderScript::IsCompilationFinished() const return bRet; } +void FNiagaraShaderScript::SetRenderThreadCachedData(const FNiagaraShaderMapCachedData& CachedData) +{ + CachedData_RenderThread = CachedData; +} + +void FNiagaraShaderScript::QueueForRelease(FThreadSafeBool& Fence) +{ + check(!bQueuedForRelease); + + bQueuedForRelease = true; + Fence = false; + FThreadSafeBool* Released = &Fence; + + ENQUEUE_RENDER_COMMAND(BeginDestroyCommand)( + [Released](FRHICommandListImmediate& RHICmdList) + { + *Released = true; + }); +} + +void FNiagaraShaderScript::UpdateCachedData_All() +{ + UpdateCachedData_PreCompile(); + UpdateCachedData_PostCompile(); +} + +void FNiagaraShaderScript::UpdateCachedData_PreCompile() +{ + if (BaseVMScript) + { + NumPermutations = 1; + ShaderStageToPermutation.Empty(); + + if (bUseShaderPermutations) + { + TConstArrayView SimulationStages = BaseVMScript->GetSimulationStageMetaData(); + + // We add the number of simulation stages as Stage 0 is always the particle stage currently + NumPermutations += SimulationStages.Num(); + + ShaderStageToPermutation.Emplace(0, 1); + for (const FSimulationStageMetaData& StageMeta : SimulationStages) + { + ShaderStageToPermutation.Emplace(StageMeta.MinStage, StageMeta.MaxStage); + } + } + } + else + { + NumPermutations = 0; + ShaderStageToPermutation.Empty(); + } +} + +void FNiagaraShaderScript::UpdateCachedData_PostCompile(bool bCalledFromSerialize) +{ + check(IsInGameThread() || bCalledFromSerialize); + + FNiagaraShaderMapCachedData CachedData; + CachedData.NumPermutations = GetNumPermutations(); + CachedData.bIsComplete = 1; + CachedData.bGlobalConstantBufferUsed = 0; + CachedData.bSystemConstantBufferUsed = 0; + CachedData.bOwnerConstantBufferUsed = 0; + CachedData.bEmitterConstantBufferUsed = 0; + CachedData.bExternalConstantBufferUsed = 0; + CachedData.bViewUniformBufferUsed = 0; + + if (GameThreadShaderMap != nullptr) + { + for (int32 iPermutation = 0; iPermutation < CachedData.NumPermutations; ++iPermutation) + { + TNiagaraShaderRef Shader = GameThreadShaderMap->GetShader(&FNiagaraShader::StaticType, iPermutation); + if (!Shader.IsValid()) + { + CachedData.bIsComplete = 0; + break; + } + FNiagaraShader* NiagaraShader = static_cast(Shader.GetShader()); + + for (int i = 0; i < 2; ++i) + { + const uint32 BitToSet = 1 << i; + CachedData.bGlobalConstantBufferUsed |= NiagaraShader->GlobalConstantBufferParam[i].IsBound() ? BitToSet : 0; + CachedData.bSystemConstantBufferUsed |= NiagaraShader->SystemConstantBufferParam[i].IsBound() ? BitToSet : 0; + CachedData.bOwnerConstantBufferUsed |= NiagaraShader->OwnerConstantBufferParam[i].IsBound() ? BitToSet : 0; + CachedData.bEmitterConstantBufferUsed |= NiagaraShader->EmitterConstantBufferParam[i].IsBound() ? BitToSet : 0; + CachedData.bExternalConstantBufferUsed |= NiagaraShader->ExternalConstantBufferParam[i].IsBound() ? BitToSet : 0; + } + CachedData.bViewUniformBufferUsed |= NiagaraShader->ViewUniformBufferParam.IsBound() ? 1 : 0; + } + } + else + { + CachedData.bIsComplete = 0; + } + + CachedData.ShaderStageToPermutation = ShaderStageToPermutation; + + if (bCalledFromSerialize) + { + CachedData_RenderThread = MoveTemp(CachedData); + } + else if (!bQueuedForRelease) + { + ENQUEUE_RENDER_COMMAND(UpdateCachedData)( + [Script_RT = this, CachedData_RT = CachedData](FRHICommandListImmediate& RHICmdList) + { + Script_RT->SetRenderThreadCachedData(CachedData_RT); + }); + } +} + /** * Cache the script's shaders */ @@ -401,6 +582,8 @@ bool FNiagaraShaderScript::CacheShaders(const FNiagaraShaderMapId& ShaderMapId, bAssumeShaderMapIsComplete = FPlatformProperties::RequiresCookedData(); #endif + UpdateCachedData_PreCompile(); + if (GameThreadShaderMap && GameThreadShaderMap->TryToAddToExistingCompilationTask(this)) { //FNiagaraShaderMap::ShaderMapsBeingCompiled.Find(GameThreadShaderMap); @@ -441,18 +624,22 @@ bool FNiagaraShaderScript::CacheShaders(const FNiagaraShaderMapId& ShaderMapId, bSucceeded = true; } - FNiagaraShaderScript* Script = this; - FNiagaraShaderMap* LoadedShaderMap = GameThreadShaderMap; - ENQUEUE_RENDER_COMMAND(FSetShaderMapOnScriptResources)( - [Script, LoadedShaderMap](FRHICommandListImmediate& RHICmdList) - { - Script->SetRenderingThreadShaderMap(LoadedShaderMap); - }); + UpdateCachedData_PostCompile(); + + if (!bQueuedForRelease) + { + FNiagaraShaderScript* Script = this; + FNiagaraShaderMap* LoadedShaderMap = GameThreadShaderMap; + ENQUEUE_RENDER_COMMAND(FSetShaderMapOnScriptResources)( + [Script, LoadedShaderMap](FRHICommandListImmediate& RHICmdList) + { + Script->SetRenderingThreadShaderMap(LoadedShaderMap); + }); + } return bSucceeded; } - void FNiagaraShaderScript::FinishCompilation() { TArray ShaderMapIdsToFinish; @@ -485,21 +672,21 @@ void FNiagaraShaderScript::SetDataInterfaceParamInfo(const TArray< FNiagaraDataI DIParamInfo = InDIParamInfo; } -NIAGARASHADER_API FNiagaraShaderRef FNiagaraShaderScript::GetShader() const +NIAGARASHADER_API FNiagaraShaderRef FNiagaraShaderScript::GetShader(int32 PermutationId) const { check(!GIsThreadedRendering || !IsInGameThread()); if (!GIsEditor || RenderingThreadShaderMap /*&& RenderingThreadShaderMap->IsComplete(this, true)*/) { - return RenderingThreadShaderMap->GetShader(); + return RenderingThreadShaderMap->GetShader(PermutationId); } return FNiagaraShaderRef(); }; -NIAGARASHADER_API FNiagaraShaderRef FNiagaraShaderScript::GetShaderGameThread() const +NIAGARASHADER_API FNiagaraShaderRef FNiagaraShaderScript::GetShaderGameThread(int32 PermutationId) const { - if (GameThreadShaderMap) + if (GameThreadShaderMap && GameThreadShaderMap->IsValid()) { - return GameThreadShaderMap->GetShader(); + return GameThreadShaderMap->GetShader(PermutationId); } return FNiagaraShaderRef(); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraScriptBase.h b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraScriptBase.h new file mode 100644 index 000000000000..f8f91ab7c169 --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraScriptBase.h @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "NiagaraScriptBase.generated.h" + +USTRUCT() +struct NIAGARASHADER_API FSimulationStageMetaData +{ + GENERATED_USTRUCT_BODY() +public: + /** User simulation stage name. */ + UPROPERTY() + FName SimulationStageName; + + /** The Data Interface that we iterate over for this stage. If None, then use particles.*/ + UPROPERTY() + FName IterationSource; + + /** Is this stage a spawn-only stage? */ + UPROPERTY() + uint32 bSpawnOnly : 1; + + /** Do we write to particles this stage? */ + UPROPERTY() + uint32 bWritesParticles : 1; + + /** When enabled the simulation stage does not write all variables out, so we are reading / writing to the same buffer. */ + UPROPERTY() + uint32 bPartialParticleUpdate : 1; + + /** DataInterfaces that we write to in this stage.*/ + UPROPERTY() + TArray OutputDestinations; + + /** Index of the simulation stage where we begin iterating. This is meant to encompass iteration count without having an entry for each iteration.*/ + UPROPERTY() + int32 MinStage; + + /** Index of the simulation stage where we end iterating. This is meant to encompass iteration count without having an entry for each iteration.*/ + UPROPERTY() + int32 MaxStage; +}; + +UCLASS(MinimalAPI, abstract) +class UNiagaraScriptBase : public UObject +{ + GENERATED_UCLASS_BODY() + + virtual TConstArrayView GetSimulationStageMetaData() const PURE_VIRTUAL(UNiagaraScriptBase::GetSimulationStageMetaData(), return MakeArrayView(nullptr, 0); ) +}; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShader.h b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShader.h index 4c77df0f0e42..d0fc423522f4 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShader.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShader.h @@ -103,7 +103,6 @@ public: LAYOUT_ARRAY(FShaderUniformBufferParameter, OwnerConstantBufferParam, 2); LAYOUT_ARRAY(FShaderUniformBufferParameter, EmitterConstantBufferParam, 2); LAYOUT_ARRAY(FShaderUniformBufferParameter, ExternalConstantBufferParam, 2); - LAYOUT_FIELD(FShaderUniformBufferParameter, DataInterfaceUniformBufferParam); LAYOUT_FIELD(FShaderUniformBufferParameter, ViewUniformBufferParam); LAYOUT_FIELD(FShaderParameter, SimStartParam); LAYOUT_FIELD(FShaderParameter, EmitterTickCounterParam); @@ -116,7 +115,10 @@ public: LAYOUT_FIELD(FShaderParameter, UpdateStartInstanceParam); LAYOUT_FIELD(FShaderParameter, DefaultSimulationStageIndexParam); LAYOUT_FIELD(FShaderParameter, SimulationStageIndexParam); - LAYOUT_FIELD(FShaderParameter, IterationInterfaceCount); + + LAYOUT_FIELD(FShaderParameter, SimulationStageIterationInfoParam); + LAYOUT_FIELD(FShaderParameter, SimulationStageNormalizedIterationIndexParam); + LAYOUT_FIELD(FShaderParameter, ComponentBufferSizeReadParam); LAYOUT_FIELD(FShaderParameter, ComponentBufferSizeWriteParam); LAYOUT_ARRAY(FRWShaderParameter, EventIntUAVParams, MAX_CONCURRENT_EVENT_DATASETS); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShaderType.h b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShaderType.h index a21484e69d36..f8c4dd1957a2 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShaderType.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShaderType.h @@ -106,6 +106,7 @@ public: */ TSharedRef BeginCompileShader( uint32 ShaderMapId, + int32 PermutationId, const FNiagaraShaderScript* Script, FShaderCompilerEnvironment* CompilationEnvironment, EShaderPlatform Platform, diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShared.h b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShared.h index 15615399a918..29daa4b52952 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShared.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShared.h @@ -29,7 +29,7 @@ class FNiagaraShaderScript; class FNiagaraShaderMap; class FNiagaraShader; class FNiagaraShaderMapId; -class UNiagaraScript; +class UNiagaraScriptBase; struct FNiagaraVMExecutableDataId; #define MAX_CONCURRENT_EVENT_DATASETS 4 @@ -236,6 +236,9 @@ public: /** Whether or not we need to bake Rapid Iteration params. True to keep params, false to bake.*/ LAYOUT_FIELD_INITIALIZED(bool, bUsesRapidIterationParams, true); + /** Should we use shader permutations to reduce the cost of simulation stages or not */ + LAYOUT_FIELD_INITIALIZED(bool, bUseShaderPermutations, true); + FNiagaraShaderMapId() : CompilerVersionID() , FeatureLevel(GMaxRHIFeatureLevel) @@ -387,8 +390,8 @@ public: static void FlushShaderTypes(TArray& ShaderTypesToFlush); // ShaderMap interface - template TNiagaraShaderRef GetShader() const { return GetContent() ? TNiagaraShaderRef(GetContent()->GetShader(), *this) : TNiagaraShaderRef(); } - TNiagaraShaderRef GetShader(FShaderType* ShaderType) const { return GetContent() ? TNiagaraShaderRef(GetContent()->GetShader(ShaderType), *this) : TNiagaraShaderRef(); } + template TNiagaraShaderRef GetShader(int32 PermutationId) const { return TNiagaraShaderRef(GetContent()->GetShader(PermutationId), *this); } + TNiagaraShaderRef GetShader(FShaderType* ShaderType, int32 PermutationId) const { return TNiagaraShaderRef(GetContent()->GetShader(ShaderType, PermutationId), *this); } //static void FixupShaderTypes(EShaderPlatform Platform, const TMap& ShaderTypeNames); @@ -401,8 +404,7 @@ public: FNiagaraShaderMap(); // Destructor. - virtual ~FNiagaraShaderMap(); - virtual void OnReleased() override; + ~FNiagaraShaderMap(); /** * Compiles the shaders for a script and caches them in this shader map. @@ -450,6 +452,10 @@ public: /** Registers a niagara shader map in the global map so it can be used by Niagara scripts. */ void Register(EShaderPlatform InShaderPlatform); + // Reference counting. + NIAGARASHADER_API void AddRef(); + NIAGARASHADER_API void Release(); + /** * Removes all entries in the cache with exceptions based on a shader type * @param ShaderType - The shader type to flush @@ -476,10 +482,6 @@ public: /** Recreates FShaders from the passed in memory, handling shader key changes. */ void RestoreShadersFromMemory(const TArray& ShaderData); - /** Returns the maximum number of texture samplers used by any shader in this shader map. */ - uint32 GetMaxTextureSamplers() const; - - // Accessors. const FNiagaraShaderMapId& GetShaderMapId() const { return GetContent()->ShaderMapId; } EShaderPlatform GetShaderPlatform() const { return GetContent()->GetShaderPlatform(); } @@ -496,6 +498,7 @@ public: //const FUniformExpressionSet& GetUniformExpressionSet() const { return NiagaraCompilationOutput.UniformExpressionSet; } + int32 GetNumRefs() const { return NumRefs; } uint32 GetCompilingId() { return CompilingId; } static TMap, TArray > &GetInFlightShaderMaps() { @@ -526,6 +529,8 @@ private: /** Uniquely identifies this shader map during compilation, needed for deferred compilation where shaders from multiple shader maps are compiled together. */ uint32 CompilingId; + mutable int32 NumRefs; + /** Used to catch errors where the shader map is deleted directly. */ bool bDeletedThroughDeferredCleanup; @@ -547,11 +552,9 @@ private: bool IsNiagaraShaderComplete(const FNiagaraShaderScript* Script, const FNiagaraShaderType* ShaderType, bool bSilent); - friend NIAGARASHADER_API void DumpNiagaraStats(EShaderPlatform Platform); friend class FShaderCompilingManager; }; - DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnNiagaraScriptCompilationComplete); /** @@ -559,6 +562,31 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnNiagaraScriptCompilationComplete); */ class FNiagaraShaderScript { + struct FNiagaraShaderMapCachedData + { + FNiagaraShaderMapCachedData() + { + NumPermutations = 0; + bIsComplete = 0; + bGlobalConstantBufferUsed = 0; + bSystemConstantBufferUsed = 0; + bOwnerConstantBufferUsed = 0; + bEmitterConstantBufferUsed = 0; + bExternalConstantBufferUsed = 0; + bViewUniformBufferUsed = 0; + } + + TArray> ShaderStageToPermutation; + int32 NumPermutations; + uint32 bIsComplete : 1; + uint32 bGlobalConstantBufferUsed : 2; + uint32 bSystemConstantBufferUsed : 2; + uint32 bOwnerConstantBufferUsed : 2; + uint32 bEmitterConstantBufferUsed : 2; + uint32 bExternalConstantBufferUsed : 2; + uint32 bViewUniformBufferUsed : 1; + }; + public: /** @@ -572,6 +600,7 @@ public: , ShaderPlatform(SP_NumPlatforms) , bLoadedCookedShaderMapId(false) , bLoadedFromCookedMaterial(false) + , bQueuedForRelease(false) {} /** @@ -639,14 +668,6 @@ public: */ NIAGARASHADER_API bool IsCompilationFinished() const; - /** - * Checks if there is a valid GameThreadShaderMap, that is, the script can be run - * - * @return returns true if there is a GameThreadShaderMap. - */ - NIAGARASHADER_API bool HasValidGameThreadShaderMap() const; - - // Accessors. const TArray& GetCompileErrors() const { return CompileErrors; } void SetCompileErrors(const TArray& InCompileErrors) { CompileErrors = InCompileErrors; } @@ -670,7 +691,10 @@ public: } /** Note: SetGameThreadShaderMap must also be called with the same value, but from the game thread. */ - NIAGARASHADER_API void SetRenderingThreadShaderMap(FNiagaraShaderMap* InShaderMap); + NIAGARASHADER_API void SetRenderingThreadShaderMap(FNiagaraShaderMap* InShaderMap); + void SetRenderThreadCachedData(const FNiagaraShaderMapCachedData& CachedData); + + NIAGARASHADER_API void QueueForRelease(FThreadSafeBool& Fence); void AddCompileId(uint32 Id) { @@ -687,19 +711,10 @@ public: } - NIAGARASHADER_API class FNiagaraShaderMap* GetRenderingThreadShaderMap() const; - - NIAGARASHADER_API void RemoveOutstandingCompileId(const int32 OldOutstandingCompileShaderMapId); NIAGARASHADER_API virtual void AddReferencedObjects(FReferenceCollector& Collector); - //virtual const TArray& GetReferencedTextures() const = 0; - - - /** Returns true if this script is allowed to make development shaders via the global CVar CompileShadersForDevelopment. */ - //virtual bool GetAllowDevelopmentShaderCompile()const{ return true; } - /** * Get user source code for the shader * @param OutSource - generated source code @@ -713,15 +728,14 @@ public: const FString& GetFriendlyName() const { return FriendlyName; } - - NIAGARASHADER_API void SetScript(UNiagaraScript* InScript, ERHIFeatureLevel::Type InFeatureLevel, EShaderPlatform InShaderPlatform, const FGuid& InCompilerVersion, const TArray& InAdditionalDefines, + NIAGARASHADER_API void SetScript(UNiagaraScriptBase* InScript, ERHIFeatureLevel::Type InFeatureLevel, EShaderPlatform InShaderPlatform, const FGuid& InCompilerVersion, const TArray& InAdditionalDefines, const FNiagaraCompileHash& InBaseCompileHash, const TArray& InReferencedCompileHashes, - bool bInUsesRapidIterationParams, FString InFriendlyName); + bool bInUsesRapidIterationParams, bool bInUseShaderPermutations, FString InFriendlyName); #if WITH_EDITOR NIAGARASHADER_API bool MatchesScript(ERHIFeatureLevel::Type InFeatureLevel, EShaderPlatform InShaderPlatform, const FNiagaraVMExecutableDataId& ScriptId) const; #endif - UNiagaraScript *GetBaseVMScript() + UNiagaraScriptBase* GetBaseVMScript() { return BaseVMScript; } @@ -735,8 +749,8 @@ public: FString HlslOutput; - NIAGARASHADER_API FNiagaraShaderRef GetShader() const; - NIAGARASHADER_API FNiagaraShaderRef GetShaderGameThread() const; + NIAGARASHADER_API FNiagaraShaderRef GetShader(int32 PermutationId) const; + NIAGARASHADER_API FNiagaraShaderRef GetShaderGameThread(int32 PermutationId) const; NIAGARASHADER_API void SetDataInterfaceParamInfo(const TArray< FNiagaraDataInterfaceGPUParamInfo >& InDIParamInfo); @@ -751,25 +765,41 @@ public: } bool IsSame(const FNiagaraShaderMapId& InId) const; -protected: - // shared code needed for GetUniformScalarParameterExpressions, GetUniformVectorParameterExpressions, GetUniformCubeTextureExpressions.. - // @return can be 0 - const FNiagaraShaderMap* GetShaderMapToUse() const; + bool GetUseShaderPermutations() const { return bUseShaderPermutations; } + NIAGARASHADER_API int32 GetNumPermutations() const { return NumPermutations; } + NIAGARASHADER_API int32 PermutationIdToShaderStageIndex(int32 PermutationId) const; + + NIAGARASHADER_API bool IsShaderMapComplete() const; + + FORCEINLINE bool IsShaderMapComplete_RenderThread() const { check(IsInRenderingThread()); return CachedData_RenderThread.bIsComplete != 0; } + FORCEINLINE int32 GetNumPermutations_RenderThread() const { check(IsInRenderingThread()); return CachedData_RenderThread.NumPermutations; } + FORCEINLINE bool IsGlobalConstantBufferUsed_RenderThread(int32 Index) const { return (CachedData_RenderThread.bGlobalConstantBufferUsed & (1 << Index)) != 0; } + FORCEINLINE bool IsSystemConstantBufferUsed_RenderThread(int32 Index) const { return (CachedData_RenderThread.bSystemConstantBufferUsed & (1 << Index)) != 0; } + FORCEINLINE bool IsOwnerConstantBufferUsed_RenderThread(int32 Index) const { return (CachedData_RenderThread.bOwnerConstantBufferUsed & (1 << Index)) != 0; } + FORCEINLINE bool IsEmitterConstantBufferUsed_RenderThread(int32 Index) const { return (CachedData_RenderThread.bEmitterConstantBufferUsed & (1 << Index)) != 0; } + FORCEINLINE bool IsExternalConstantBufferUsed_RenderThread(int32 Index) const { return (CachedData_RenderThread.bExternalConstantBufferUsed & (1 << Index)) != 0; } + FORCEINLINE bool IsViewUniformBufferUsed_RenderThread() const { return CachedData_RenderThread.bViewUniformBufferUsed != 0; } + NIAGARASHADER_API int32 ShaderStageIndexToPermutationId_RenderThread(int32 ShaderStageIndex) const; + +protected: /** * Fills the passed array with IDs of shader maps unfinished compilation jobs. */ void GetShaderMapIDsWithUnfinishedCompilation(TArray& ShaderMapIds); - void SetFeatureLevel(ERHIFeatureLevel::Type InFeatureLevel) { FeatureLevel = InFeatureLevel; } + void UpdateCachedData_All(); + void UpdateCachedData_PreCompile(); + void UpdateCachedData_PostCompile(bool bCalledFromSerialize = false); + private: - UNiagaraScript* BaseVMScript; + UNiagaraScriptBase* BaseVMScript; TArray CompileErrors; @@ -799,6 +829,9 @@ private: /** Whether or not we need to bake Rapid Iteration params. True to keep params, false to bake.*/ bool bUsesRapidIterationParams = true; + /** Should we use shader permutations to reduce the cost of simulation stages or not */ + bool bUseShaderPermutations = true; + /** Compile hash for the base script. */ FNiagaraCompileHash BaseCompileHash; @@ -820,6 +853,12 @@ private: uint32 bLoadedCookedShaderMapId : 1; uint32 bLoadedFromCookedMaterial : 1; + uint32 bQueuedForRelease : 1; + + int32 NumPermutations = 0; + TArray> ShaderStageToPermutation; + + FNiagaraShaderMapCachedData CachedData_RenderThread; FNiagaraShaderMapId CookedShaderMapId; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Public/NiagaraRibbonVertexFactory.h b/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Public/NiagaraRibbonVertexFactory.h index a6ab6d35aee9..1f82aabbb7fe 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Public/NiagaraRibbonVertexFactory.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Public/NiagaraRibbonVertexFactory.h @@ -42,11 +42,15 @@ BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FNiagaraRibbonUniformParameters, NIAGARAVER SHADER_PARAMETER(int, MaterialParam1DataOffset) SHADER_PARAMETER(int, MaterialParam2DataOffset) SHADER_PARAMETER(int, MaterialParam3DataOffset) + SHADER_PARAMETER(int, U0OverrideDataOffset) + SHADER_PARAMETER(int, V0RangeOverrideDataOffset) + SHADER_PARAMETER(int, U1OverrideDataOffset) + SHADER_PARAMETER(int, V1RangeOverrideDataOffset) SHADER_PARAMETER(int, TotalNumInstances) SHADER_PARAMETER(int, InterpCount) SHADER_PARAMETER(float, OneOverInterpCount) - SHADER_PARAMETER(float, OneOverUV0TilingDistance) - SHADER_PARAMETER(float, OneOverUV1TilingDistance) + SHADER_PARAMETER(int, U0DistributionMode) + SHADER_PARAMETER(int, U1DistributionMode) SHADER_PARAMETER(FVector4, PackedVData) SHADER_PARAMETER(uint32, bLocalSpace) SHADER_PARAMETER_EX(float, DeltaSeconds, EShaderPrecisionModifier::Half) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Public/NiagaraSpriteVertexFactory.h b/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Public/NiagaraSpriteVertexFactory.h index c1d6437052fc..4d14e3010793 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Public/NiagaraSpriteVertexFactory.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Public/NiagaraSpriteVertexFactory.h @@ -51,6 +51,21 @@ BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT( FNiagaraSpriteUniformParameters, NIAGARAVE SHADER_PARAMETER(int, NormalizedAgeDataOffset) SHADER_PARAMETER(int, MaterialRandomDataOffset) SHADER_PARAMETER(FVector4, DefaultPos) + SHADER_PARAMETER(FVector2D, DefaultSize) + SHADER_PARAMETER(FVector2D, DefaultUVScale) + SHADER_PARAMETER(FVector, DefaultVelocity) + SHADER_PARAMETER(float, DefaultRotation) + SHADER_PARAMETER(FVector4, DefaultColor) + SHADER_PARAMETER(float, DefaultMatRandom) + SHADER_PARAMETER(float, DefaultCamOffset) + SHADER_PARAMETER(float, DefaultNormAge) + SHADER_PARAMETER(float, DefaultSubImage) + SHADER_PARAMETER(FVector4, DefaultFacing) + SHADER_PARAMETER(FVector4, DefaultAlignment) + SHADER_PARAMETER(FVector4, DefaultDynamicMaterialParameter0) + SHADER_PARAMETER(FVector4, DefaultDynamicMaterialParameter1) + SHADER_PARAMETER(FVector4, DefaultDynamicMaterialParameter2) + SHADER_PARAMETER(FVector4, DefaultDynamicMaterialParameter3) END_GLOBAL_SHADER_PARAMETER_STRUCT() typedef TUniformBufferRef FNiagaraSpriteUniformBufferRef; diff --git a/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapEyeTracker/Private/MagicLeapVREyeTracker.cpp b/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapEyeTracker/Private/MagicLeapVREyeTracker.cpp index 2d6a2b980b8f..d9eb18a78f9b 100644 --- a/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapEyeTracker/Private/MagicLeapVREyeTracker.cpp +++ b/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapEyeTracker/Private/MagicLeapVREyeTracker.cpp @@ -16,6 +16,7 @@ #include "IMagicLeapPlugin.h" #include "MagicLeapCFUID.h" #include "Lumin/CAPIShims/LuminAPI.h" +#include "Stats/Stats.h" #if WITH_MLSDK EMagicLeapEyeTrackingCalibrationStatus MLToUnrealEyeCalibrationStatus(MLEyeTrackingCalibrationStatus InStatus) @@ -85,6 +86,8 @@ void FMagicLeapVREyeTracker::SetActivePlayerController(APlayerController* NewAct bool FMagicLeapVREyeTracker::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapVREyeTracker_Tick); + bool bSuccess = true; #if WITH_MLSDK //assume we're in a bad state diff --git a/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapHandMeshing/Private/MagicLeapHandMeshingModule.cpp b/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapHandMeshing/Private/MagicLeapHandMeshingModule.cpp index 782dd9142b9b..a5d895c7c9a6 100644 --- a/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapHandMeshing/Private/MagicLeapHandMeshingModule.cpp +++ b/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapHandMeshing/Private/MagicLeapHandMeshingModule.cpp @@ -3,6 +3,7 @@ #include "MagicLeapHandMeshingModule.h" #include "GameFramework/Actor.h" #include "EngineUtils.h" +#include "Stats/Stats.h" DEFINE_LOG_CATEGORY(LogMagicLeapHandMeshing); @@ -21,6 +22,7 @@ void FMagicLeapHandMeshingModule::ShutdownModule() bool FMagicLeapHandMeshingModule::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapHandMeshingModule_Tick); MeshTracker.Update(); return true; } diff --git a/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapImageTracker/Private/MagicLeapImageTrackerModule.cpp b/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapImageTracker/Private/MagicLeapImageTrackerModule.cpp index f6819b9fe3f1..0145e53df89c 100644 --- a/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapImageTracker/Private/MagicLeapImageTrackerModule.cpp +++ b/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapImageTracker/Private/MagicLeapImageTrackerModule.cpp @@ -2,6 +2,7 @@ #include "MagicLeapImageTrackerModule.h" #include "MagicLeapImageTrackerRunnable.h" +#include "Stats/Stats.h" FMagicLeapImageTrackerModule::FMagicLeapImageTrackerModule() : Runnable(nullptr) @@ -48,6 +49,8 @@ void FMagicLeapImageTrackerModule::DestroyEntityTracker() bool FMagicLeapImageTrackerModule::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapImageTrackerModule_Tick); + if (!Runnable->IsRunning()) return true; FMagicLeapImageTrackerTask CompletedTask; diff --git a/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapPlanes/Source/Private/MagicLeapPlanesModule.cpp b/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapPlanes/Source/Private/MagicLeapPlanesModule.cpp index 01b8f2011d9c..b3d2ae081f92 100644 --- a/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapPlanes/Source/Private/MagicLeapPlanesModule.cpp +++ b/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapPlanes/Source/Private/MagicLeapPlanesModule.cpp @@ -6,6 +6,7 @@ #include "Engine/Engine.h" #include "Components/BoxComponent.h" #include "MagicLeapHandle.h" +#include "Stats/Stats.h" //PRAGMA_DISABLE_OPTIMIZATION using namespace MagicLeap; @@ -135,6 +136,8 @@ void FMagicLeapPlanesModule::DestroyEntityTracker() bool FMagicLeapPlanesModule::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapPlanesModule_Tick); + #if WITH_MLSDK if (!(IMagicLeapPlugin::Get().IsMagicLeapHMDValid())) { diff --git a/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapPrivileges/Private/MagicLeapPrivilegesModule.cpp b/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapPrivileges/Private/MagicLeapPrivilegesModule.cpp index 12b3f65bd0ea..232a64f51f87 100644 --- a/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapPrivileges/Private/MagicLeapPrivilegesModule.cpp +++ b/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapPrivileges/Private/MagicLeapPrivilegesModule.cpp @@ -3,6 +3,7 @@ #include "MagicLeapPrivilegesModule.h" #include "MagicLeapPrivilegeUtils.h" #include "Engine/Engine.h" +#include "Stats/Stats.h" using namespace MagicLeap; @@ -44,6 +45,8 @@ void FMagicLeapPrivilegesModule::ShutdownModule() bool FMagicLeapPrivilegesModule::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapPrivilegesModule_Tick); + #if WITH_MLSDK auto CopyPendingAsyncRequests(PendingAsyncRequests); diff --git a/Engine/Plugins/Lumin/MagicLeapCamera/Source/MagicLeapCVCamera/Private/MagicLeapCVCameraModule.cpp b/Engine/Plugins/Lumin/MagicLeapCamera/Source/MagicLeapCVCamera/Private/MagicLeapCVCameraModule.cpp index 221ad459f16e..18649702e225 100644 --- a/Engine/Plugins/Lumin/MagicLeapCamera/Source/MagicLeapCVCamera/Private/MagicLeapCVCameraModule.cpp +++ b/Engine/Plugins/Lumin/MagicLeapCamera/Source/MagicLeapCVCamera/Private/MagicLeapCVCameraModule.cpp @@ -3,6 +3,7 @@ #include "MagicLeapCVCameraModule.h" #include "Async/Async.h" #include "Lumin/CAPIShims/LuminAPI.h" +#include "Stats/Stats.h" DEFINE_LOG_CATEGORY(LogMagicLeapCVCamera); @@ -40,6 +41,8 @@ void FMagicLeapCVCameraModule::ShutdownModule() bool FMagicLeapCVCameraModule::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapCVCameraModule_Tick); + MinCameraRunTimer -= DeltaTime; FCVCameraTask CompletedTask; if (Runnable->TryGetCompletedTask(CompletedTask)) diff --git a/Engine/Plugins/Lumin/MagicLeapCamera/Source/MagicLeapCamera/Private/MagicLeapCameraPlugin.cpp b/Engine/Plugins/Lumin/MagicLeapCamera/Source/MagicLeapCamera/Private/MagicLeapCameraPlugin.cpp index 0d259dcaa710..60006bb94ea1 100644 --- a/Engine/Plugins/Lumin/MagicLeapCamera/Source/MagicLeapCamera/Private/MagicLeapCameraPlugin.cpp +++ b/Engine/Plugins/Lumin/MagicLeapCamera/Source/MagicLeapCamera/Private/MagicLeapCameraPlugin.cpp @@ -3,6 +3,7 @@ #include "MagicLeapCameraPlugin.h" #include "Async/Async.h" #include "Lumin/CAPIShims/LuminAPI.h" +#include "Stats/Stats.h" DEFINE_LOG_CATEGORY(LogMagicLeapCamera); @@ -42,6 +43,8 @@ void FMagicLeapCameraPlugin::ShutdownModule() bool FMagicLeapCameraPlugin::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapCameraPlugin_Tick); + MinVidCaptureTimer -= DeltaTime; FCameraTask CompletedTask; if (Runnable->TryGetCompletedTask(CompletedTask)) diff --git a/Engine/Plugins/Lumin/MagicLeapConnections/Source/Private/MagicLeapConnectionsPlugin.cpp b/Engine/Plugins/Lumin/MagicLeapConnections/Source/Private/MagicLeapConnectionsPlugin.cpp index 4b7eab119a13..4fc283bcab35 100644 --- a/Engine/Plugins/Lumin/MagicLeapConnections/Source/Private/MagicLeapConnectionsPlugin.cpp +++ b/Engine/Plugins/Lumin/MagicLeapConnections/Source/Private/MagicLeapConnectionsPlugin.cpp @@ -3,6 +3,7 @@ #include "MagicLeapConnectionsPlugin.h" #include "MagicLeapHandle.h" #include "Async/Async.h" +#include "Stats/Stats.h" DEFINE_LOG_CATEGORY(LogMagicLeapConnections); @@ -33,6 +34,8 @@ void FMagicLeapConnectionsPlugin::ShutdownModule() bool FMagicLeapConnectionsPlugin::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapConnectionsPlugin_Tick); + (void)DeltaTime; if (!bEnabled) { diff --git a/Engine/Plugins/Lumin/MagicLeapContacts/Source/Private/MagicLeapContactsPlugin.cpp b/Engine/Plugins/Lumin/MagicLeapContacts/Source/Private/MagicLeapContactsPlugin.cpp index 389e95e73b59..cb957da3ca18 100644 --- a/Engine/Plugins/Lumin/MagicLeapContacts/Source/Private/MagicLeapContactsPlugin.cpp +++ b/Engine/Plugins/Lumin/MagicLeapContacts/Source/Private/MagicLeapContactsPlugin.cpp @@ -1,6 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "MagicLeapContactsPlugin.h" +#include "Stats/Stats.h" using namespace MagicLeap; @@ -35,6 +36,8 @@ void FMagicLeapContactsPlugin::ShutdownModule() bool FMagicLeapContactsPlugin::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapContactsPlugin_Tick); + #if WITH_MLSDK if (PendingRequests.Num() > 0) { diff --git a/Engine/Plugins/Lumin/MagicLeapNetworking/Source/Private/MagicLeapNetworkingPlugin.cpp b/Engine/Plugins/Lumin/MagicLeapNetworking/Source/Private/MagicLeapNetworkingPlugin.cpp index de70c9f7651b..bee44248c396 100644 --- a/Engine/Plugins/Lumin/MagicLeapNetworking/Source/Private/MagicLeapNetworkingPlugin.cpp +++ b/Engine/Plugins/Lumin/MagicLeapNetworking/Source/Private/MagicLeapNetworkingPlugin.cpp @@ -2,6 +2,7 @@ #include "MagicLeapNetworkingPlugin.h" #include "Async/Async.h" +#include "Stats/Stats.h" DEFINE_LOG_CATEGORY(LogMagicLeapNetworking); @@ -23,6 +24,8 @@ void FMagicLeapNetworkingPlugin::ShutdownModule() bool FMagicLeapNetworkingPlugin::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapNetworkingPlugin_Tick); + FConnectionQuery ConnectionQuery; if (ConnectionQueries.Peek(ConnectionQuery)) { diff --git a/Engine/Plugins/Lumin/MagicLeapScreens/Source/MagicLeapScreens/Private/MagicLeapScreensPlugin.cpp b/Engine/Plugins/Lumin/MagicLeapScreens/Source/MagicLeapScreens/Private/MagicLeapScreensPlugin.cpp index e300ff4d70bf..cd24e42b6476 100644 --- a/Engine/Plugins/Lumin/MagicLeapScreens/Source/MagicLeapScreens/Private/MagicLeapScreensPlugin.cpp +++ b/Engine/Plugins/Lumin/MagicLeapScreens/Source/MagicLeapScreens/Private/MagicLeapScreensPlugin.cpp @@ -10,6 +10,7 @@ #include "MagicLeapMath.h" #include "MagicLeapHandle.h" #include "Misc/CommandLine.h" +#include "Stats/Stats.h" #define MAX_TEXTURE_SIZE 450 * 450 * 4 // currently limited by binder implementation @@ -411,6 +412,8 @@ FScreensTask FMagicLeapScreensPlugin::UpdateWatchHistoryEntry(const FMagicLeapSc bool FMagicLeapScreensPlugin::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapScreensPlugin_Tick); + if (!Runnable->CompletedTaskQueueIsEmpty()) { FScreensTask CompletedTask; diff --git a/Engine/Plugins/Lumin/MagicLeapSharedFile/Source/Private/MagicLeapSharedFilePlugin.cpp b/Engine/Plugins/Lumin/MagicLeapSharedFile/Source/Private/MagicLeapSharedFilePlugin.cpp index cc03567ec9fa..b7b2ab27a043 100644 --- a/Engine/Plugins/Lumin/MagicLeapSharedFile/Source/Private/MagicLeapSharedFilePlugin.cpp +++ b/Engine/Plugins/Lumin/MagicLeapSharedFile/Source/Private/MagicLeapSharedFilePlugin.cpp @@ -3,6 +3,7 @@ #include "MagicLeapSharedFilePlugin.h" #include "Lumin/CAPIShims/LuminAPISharedFile.h" #include "Lumin/CAPIShims/LuminAPIFileInfo.h" +#include "Stats/Stats.h" #if PLATFORM_LUMIN #include "Lumin/LuminPlatformFile.h" @@ -137,6 +138,8 @@ bool FMagicLeapSharedFilePlugin::SharedFilePickAsync(const FMagicLeapFilesPicked bool FMagicLeapSharedFilePlugin::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapSharedFilePlugin_Tick); + bool bWaitingForDelegateResult_Cached; FMagicLeapFilesPickedResultDelegate ResultDelegate_Cached; diff --git a/Engine/Plugins/Lumin/MagicLeapTablet/Source/Private/MagicLeapTabletPlugin.cpp b/Engine/Plugins/Lumin/MagicLeapTablet/Source/Private/MagicLeapTabletPlugin.cpp index e4fa7297326e..d1bb7690e650 100644 --- a/Engine/Plugins/Lumin/MagicLeapTablet/Source/Private/MagicLeapTabletPlugin.cpp +++ b/Engine/Plugins/Lumin/MagicLeapTablet/Source/Private/MagicLeapTabletPlugin.cpp @@ -6,6 +6,7 @@ #include "MagicLeapHandle.h" #include "MagicLeapMath.h" #include "MagicLeap/Private/MagicLeapHMD.h" +#include "Stats/Stats.h" DEFINE_LOG_CATEGORY(LogMagicLeapTablet); @@ -51,6 +52,8 @@ void FMagicLeapTabletPlugin::DestroyEntityTracker() bool FMagicLeapTabletPlugin::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMagicLeapTabletPlugin_Tick); + FPendingCallbackData PendingCallbackData; while (PendingCallbacks.Dequeue(PendingCallbackData)) { diff --git a/Engine/Plugins/Media/AppleProResMedia/AppleProResMedia.uplugin b/Engine/Plugins/Media/AppleProResMedia/AppleProResMedia.uplugin index 926b19c8f54a..8893f7488f34 100644 --- a/Engine/Plugins/Media/AppleProResMedia/AppleProResMedia.uplugin +++ b/Engine/Plugins/Media/AppleProResMedia/AppleProResMedia.uplugin @@ -25,6 +25,10 @@ { "Name": "WmfMedia", "Enabled": true + }, + { + "Name": "MovieRenderPipeline", + "Enabled": true } ] } diff --git a/Engine/Plugins/Media/AppleProResMedia/Source/AppleProResMedia/AppleProResMedia.Build.cs b/Engine/Plugins/Media/AppleProResMedia/Source/AppleProResMedia/AppleProResMedia.Build.cs index aad771548ec7..7646330064ff 100644 --- a/Engine/Plugins/Media/AppleProResMedia/Source/AppleProResMedia/AppleProResMedia.Build.cs +++ b/Engine/Plugins/Media/AppleProResMedia/Source/AppleProResMedia/AppleProResMedia.Build.cs @@ -12,7 +12,8 @@ namespace UnrealBuildTool.Rules "ProResToolbox", "WmfMediaFactory", "WmfMedia", - "ImageWriteQueue" + "ImageWriteQueue", + "MovieRenderPipelineCore", }); PrivateDependencyModuleNames.AddRange( diff --git a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MoviePipelineAppleProResOutput.cpp b/Engine/Plugins/Media/AppleProResMedia/Source/AppleProResMedia/Private/MoviePipelineAppleProResOutput.cpp similarity index 99% rename from Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MoviePipelineAppleProResOutput.cpp rename to Engine/Plugins/Media/AppleProResMedia/Source/AppleProResMedia/Private/MoviePipelineAppleProResOutput.cpp index d70c2226f604..4730a7cde3d8 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MoviePipelineAppleProResOutput.cpp +++ b/Engine/Plugins/Media/AppleProResMedia/Source/AppleProResMedia/Private/MoviePipelineAppleProResOutput.cpp @@ -86,6 +86,7 @@ void UMoviePipelineAppleProResOutput::Finalize_EncodeThread(MovieRenderPipeline: CodecWriter->Writer->Finalize(); } +#if WITH_EDITOR FText UMoviePipelineAppleProResOutput::GetDisplayText() const { // When it's called from the CDO it's in the drop down menu so they haven't selected a setting yet. @@ -103,3 +104,4 @@ FText UMoviePipelineAppleProResOutput::GetDisplayText() const return NSLOCTEXT("MovieRenderPipeline", "AppleProRes_DisplayName10Bit", "Apple ProRes [10bit]"); } } +#endif diff --git a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MoviePipelineAppleProResOutput.h b/Engine/Plugins/Media/AppleProResMedia/Source/AppleProResMedia/Private/MoviePipelineAppleProResOutput.h similarity index 99% rename from Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MoviePipelineAppleProResOutput.h rename to Engine/Plugins/Media/AppleProResMedia/Source/AppleProResMedia/Private/MoviePipelineAppleProResOutput.h index acc66eda5fbf..461fc98ee02c 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MoviePipelineAppleProResOutput.h +++ b/Engine/Plugins/Media/AppleProResMedia/Source/AppleProResMedia/Private/MoviePipelineAppleProResOutput.h @@ -34,7 +34,9 @@ protected: // ~UMoviePipelineVideoOutputBase Interface // UMoviePipelineOutputBase Interface +#if WITH_EDITOR virtual FText GetDisplayText() const override; +#endif virtual bool IsAlphaSupportedImpl() const override { bool bSupportedCodec = Codec == EAppleProResEncoderCodec::ProRes_4444 || Codec == EAppleProResEncoderCodec::ProRes_4444XQ; diff --git a/Engine/Plugins/Media/AvidDNxMedia/AvidDNxMedia.uplugin b/Engine/Plugins/Media/AvidDNxMedia/AvidDNxMedia.uplugin index 289528c609a3..80c270364490 100644 --- a/Engine/Plugins/Media/AvidDNxMedia/AvidDNxMedia.uplugin +++ b/Engine/Plugins/Media/AvidDNxMedia/AvidDNxMedia.uplugin @@ -19,5 +19,12 @@ "WhitelistPlatforms": [ "Win64" ], "BlacklistTargets": [ "Server" ] } + ], + "Plugins": + [ + { + "Name": "MovieRenderPipeline", + "Enabled": true + } ] } diff --git a/Engine/Plugins/Media/AvidDNxMedia/Source/Source/AvidDNxMedia.Build.cs b/Engine/Plugins/Media/AvidDNxMedia/Source/Source/AvidDNxMedia.Build.cs index d85dd77571b1..273f286392c9 100644 --- a/Engine/Plugins/Media/AvidDNxMedia/Source/Source/AvidDNxMedia.Build.cs +++ b/Engine/Plugins/Media/AvidDNxMedia/Source/Source/AvidDNxMedia.Build.cs @@ -16,6 +16,7 @@ namespace UnrealBuildTool.Rules "Engine", "MovieSceneCapture", "Projects", + "MovieRenderPipelineCore", } ); diff --git a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MoviePipelineAvidDNxOutput.cpp b/Engine/Plugins/Media/AvidDNxMedia/Source/Source/Private/MoviePipelineAvidDNxOutput.cpp similarity index 100% rename from Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MoviePipelineAvidDNxOutput.cpp rename to Engine/Plugins/Media/AvidDNxMedia/Source/Source/Private/MoviePipelineAvidDNxOutput.cpp diff --git a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MoviePipelineAvidDNxOutput.h b/Engine/Plugins/Media/AvidDNxMedia/Source/Source/Private/MoviePipelineAvidDNxOutput.h similarity index 98% rename from Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MoviePipelineAvidDNxOutput.h rename to Engine/Plugins/Media/AvidDNxMedia/Source/Source/Private/MoviePipelineAvidDNxOutput.h index 1e019a74a91b..5908b07cecc3 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MoviePipelineAvidDNxOutput.h +++ b/Engine/Plugins/Media/AvidDNxMedia/Source/Source/Private/MoviePipelineAvidDNxOutput.h @@ -31,7 +31,9 @@ protected: // ~UMoviePipelineVideoOutputBase Interface // UMoviePipelineOutputBase Interface +#if WITH_EDITOR virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "AvidDNx_DisplayName", "Avid DNx [8bit]"); } +#endif // ~UMoviePipelineOutputBase Interface public: diff --git a/Engine/Plugins/Media/PixelStreaming/Source/PixelStreaming/Private/Codecs/VideoEncoder.cpp b/Engine/Plugins/Media/PixelStreaming/Source/PixelStreaming/Private/Codecs/VideoEncoder.cpp index 28cc4e6939c1..17c77c19acd4 100644 --- a/Engine/Plugins/Media/PixelStreaming/Source/PixelStreaming/Private/Codecs/VideoEncoder.cpp +++ b/Engine/Plugins/Media/PixelStreaming/Source/PixelStreaming/Private/Codecs/VideoEncoder.cpp @@ -238,7 +238,7 @@ int32 FVideoEncoder::Encode(const webrtc::VideoFrame& Frame, const webrtc::Codec if (HWEncoderDetails.LastFramerate != int(Fps)) { - if (FMath::Abs(HWEncoderDetails.LastFramerate - Fps) > 5 || PixelStreamer.GetVerbosity() >= ELogVerbosity::Verbose) + if (FMath::Abs(HWEncoderDetails.LastFramerate - Fps) > 5 || UE_GET_LOG_VERBOSITY(PixelStreamer) >= ELogVerbosity::Verbose) { UE_LOG(PixelStreamer, Log, TEXT("Quality prioritization: QP %d, FPS %.0f"), HWEncoderDetails.LastAvgQP, Fps); } diff --git a/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/vpx/build/EpicChanges.txt b/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/vpx/build/EpicChanges.txt new file mode 100644 index 000000000000..b17ff2c07eba --- /dev/null +++ b/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/vpx/build/EpicChanges.txt @@ -0,0 +1,16 @@ + +The original vpx source is in orig-libvpx-1.6.1.tar.bz2 for reference. libvpx-1.6.1.tar.bz2 contains Epic modifications. + + +2020/07/24 (andrew.grant) + +Added support for building for arm64 macs. Currently vpx assumes arm64-darwin +is iPhoneOS so now follow the convention it uses for x86_64 where darwinNN +specifies the macos version. + +E.g. arm64-darwin-gcc remains iOS and arm64-darwin13-gcc is macos + +'configure' has been updated to recognize arm64-darwinxx-gcc targets +'build/make/configure.sh' has been updated to differntiate between 'darwin' +and 'darwinxx' where necessary (primarily when adding SDK paths) + diff --git a/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/vpx/build/Mac/BuildForMac.command b/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/vpx/build/Mac/BuildForMac.command new file mode 100644 index 000000000000..4439f5a41043 --- /dev/null +++ b/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/vpx/build/Mac/BuildForMac.command @@ -0,0 +1,197 @@ +#!/bin/sh +# Copyright Epic Games, Inc. All Rights Reserved. + +## +## Most of the following script is intended to be consistent for building all Mac +## third-party source. The sequence of steps are - +## 1) Set up constants, create temp dir, checkout files, save file info +## 2) lib-specific build steps +## 3) Check files were updated + +## +## Lib specific constants + +# Name of lib +LIB_NAME="vpx" +# Drops from the location of this script to where libfiles are relative to +# e.g. +# {DROP_TO_LIBROOT}/README +# {DROP_TO_LIBROOT}/include) +# ${DROP_TO_LIBROOT}/$LIBFILES[0]) +DROP_TO_LIBROOT=../.. +# Drops from the location of LIBROOT to Engine/Source/ThirdParrty +DROP_TO_THIRDPARTY=../../../../../../Source/ThirdParty + +# Path to libs from libroot +LIB_PATH=lib/Mac + +# files we build +LIBFILES=( + "${LIB_PATH}/libvpx.a" + "${LIB_PATH}/libvpx_fPIC.a" +) + +## +## Common setup steps + +# Build script will be in /Build/Mac so get that path and drop two folders to leave us +# in the actual lib folder +pushd . > /dev/null + +SCRIPT_DIR="`dirname "${BASH_SOURCE[0]}"`" +cd ${SCRIPT_DIR}/${DROP_TO_LIBROOT} +LIB_ROOT_DIR=${PWD} +echo Changed to ${LIB_ROOT_DIR} + +# We should be in ThirdParty/LibName and we want to pull in some common things from +# ThirdParty/BuildScripts/Mac/Common +source ${DROP_TO_THIRDPARTY}/BuildScripts/Mac/Common/Common.sh + +echo Rebuilding ${LIB_NAME} in $PWD + +# create a tempdir and save it (note the tmpdir variable is used by the functions that +# check file state) +TMPDIR="/tmp/${LIB_NAME}-$$" +mkdir -p ${TMPDIR} > /dev/null 2>&1 + +# checkout the library list and save their state (uses TMPDIR) +checkoutFiles ${LIBFILES[@]} +saveFileStates ${LIBFILES[@]} + +#################### +# vpx specific steps + +##################### +# configuration + +# library versions - expected to match tarball and directory names +VER=libvpx-1.6.1 + +# don't forget to match archive options with tarball type (bz/gz) +TARBALL=${SCRIPT_DIR}/../$VER.tar.bz2 + +# includ PID in scratch dir - needs to be absolute +SCRATCH_DIR=${TMPDIR}/build +SOURCE_DIR=$SCRATCH_DIR/$VER + + +##################### +# unpack + +rm -rf $SCRATCH_DIR +mkdir -p $SCRATCH_DIR + +echo "#######################################" +echo "# Unpacking the tarballs" +tar xjf $TARBALL -C $SCRATCH_DIR + +if [ $? -ne 0 ]; then + echo "" + echo "#######################################" + echo "# tarball $PWD/$TARBALL not found !" + echo "" + exit 1 +fi + +##################### +# build + +cd $SOURCE_DIR +echo changed to $PWD + +if [ "$BUILD_UNIVERSAL" = true ] ; then + SLICES=( + "x86_64-darwin13-gcc" + "arm64-darwin13-gcc" + ) +else + SLICES=( + "x86_64-darwin13-gcc" + ) +fi + +for SLICE in "${SLICES[@]}" +do + set BUILD_CFLAGS="-fvisibility=hidden -mmacosx-version-min=10.12 -stdlib=libc++" + set BUILD_CXXFLAGS="-fvisibility=hidden -mmacosx-version-min=10.12 -stdlib=libc++" + echo "#######################################" + + echo "# Configuring $VER for ${SLICE}" + ./configure --target=${SLICE} --disable-examples --disable-unit-tests --extra-cflags="${BUILD_CFLAGS}" --extra-cxxflags="${BUILD_CXXFLAGS}" + + if [ $? -ne 0 ]; then + echo "#######################################" + echo "# ERROR: Failed to run .configure" + exit 1 + fi + + echo "# Building $VER" + make clean >> /dev/null && make -j$(get_core_count) + + if [ $? -ne 0 ]; then + echo "#######################################" + echo "# ERROR: Make Failed " + exit 1 + fi + + # use some hardcoded knowledge and get static library out + #cp $SOURCE_DIR/libvpx.a ${LIB_ROOT_DIR}/${LIB_PATH} + cp $SOURCE_DIR/libvpx.a $TMPDIR/${SLICE}_libvpx.a + + ##################### + # build PIC version + + cd $SOURCE_DIR + echo "#######################################" + + echo "# Configuring $VER for ${SLICE} with PIC" + ./configure --target=${SLICE} --enable-pic --enable-static --disable-examples --disable-unit-tests --extra-cflags="${BUILD_CFLAGS}" --extra-cxxflags="${BUILD_CXXFLAGS}" + + if [ $? -ne 0 ]; then + echo "#######################################" + echo "# ERROR: Failed to run .configure" + exit 1 + fi + + echo "# Building $VER with PIC" + make clean >> /dev/null && make -j$(get_core_count) + + if [ $? -ne 0 ]; then + echo "#######################################" + echo "# ERROR: Make Failed " + exit 1 + fi + + # use some hardcoded knowledge and get static library out + #cp $SOURCE_DIR/libvpx.a ${LIB_ROOT_DIR}/${LIB_PATH}/libvpx_fPIC.a + cp $SOURCE_DIR/libvpx.a $TMPDIR/${SLICE}_libvpx_fPIC.a +done + +if [ "$BUILD_UNIVERSAL" = true ] ; then + echo "Creating universal libraries from slices" + lipo -create $TMPDIR/${SLICES[0]}_libvpx.a $TMPDIR/${SLICES[1]}_libvpx.a -o ${LIB_ROOT_DIR}/${LIB_PATH}/libvpx.a + lipo -create $TMPDIR/${SLICES[0]}_libvpx_fPIC.a $TMPDIR/${SLICES[1]}_libvpx_fPIC.a -o ${LIB_ROOT_DIR}/${LIB_PATH}/libvpx_fPIC.a +else + # just copy the single x86 slice + cp -v $TMPDIR/${SLICES[0}_libvpx.a ${LIB_ROOT_DIR}/${LIB_PATH}/libvpx.a + cp -v $TMPDIR/${SLICES[0}_libvpx_fPIC.a ${LIB_ROOT_DIR}/${LIB_PATH}/libvpx_fPIC.a +fi + +if [ $? -eq 0 ]; then + echo "" + echo "#######################################" + echo "# Newly built libs have been put into ${LIB_ROOT_DIR}/${LIB_PATH}" + echo "" +fi + +# back to where our libs are relative to +cd ${LIB_ROOT_DIR} + +# check the files were all touched +checkFilesWereUpdated ${LIBFILES[@]} + +checkFilesAreFatBinaries ${LIBFILES[@]} + +echo The following files were rebuilt: ${LIBFILES[@]} + +popd > /dev/null \ No newline at end of file diff --git a/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/vpx/build/Mac/build-libvpx-mac.sh b/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/vpx/build/Mac/build-libvpx-mac.sh deleted file mode 100644 index cb19ecee3f73..000000000000 --- a/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/vpx/build/Mac/build-libvpx-mac.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/sh -# Needs to be run on a Linux installation - -##################### -# configuration - -# library versions - expected to match tarball and directory names -VER=libvpx-1.6.1 - -# don't forget to match archive options with tarball type (bz/gz) -TARBALL=../$VER.tar.bz2 - -# includ PID in scratch dir - needs to be absolute -SCRATCH_DIR=/tmp/scratch/$$ -DIR=$SCRATCH_DIR/$VER - -DEST_DIR=`pwd` - -##################### -# unpack - -rm -rf $SCRATCH_DIR -mkdir -p $SCRATCH_DIR - -echo "#######################################" -echo "# Unpacking the tarballs" -tar xjf $TARBALL -C $SCRATCH_DIR - -##################### -# build - -cd $DIR -echo "#######################################" -echo "# Configuring $VER" -./configure --disable-examples --disable-unit-tests --extra-cflags="-fvisibility=hidden -mmacosx-version-min=10.12" > $DEST_DIR/build.log -echo "# Building $VER" -make -j8 >> $DEST_DIR/build.log -if [ $? -ne 0 ]; then - echo "" - echo "#######################################" - echo "# ERROR!" - echo "" - exit 1 -fi -# use some hardcoded knowledge and get static library out -cp $DIR/libvpx.a $DEST_DIR - -##################### -# build PIC version - -cd $DIR -echo "#######################################" -echo "# Configuring $VER with PIC" -./configure --enable-pic --enable-static --disable-examples --disable-unit-tests --extra-cflags="-fvisibility=hidden -mmacosx-version-min=10.12" > $DEST_DIR/build-pic.log -echo "# Building $VER with PIC" -make -j8 >> $DEST_DIR/build-pic.log -if [ $? -ne 0 ]; then - echo "" - echo "#######################################" - echo "# ERROR!" - echo "" - exit 1 -fi -# use some hardcoded knowledge and get static library out -cp $DIR/libvpx.a $DEST_DIR/libvpx_fPIC.a - -if [ $? -eq 0 ]; then - echo "" - echo "#######################################" - echo "# Newly built libs have been put into current directory." - echo "" -fi diff --git a/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/webm/build/Mac/BuildForMac.command b/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/webm/build/Mac/BuildForMac.command new file mode 100755 index 000000000000..c687ab75e04b --- /dev/null +++ b/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/webm/build/Mac/BuildForMac.command @@ -0,0 +1,161 @@ +#!/bin/sh +# Copyright Epic Games, Inc. All Rights Reserved. + +## +## Most of the following script is intended to be consistent for building all Mac +## third-party source. The sequence of steps are - +## 1) Set up constants, create temp dir, checkout files, save file info +## 2) lib-specific build steps +## 3) Check files were updated + +## +## Lib specific constants + +# Name of lib +LIB_NAME="WebM" +# Drops from the location of this script to where libfiles are relative to +# e.g. +# {DROP_TO_LIBROOT}/README +# {DROP_TO_LIBROOT}/include) +# ${DROP_TO_LIBROOT}/$LIBFILES[0]) +DROP_TO_LIBROOT=../.. +# Drops from the location of LIBROOT to Engine/Source/ThirdParrty +DROP_TO_THIRDPARTY=../../../../../../Source/ThirdParty + +# Path to libs from libroot +LIB_PATH=lib/Mac + +# files we build +LIBFILES=( + "${LIB_PATH}/libwebm.a" + "${LIB_PATH}/libwebm_fPIC.a" +) + +## +## Common setup steps + +# Build script will be in /Build/Mac so get that path and drop two folders to leave us +# in the actual lib folder +pushd . > /dev/null +SCRIPT_DIR="`dirname "${BASH_SOURCE[0]}"`" +cd ${SCRIPT_DIR}/${DROP_TO_LIBROOT} +LIB_ROOT_DIR=${PWD} +echo Changed to ${LIB_ROOT_DIR} + +# We should be in ThirdParty/LibName and we want to pull in some common things from +# ThirdParty/BuildScripts/Mac/Common +source ${DROP_TO_THIRDPARTY}/BuildScripts/Mac/Common/Common.sh + +echo Rebuilding ${LIB_NAME} in $PWD + +# create a tempdir and save it (note the tmpdir variable is used by the functions that +# check file state) +TMPDIR="/tmp/${LIB_NAME}-$$" +mkdir -p ${TMPDIR} > /dev/null 2>&1 + +# checkout the library list and save their state (uses TMPDIR) +checkoutFiles ${LIBFILES[@]} +saveFileStates ${LIBFILES[@]} + +#################### +# libm specific steps + +##################### +# configuration + +# library versions - expected to match tarball and directory names +VER=libwebm-1.0.0.27 + +# don't forget to match archive options with tarball type (bz/gz) +TARBALL=${SCRIPT_DIR}/../$VER.tar.bz2 + +# includ PID in scratch dir - needs to be absolute +SCRATCH_DIR=${TMPDIR}/build-$$ +SOURCE_DIR=$SCRATCH_DIR/$VER +BUILD_DIR=$SCRATCH_DIR/build + +##################### +# unpack + +rm -rf $SCRATCH_DIR +mkdir -p $SCRATCH_DIR + +echo "#######################################" +echo "# Unpacking the tarballs" +tar xjf $TARBALL -C $SCRATCH_DIR + +if [ $? -ne 0 ]; then + echo "" + echo "#######################################" + echo "# tarball $PWD/$TARBALL not found !" + echo "" + exit 1 +fi + +##################### +# build + +OSX_ARCHITECTURES="x86_64" +if [ "$BUILD_UNIVERSAL" = true ] ; then + OSX_ARCHITECTURES="${OSX_ARCHITECTURES};arm64" +fi + +mkdir -p $BUILD_DIR +cd $BUILD_DIR +cp ${SCRIPT_DIR}/CMakeLists.txt $SOURCE_DIR/CMakeLists.txt +echo "#######################################" +echo "# Configuring $VER" +cmake -DCMAKE_OSX_ARCHITECTURES="${OSX_ARCHITECTURES}" $SOURCE_DIR > $SCRIPT_DIR/build.log +echo "# Building $VER" +make -j$(get_core_count) webm >> $SCRIPT_DIR/build.log +if [ $? -ne 0 ]; then + echo "" + echo "#######################################" + echo "# ERROR!" + echo "" + exit 1 +fi +# use some hardcoded knowledge and get static library out +cp $BUILD_DIR/libwebm.a ${LIB_ROOT_DIR}/${LIB_PATH} + +##################### +# build PIC version + +rm -rf $BUILD_DIR +mkdir -p $BUILD_DIR +cd $BUILD_DIR +cp ${SCRIPT_DIR}/CMakeLists_Editor.txt $SOURCE_DIR/CMakeLists.txt +echo "#######################################" +echo "# Configuring $VER with PIC" +cmake -DCMAKE_OSX_ARCHITECTURES="${OSX_ARCHITECTURES}" $SOURCE_DIR > $SCRIPT_DIR/build-pic.log +echo "# Building $VER with PIC" +make -j$(get_core_count) webm >> $SCRIPT_DIR/build-pic.log + +if [ $? -ne 0 ]; then + echo "" + echo "#######################################" + echo "# ERROR!" + echo "" + exit 1 +fi +# use some hardcoded knowledge and get static library out +cp $BUILD_DIR/libwebm.a ${LIB_ROOT_DIR}/${LIB_PATH}/libwebm_fPIC.a + +if [ $? -eq 0 ]; then + echo "" + echo "#######################################" + echo "# Newly built libs have been put into ${LIB_ROOT_DIR}/${LIB_PATH}" + echo "" +fi + +# back to where our libs are relative to +cd ${LIB_ROOT_DIR} + +# check the files were all touched +checkFilesWereUpdated ${LIBFILES[@]} + +checkFilesAreFatBinaries ${LIBFILES[@]} + +echo The following files were rebuilt: ${LIBFILES[@]} + +popd > /dev/null \ No newline at end of file diff --git a/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/webm/build/Mac/build-libwebm-mac.sh b/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/webm/build/Mac/build-libwebm-mac.sh deleted file mode 100644 index f48488779155..000000000000 --- a/Engine/Plugins/Media/WebMMedia/Source/ThirdParty/webm/build/Mac/build-libwebm-mac.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/sh -# Needs to be run on a Linux installation - -##################### -# configuration - -# library versions - expected to match tarball and directory names -VER=libwebm-1.0.0.27 - -# don't forget to match archive options with tarball type (bz/gz) -TARBALL=../$VER.tar.bz2 - -# includ PID in scratch dir - needs to be absolute -SCRATCH_DIR=/tmp/scratch/$$ -SOURCE_DIR=$SCRATCH_DIR/$VER -BUILD_DIR=$SCRATCH_DIR/build - -DEST_DIR=`pwd` - -##################### -# unpack - -rm -rf $SCRATCH_DIR -mkdir -p $SCRATCH_DIR - -echo "#######################################" -echo "# Unpacking the tarballs" -tar xjf $TARBALL -C $SCRATCH_DIR - -##################### -# build - -mkdir -p $BUILD_DIR -cd $BUILD_DIR -cp $DEST_DIR/CMakeLists.txt $SOURCE_DIR/CMakeLists.txt -echo "#######################################" -echo "# Configuring $VER" -cmake $SOURCE_DIR > $DEST_DIR/build.log -echo "# Building $VER" -make -j8 webm >> $DEST_DIR/build.log -if [ $? -ne 0 ]; then - echo "" - echo "#######################################" - echo "# ERROR!" - echo "" - exit 1 -fi -# use some hardcoded knowledge and get static library out -cp $BUILD_DIR/libwebm.a $DEST_DIR - -##################### -# build PIC - -rm -rf $BUILD_DIR -mkdir -p $BUILD_DIR -cd $BUILD_DIR -cp $DEST_DIR/CMakeLists_Editor.txt $SOURCE_DIR/CMakeLists.txt -echo "#######################################" -echo "# Configuring $VER with PIC" -cmake $SOURCE_DIR > $DEST_DIR/build-pic.log -echo "# Building $VER with PIC" -make -j8 webm >> $DEST_DIR/build-pic.log -if [ $? -ne 0 ]; then - echo "" - echo "#######################################" - echo "# ERROR!" - echo "" - exit 1 -fi -# use some hardcoded knowledge and get static library out -cp $BUILD_DIR/libwebm.a $DEST_DIR/libwebm_fPIC.a - -if [ $? -eq 0 ]; then - echo "" - echo "#######################################" - echo "# Newly built libs have been put into current directory." - echo "" -fi diff --git a/Engine/Plugins/MeshPainting/Source/MeshPaintEditorMode/Private/MeshPaintMode.cpp b/Engine/Plugins/MeshPainting/Source/MeshPaintEditorMode/Private/MeshPaintMode.cpp index ccdf6545acd3..5eeb61e1c539 100644 --- a/Engine/Plugins/MeshPainting/Source/MeshPaintEditorMode/Private/MeshPaintMode.cpp +++ b/Engine/Plugins/MeshPainting/Source/MeshPaintEditorMode/Private/MeshPaintMode.cpp @@ -25,6 +25,7 @@ #include "MeshSelect.h" #include "MeshTexturePaintingTool.h" #include "Modules/ModuleManager.h" +#include "Settings/LevelEditorMiscSettings.h" #define LOCTEXT_NAMESPACE "MeshPaintMode" @@ -118,14 +119,15 @@ UMeshPaintMode::UMeshPaintMode() { SettingsClass = UMeshPaintModeSettings::StaticClass(); ToolsContextClass = UMeshToolsContext::StaticClass(); - + // Don't be a visible mode unless legacy mesh paint mode is not on. + const bool bVisible = !GetDefault()->bEnableLegacyMeshPaintMode; FModuleManager::Get().LoadModule("EditorStyle"); Info = FEditorModeInfo( FName(TEXT("MeshPaintMode")), LOCTEXT("ModeName", "Mesh Paint"), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.MeshPaintMode", "LevelEditor.MeshPaintMode.Small"), - true, + bVisible, 600 ); } diff --git a/Engine/Plugins/Messaging/UdpMessaging/Source/UdpMessaging/Private/UdpMessagingModule.cpp b/Engine/Plugins/Messaging/UdpMessaging/Source/UdpMessaging/Private/UdpMessagingModule.cpp index c85ceb190224..204aa1537101 100644 --- a/Engine/Plugins/Messaging/UdpMessaging/Source/UdpMessaging/Private/UdpMessagingModule.cpp +++ b/Engine/Plugins/Messaging/UdpMessaging/Source/UdpMessaging/Private/UdpMessagingModule.cpp @@ -10,6 +10,7 @@ #include "Misc/App.h" #include "Containers/Ticker.h" #include "Interfaces/IPv4/IPv4Endpoint.h" +#include "Stats/Stats.h" #if WITH_EDITOR #include "ISettingsModule.h" @@ -627,6 +628,7 @@ private: uint32 CheckNumber = 1; AutoRepairHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([WeakTransport = WeakBridgeTransport, LastTime = FDateTime::UtcNow(), CheckDelay, CheckNumber](float DeltaTime) mutable { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FUdpMessagingModule_AutoRepair); bool bContinue = true; FDateTime UtcNow = FDateTime::UtcNow(); if (LastTime + (CheckDelay * CheckNumber) <= UtcNow) diff --git a/Engine/Plugins/MovieScene/CustomizableSequencerTracks/Source/CustomizableSequencerTracks/Private/SequencerSectionBP.cpp b/Engine/Plugins/MovieScene/CustomizableSequencerTracks/Source/CustomizableSequencerTracks/Private/SequencerSectionBP.cpp index b1eb4558825e..4147d82d3617 100644 --- a/Engine/Plugins/MovieScene/CustomizableSequencerTracks/Source/CustomizableSequencerTracks/Private/SequencerSectionBP.cpp +++ b/Engine/Plugins/MovieScene/CustomizableSequencerTracks/Source/CustomizableSequencerTracks/Private/SequencerSectionBP.cpp @@ -29,10 +29,11 @@ void USequencerSectionBP::ImportEntityImpl(UMovieSceneEntitySystemLinker* Entity { FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get(); + FGuid ObjectBindingID = Params.GetObjectBindingID(); OutImportedEntity->AddBuilder( FEntityBuilder() .Add(BuiltInComponents->TrackInstance, FMovieSceneTrackInstanceComponent{ this, CustomTrack->TrackInstanceType }) - .AddConditional(BuiltInComponents->GenericObjectBinding, Params.ObjectBindingID, Params.ObjectBindingID.IsValid()) + .AddConditional(BuiltInComponents->GenericObjectBinding, ObjectBindingID, ObjectBindingID.IsValid()) ); } } \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/LevelSequenceEditor/Source/LevelSequenceEditor/Private/LevelSequenceEditorToolkit.cpp b/Engine/Plugins/MovieScene/LevelSequenceEditor/Source/LevelSequenceEditor/Private/LevelSequenceEditorToolkit.cpp index 75dd4df4ec1d..786101e44bbb 100644 --- a/Engine/Plugins/MovieScene/LevelSequenceEditor/Source/LevelSequenceEditor/Private/LevelSequenceEditorToolkit.cpp +++ b/Engine/Plugins/MovieScene/LevelSequenceEditor/Source/LevelSequenceEditor/Private/LevelSequenceEditorToolkit.cpp @@ -537,7 +537,7 @@ void FLevelSequenceEditorToolkit::AddDefaultTracksForActor(AActor& Actor, const break; } - if (!Sequencer->CanKeyProperty(FCanKeyPropertyParams(Actor.GetClass(), *PropertyPath))) + if (!Sequencer->CanKeyProperty(FCanKeyPropertyParams(PropertyOwner->GetClass(), *PropertyPath))) { continue; } diff --git a/Engine/Plugins/MovieScene/LevelSequenceEditor/Source/LevelSequenceEditor/Private/Misc/LevelSequenceEditorSpawnRegister.cpp b/Engine/Plugins/MovieScene/LevelSequenceEditor/Source/LevelSequenceEditor/Private/Misc/LevelSequenceEditorSpawnRegister.cpp index a7158756f078..b71baae8ff1b 100644 --- a/Engine/Plugins/MovieScene/LevelSequenceEditor/Source/LevelSequenceEditor/Private/Misc/LevelSequenceEditorSpawnRegister.cpp +++ b/Engine/Plugins/MovieScene/LevelSequenceEditor/Source/LevelSequenceEditor/Private/Misc/LevelSequenceEditorSpawnRegister.cpp @@ -194,6 +194,13 @@ void FLevelSequenceEditorSpawnRegister::SaveDefaultSpawnableStateImpl(FMovieScen { TrackedState->bHasBeenModified = false; } + + TSharedPtr Sequencer = WeakSequencer.Pin(); + if (Sequencer.IsValid()) + { + Sequencer->RequestInvalidateCachedData(); + Sequencer->RequestEvaluate(); + } } /* FLevelSequenceEditorSpawnRegister implementation diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/MovieRenderPipeline.uplugin b/Engine/Plugins/MovieScene/MovieRenderPipeline/MovieRenderPipeline.uplugin index 4a362a8994d4..e7552474eb45 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/MovieRenderPipeline.uplugin +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/MovieRenderPipeline.uplugin @@ -13,7 +13,7 @@ "CanContainContent": true, "IsBetaVersion": true, "Installed": false, - "EnabledByDefault": true, + "EnabledByDefault": false, "Plugins": [ { "Name": "EditorScriptingUtilities", @@ -28,23 +28,32 @@ "Modules": [ { "Name": "MovieRenderPipelineCore", - "Type": "UncookedOnly", - "LoadingPhase": "Default" + "Type": "Runtime", + "LoadingPhase": "Default", + "WhitelistPlatforms": [ "Mac", "Win32", "Win64", "Linux" ] }, { "Name": "MovieRenderPipelineSettings", - "Type": "UncookedOnly", - "LoadingPhase": "Default" - }, + "Type": "Runtime", + "LoadingPhase": "Default", + "WhitelistPlatforms": [ "Mac", "Win32", "Win64", "Linux" ] + }, { - "Name" : "MovieRenderPipelineRenderPasses", - "Type": "UncookedOnly", - "LoadingPhase": "Default" + "Name": "MovieRenderPipelineRenderPasses", + "Type": "Runtime", + "LoadingPhase": "Default", + "WhitelistPlatforms": [ "Mac", "Win32", "Win64", "Linux" ] }, { "Name": "MovieRenderPipelineEditor", "Type": "Editor", "LoadingPhase": "Default" + }, + { + "Name": "UEOpenExrRTTI", + "Type": "Runtime", + "LoadingPhase": "Default", + "WhitelistPlatforms": [ "Mac", "Win32", "Win64", "Linux" ] } ] } \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/MovieRenderPipelineCore.Build.cs b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/MovieRenderPipelineCore.Build.cs index 188ed85a701f..3c4a99ea11d5 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/MovieRenderPipelineCore.Build.cs +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/MovieRenderPipelineCore.Build.cs @@ -34,5 +34,11 @@ public class MovieRenderPipelineCore : ModuleRules "ImageWriteQueue", // For debug tile writing } ); + + if (Target.bBuildEditor == true) + { + PublicDependencyModuleNames.Add("MovieSceneTools"); + } + } } diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipeline.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipeline.cpp index 21f42db0d2f3..875c732687fe 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipeline.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipeline.cpp @@ -38,6 +38,10 @@ #include "MoviePipelineCameraSetting.h" #include "MoviePipelineQueue.h" #include "HAL/FileManager.h" +#include "Misc/CoreDelegates.h" +#if WITH_EDITOR +#include "MovieSceneExportMetadata.h" +#endif #define LOCTEXT_NAMESPACE "MoviePipeline" @@ -158,6 +162,7 @@ void UMoviePipeline::Initialize(UMoviePipelineExecutorJob* InJob) // Disable some user settings that conflict with our need to mutate the data. { +#if WITH_EDITORONLY_DATA // Movie Scene Read Only SequenceChanges.bSequenceReadOnly = TargetSequence->GetMovieScene()->IsReadOnly(); TargetSequence->GetMovieScene()->SetReadOnly(false); @@ -165,7 +170,7 @@ void UMoviePipeline::Initialize(UMoviePipelineExecutorJob* InJob) // Playback Range locked SequenceChanges.bSequencePlaybackRangeLocked = TargetSequence->GetMovieScene()->IsPlaybackRangeLocked(); TargetSequence->GetMovieScene()->SetPlaybackRangeLocked(false); - +#endif // Force Frame-locked evaluation off on the sequence. We control time and will respect that, but need it off for subsampling. SequenceChanges.EvaluationType = TargetSequence->GetMovieScene()->GetEvaluationType(); TargetSequence->GetMovieScene()->SetEvaluationType(EMovieSceneEvaluationType::WithSubFrames); @@ -210,6 +215,19 @@ void UMoviePipeline::Initialize(UMoviePipelineExecutorJob* InJob) // validation should re-use what was used in the UI. ValidateSequenceAndSettings(); +#if WITH_EDITOR + // Next, initialize the output metadata with the shot list data we just built + OutputMetadata.Shots.Empty(ActiveShotList.Num()); + for (UMoviePipelineExecutorShot* Shot : ActiveShotList) + { + UMoviePipelineOutputSetting* OutputSettings = FindOrAddSetting(Shot); + + FMovieSceneExportMetadataShot& ShotMetadata = OutputMetadata.Shots.AddDefaulted_GetRef(); + ShotMetadata.MovieSceneShotSection = Cast(Shot->OuterPathKey.TryLoad()); + ShotMetadata.HandleFrames = OutputSettings->HandleFrameCount; + } +#endif + // Finally, we're going to create a Level Sequence Actor in the world that has its settings configured by us. // Because this callback is at the end of startup (and before tick) we should be able to spawn the actor // and give it a chance to tick once (where it should do nothing) before we start manually manipulating it. @@ -249,6 +267,8 @@ void UMoviePipeline::Initialize(UMoviePipelineExecutorJob* InJob) CurrentShotIndex = 0; CachedOutputState.ShotCount = ActiveShotList.Num(); + InitializationVersion = UMoviePipelineBlueprintLibrary::ResolveVersionNumber(this); + // Initialization is complete. This engine frame is a wash (because the tick started with a // delta time not generated by us) so we'll wait until the next engine frame to start rendering. InitializationTime = FDateTime::UtcNow(); @@ -282,8 +302,10 @@ void UMoviePipeline::RestoreTargetSequenceToOriginalState() TargetSequence->GetMovieScene()->SetEvaluationType(SequenceChanges.EvaluationType); TargetSequence->GetMovieScene()->SetPlaybackRange(SequenceChanges.PlaybackRange); +#if WITH_EDITORONLY_DATA TargetSequence->GetMovieScene()->SetReadOnly(SequenceChanges.bSequenceReadOnly); TargetSequence->GetMovieScene()->SetPlaybackRangeLocked(SequenceChanges.bSequencePlaybackRangeLocked); +#endif if(UPackage* Package = TargetSequence->GetMovieScene()->GetTypedOuter()) { Package->SetDirtyFlag(SequenceChanges.bSequencePackageDirty); @@ -295,8 +317,9 @@ void UMoviePipeline::RestoreTargetSequenceToOriginalState() if (ModifiedSegment.MovieScene.IsValid()) { ModifiedSegment.MovieScene->SetPlaybackRange(ModifiedSegment.MovieScenePlaybackRange); +#if WITH_EDITORONLY_DATA ModifiedSegment.MovieScene->SetReadOnly(ModifiedSegment.bMovieSceneReadOnly); - +#endif if(UPackage* Package = ModifiedSegment.MovieScene->GetTypedOuter()) { Package->SetDirtyFlag(ModifiedSegment.bMovieScenePackageDirty); @@ -480,6 +503,7 @@ void UMoviePipeline::TransitionToState(const EMovieRenderPipelineState InNewStat } TeardownAudioRendering(); + LevelSequenceActor->GetSequencePlayer()->Stop(); RestoreTargetSequenceToOriginalState(); if (UGameViewportClient* Viewport = GetWorld()->GetGameViewport()) @@ -751,14 +775,16 @@ void UMoviePipeline::InitializeLevelSequenceActor() LevelSequenceActor = GetWorld()->SpawnActor(); check(LevelSequenceActor); } - - // Use our duplicated sequence - LevelSequenceActor->SetSequence(TargetSequence); - + // Enforce settings. LevelSequenceActor->PlaybackSettings.LoopCount.Value = 0; LevelSequenceActor->PlaybackSettings.bAutoPlay = false; LevelSequenceActor->PlaybackSettings.bPauseAtEnd = true; + LevelSequenceActor->PlaybackSettings.bRestoreState = true; + + // Use our duplicated sequence + LevelSequenceActor->SetSequence(TargetSequence); + LevelSequenceActor->GetSequencePlayer()->SetTimeController(CustomSequenceTimeController); LevelSequenceActor->GetSequencePlayer()->Stop(); @@ -808,15 +834,18 @@ void UMoviePipeline::BuildShotListFromSequence() else { ModifiedSegment.MovieScenePlaybackRange = InnerMovieScene->GetPlaybackRange(); +#if WITH_EDITORONLY_DATA ModifiedSegment.bMovieSceneReadOnly = InnerMovieScene->IsReadOnly(); - +#endif if (UPackage* OwningPackage = InnerMovieScene->GetTypedOuter()) { ModifiedSegment.bMovieScenePackageDirty = OwningPackage->IsDirty(); } +#if WITH_EDITORONLY_DATA // Unlock the playback range and readonly flags so we can modify the scene. InnerMovieScene->SetReadOnly(false); +#endif } } @@ -1153,10 +1182,15 @@ bool UMoviePipeline::DebugFrameStepPreTick() void UMoviePipeline::LoadDebugWidget() { - FSoftClassPath DebugWidgetClassRef(TEXT("/MovieRenderPipeline/Blueprints/UI_MovieRenderPipelineScreenOverlay.UI_MovieRenderPipelineScreenOverlay_C")); - if (UClass* DebugWidgetClass = DebugWidgetClassRef.TryLoadClass()) + TSubclassOf DebugWidgetClassToUse = DebugWidgetClass; + if (DebugWidgetClassToUse.Get() == nullptr) { - DebugWidget = CreateWidget(GetWorld(), DebugWidgetClass); + DebugWidgetClassToUse = LoadClass(nullptr, TEXT("/MovieRenderPipeline/Blueprints/UI_MovieRenderPipelineScreenOverlay.UI_MovieRenderPipelineScreenOverlay_C"), nullptr, LOAD_None, nullptr); + } + + if (DebugWidgetClassToUse.Get() != nullptr) + { + DebugWidget = CreateWidget(GetWorld(), DebugWidgetClassToUse.Get()); if (DebugWidget) { DebugWidget->OnInitializedForPipeline(this); @@ -1286,73 +1320,93 @@ static bool CanWriteToFile(const TCHAR* InFilename, bool bOverwriteExisting) return bIsFreeSpace && (bOverwriteExisting || IFileManager::Get().FileSize(InFilename) == -1); } -FString UMoviePipeline::ResolveFilenameFormatArguments(const FString& InFormatString, const FMoviePipelineFrameOutputState& InOutputState, const FStringFormatNamedArguments& InFormatOverrides) const +void UMoviePipeline::ResolveFilenameFormatArguments(const FString& InFormatString, const FStringFormatNamedArguments& InFormatOverrides, FString& OutFinalPath, FMoviePipelineFormatArgs& OutFinalFormatArgs, const FMoviePipelineFrameOutputState* InOutputState, const int32 InFrameNumberOffset) const { UMoviePipelineOutputSetting* OutputSettings = GetPipelineMasterConfig()->FindSetting(); check(OutputSettings); // Gather all the variables - FMoviePipelineFormatArgs FilenameFormatArgs; - FilenameFormatArgs.InJob = CurrentJob; + OutFinalFormatArgs = FMoviePipelineFormatArgs(); + OutFinalFormatArgs.InJob = CurrentJob; // From Settings - GetPipelineMasterConfig()->GetFilenameFormatArguments(FilenameFormatArgs); + GetPipelineMasterConfig()->GetFormatArguments(OutFinalFormatArgs, true); + + // Ensure they used relative frame numbers in the output so they get the right number of output frames. + bool bForceRelativeFrameNumbers = false; + if (InFormatString.Contains(TEXT("{frame")) && InOutputState && InOutputState->TimeData.IsTimeDilated() && !InFormatString.Contains(TEXT("_rel}"))) + { + UE_LOG(LogMovieRenderPipeline, Warning, TEXT("Time Dilation was used but output format does not use relative time, forcing relative numbers.")); + bForceRelativeFrameNumbers = true; + } // From Output State - InOutputState.GetFilenameFormatArguments(FilenameFormatArgs, OutputSettings->ZeroPadFrameNumbers, OutputSettings->FrameNumberOffset); + if (InOutputState) + { + OutFinalFormatArgs.FileMetadata = InOutputState->FileMetadata; + InOutputState->GetFilenameFormatArguments(OutFinalFormatArgs, OutputSettings->ZeroPadFrameNumbers, OutputSettings->FrameNumberOffset + InFrameNumberOffset, bForceRelativeFrameNumbers); + } // And from ourself { - FilenameFormatArgs.Arguments.Add(TEXT("date"), InitializationTime.ToString(TEXT("%Y.%m.%d"))); - FilenameFormatArgs.Arguments.Add(TEXT("time"), InitializationTime.ToString(TEXT("%H.%M.%S"))); + OutFinalFormatArgs.FilenameArguments.Add(TEXT("date"), InitializationTime.ToString(TEXT("%Y.%m.%d"))); + OutFinalFormatArgs.FilenameArguments.Add(TEXT("time"), InitializationTime.ToString(TEXT("%H.%M.%S"))); + + FString VersionText = FString::Printf(TEXT("v%0*d"), 3, InitializationVersion); + + OutFinalFormatArgs.FilenameArguments.Add(TEXT("version"), VersionText); + + OutFinalFormatArgs.FileMetadata.Add(TEXT("unreal/jobDate"), InitializationTime.ToString(TEXT("%Y.%m.%d"))); + OutFinalFormatArgs.FileMetadata.Add(TEXT("unreal/jobTime"), InitializationTime.ToString(TEXT("%H.%M.%S"))); + OutFinalFormatArgs.FileMetadata.Add(TEXT("unreal/jobVersion"), InitializationVersion); // By default, we don't want to show frame duplication numbers. If we need to start writing them, // they need to come before the frame number (so that tools recognize them as a sequence). - FilenameFormatArgs.Arguments.Add(TEXT("file_dup"), FString()); + OutFinalFormatArgs.FilenameArguments.Add(TEXT("file_dup"), FString()); } // Overwrite the variables with overrides if needed. This allows different requesters to share the same variables (ie: filename extension, render pass name) for (const TPair& KVP : InFormatOverrides) { - FilenameFormatArgs.Arguments.Add(KVP); + OutFinalFormatArgs.FilenameArguments.Add(KVP); } // No extension should be provided at this point, because we need to tack the extension onto the end after appending numbers (in the event of no overwrites) - FString BaseFilename = FString::Format(*InFormatString, FilenameFormatArgs.Arguments); + FString BaseFilename = FString::Format(*InFormatString, OutFinalFormatArgs.FilenameArguments); FPaths::NormalizeFilename(BaseFilename); // If we end with a "." character, remove it. The extension will put it back on. We can end up with this sometimes after resolving file format strings, ie: // {sequence_name}.{frame_number} becomes {sequence_name}. for videos (which can't use frame_numbers). BaseFilename.RemoveFromEnd(TEXT(".")); - FString Extension = FString::Format(TEXT(".{ext}"), FilenameFormatArgs.Arguments); + FString Extension = FString::Format(TEXT(".{ext}"), OutFinalFormatArgs.FilenameArguments); FString ThisTry = BaseFilename + Extension; if (CanWriteToFile(*ThisTry, OutputSettings->bOverrideExistingOutput)) { - return ThisTry; + OutFinalPath = ThisTry; + return; } int32 DuplicateIndex = 2; while(true) { - FilenameFormatArgs.Arguments.Add(TEXT("file_dup"), FString::Printf(TEXT("_(%d)"), DuplicateIndex)); + OutFinalFormatArgs.FilenameArguments.Add(TEXT("file_dup"), FString::Printf(TEXT("_(%d)"), DuplicateIndex)); // Re-resolve the format string now that we've reassigned frame_dup to a number. - ThisTry = FString::Format(*InFormatString, FilenameFormatArgs.Arguments) + Extension; + ThisTry = FString::Format(*InFormatString, OutFinalFormatArgs.FilenameArguments) + Extension; // If the file doesn't exist, we can use that, else, increment the index and try again if (CanWriteToFile(*ThisTry, OutputSettings->bOverrideExistingOutput)) { - return ThisTry; + OutFinalPath = ThisTry; + return; } ++DuplicateIndex; } - - return ThisTry; } void UMoviePipeline::SetProgressWidgetVisible(bool bVisible) diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineBlueprintLibrary.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineBlueprintLibrary.cpp index 13ce14d2f4de..a74bfc805cf0 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineBlueprintLibrary.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineBlueprintLibrary.cpp @@ -18,6 +18,9 @@ #include "Sections/MovieSceneCameraCutSection.h" #include "MovieSceneTimeHelpers.h" #include "MoviePipelineQueue.h" +#include "MoviePipelineOutputSetting.h" +#include "HAL/FileManager.h" +#include "Internationalization/Regex.h" EMovieRenderPipelineState UMoviePipelineBlueprintLibrary::GetPipelineState(const UMoviePipeline* InPipeline) { @@ -78,18 +81,17 @@ void UMoviePipelineBlueprintLibrary::GetOverallOutputFrames(const UMoviePipeline } } -FText UMoviePipelineBlueprintLibrary::GetCurrentSegmentName(UMoviePipeline* InMoviePipeline) +void UMoviePipelineBlueprintLibrary::GetCurrentSegmentName(UMoviePipeline* InMoviePipeline, FText& OutOuterName, FText& OutInnerName) { if (InMoviePipeline) { int32 ShotIndex = InMoviePipeline->GetCurrentShotIndex(); if (ShotIndex < InMoviePipeline->GetActiveShotList().Num()) { - return FText::FromString(InMoviePipeline->GetActiveShotList()[ShotIndex]->InnerName); + OutOuterName = FText::FromString(InMoviePipeline->GetActiveShotList()[ShotIndex]->OuterName); + OutInnerName = FText::FromString(InMoviePipeline->GetActiveShotList()[ShotIndex]->InnerName); } } - - return FText(); } FDateTime UMoviePipelineBlueprintLibrary::GetJobInitializationTime(const UMoviePipeline* InMoviePipeline) @@ -509,3 +511,77 @@ void UMoviePipelineBlueprintLibrary::UpdateJobShotListFromSequence(ULevelSequenc InJob->ShotInfo.Reset(); InJob->ShotInfo = NewShots; } + +int32 UMoviePipelineBlueprintLibrary::ResolveVersionNumber(const UMoviePipeline* InMoviePipeline) +{ + if (!InMoviePipeline) + { + return -1; + } + + UMoviePipelineOutputSetting* OutputSettings = InMoviePipeline->GetPipelineMasterConfig()->FindSetting(); + if (!OutputSettings->bAutoVersion) + { + return OutputSettings->VersionNumber; + } + + // Calculate a version number by looking at the output path and then scanning for a version token. + FString FileNameFormatString = OutputSettings->OutputDirectory.Path / OutputSettings->FileNameFormat; + + FString FinalPath; + FMoviePipelineFormatArgs FinalFormatArgs; + FStringFormatNamedArguments Overrides; + Overrides.Add(TEXT("version"), TEXT("{version}")); // Force the Version string to stay as {version} so we can substring based on it later. + + InMoviePipeline->ResolveFilenameFormatArguments(FileNameFormatString, Overrides, FinalPath, FinalFormatArgs); + FinalPath = FPaths::ConvertRelativePathToFull(FinalPath); + FPaths::NormalizeFilename(FinalPath); + + // If they're using the version token, try to resolve the directory that the files are in. + if (FinalPath.Contains(TEXT("{version}"))) + { + int32 HighestVersion = 0; + + // FinalPath can have {version} either in a folder name or in a file name. We need to find the 'parent' of either the file or folder that contains it. We can do this by substringing + // for {version} and then finding the last "/" character, which will be the containing folder. + int32 VersionStringIndex = FinalPath.Find(TEXT("{version}"), ESearchCase::Type::IgnoreCase, ESearchDir::Type::FromStart); + if (VersionStringIndex >= 0) + { + int32 LastParentFolder = FinalPath.Find(TEXT("/"), ESearchCase::Type::IgnoreCase, ESearchDir::Type::FromEnd, VersionStringIndex); + FinalPath.LeftInline(LastParentFolder + 1); + + // Now that we have the parent folder of either the folder with the version token, or the file with the version token, we will + // look through all immediate children and scan for version tokens so we can find the highest one. + const FRegexPattern VersionSearchPattern(TEXT("v([0-9]{3})")); + TArray FilesAndFoldersInDirectory; + IFileManager& FileManager = IFileManager::Get(); + FileManager.FindFiles(FilesAndFoldersInDirectory, *(FinalPath / TEXT("*.*")), true, true); + + for (const FString& Path : FilesAndFoldersInDirectory) + { + FRegexMatcher Regex(VersionSearchPattern, *Path); + if (Regex.FindNext()) + { + FString Result = Regex.GetCaptureGroup(0); + if (Result.Len() > 0) + { + // Strip the "v" token off, expected pattern is vXXX + Result.RightChopInline(1); + } + + int32 VersionNumber = 0; + LexFromString(VersionNumber, *Result); + if (VersionNumber > HighestVersion) + { + HighestVersion = VersionNumber; + } + } + } + + } + + return HighestVersion + 1; + } + + return 0; +} \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineFCPXMLExporterSetting.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineFCPXMLExporterSetting.cpp new file mode 100644 index 000000000000..db9790af85e9 --- /dev/null +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineFCPXMLExporterSetting.cpp @@ -0,0 +1,123 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MoviePipelineFCPXMLExporterSetting.h" +#include "MoviePipeline.h" +#include "MoviePipelineOutputSetting.h" +#include "MoviePipelineMasterConfig.h" +#include "Misc/FileHelper.h" +#include "HAL/PlatformFileManager.h" + +#if WITH_EDITOR +#include "FCPXML/FCPXMLMovieSceneTranslator.h" +#include "MovieSceneExportMetadata.h" +#include "MovieSceneToolHelpers.h" +#endif +#include "LevelSequence.h" + +// For logs +#include "MovieRenderPipelineCoreModule.h" + +void UMoviePipelineFCPXMLExporter::BeginExportImpl() +{ + bHasFinishedExporting = true; + +#if WITH_EDITOR + UMoviePipelineOutputSetting* OutputSetting = GetPipeline()->GetPipelineMasterConfig()->FindSetting(); + + // Use our file name format on the end of the shared common directory. + FString FileNameFormatString = OutputSetting->OutputDirectory.Path / FileNameFormat; + + FStringFormatNamedArguments FormatOverrides; + FormatOverrides.Add(TEXT("ext"), TEXT("xml")); + + // Create a full absolute path + FMoviePipelineFormatArgs TempFormatArgs; + GetPipeline()->ResolveFilenameFormatArguments(FileNameFormatString, FormatOverrides, FilePath, TempFormatArgs); + + bool bSuccess = EnsureWritableFile(); + + if (bSuccess) + { + ULevelSequence* Sequence = GetPipeline()->GetTargetSequence(); + UMovieScene* MovieScene = Sequence->GetMovieScene(); + if (!MovieScene) + { + return; + } + + UMovieSceneCinematicShotTrack* ShotTrack = MovieScene->FindMasterTrack(); + if (!ShotTrack) + { + return; + } + + FString FilenameFormat = OutputSetting->FileNameFormat; + int32 HandleFrames = OutputSetting->HandleFrameCount; + FFrameRate FrameRate = GetPipeline()->GetPipelineMasterConfig()->GetEffectiveFrameRate(Sequence); + uint32 ResX = OutputSetting->OutputResolution.X; + uint32 ResY = OutputSetting->OutputResolution.Y; + FString MovieExtension = ".avi"; + + FFCPXMLExporter* Exporter = new FFCPXMLExporter; + + TSharedRef ExportContext(new FMovieSceneTranslatorContext); + ExportContext->Init(); + + switch (DataSource) + { + case FCPXMLExportDataSource::OutputMetadata: + { + const FMovieSceneExportMetadata& OutputMetadata = GetPipeline()->GetOutputMetadata(); + bSuccess = Exporter->Export(MovieScene, FilenameFormat, FrameRate, ResX, ResY, HandleFrames, FilePath, ExportContext, MovieExtension, &OutputMetadata); + break; + } + case FCPXMLExportDataSource::SequenceData: + { + bSuccess = Exporter->Export(MovieScene, FilenameFormat, FrameRate, ResX, ResY, HandleFrames, FilePath, ExportContext, MovieExtension); + break; + } + } + + // Log any messages in context + MovieSceneToolHelpers::MovieSceneTranslatorLogMessages(Exporter, ExportContext, false); + + delete Exporter; + } + + if (!bSuccess) + { + UE_LOG(LogMovieRenderPipeline, Error, TEXT("Failed to write Final Cut Pro XML")); + } +#else + UE_LOG(LogMovieRenderPipeline, Error, TEXT("Final Cut Pro XML writing only supported in editor.")); +#endif +} + +bool UMoviePipelineFCPXMLExporter::EnsureWritableFile() +{ + FString Directory = FPaths::GetPath(FilePath); + + if (!IFileManager::Get().DirectoryExists(*Directory)) + { + IFileManager::Get().MakeDirectory(*Directory); + } + + UMoviePipelineOutputSetting* OutputSetting = GetPipeline()->GetPipelineMasterConfig()->FindSetting(); + + // If the file doesn't exist, we're ok to continue + if (IFileManager::Get().FileSize(*FilePath) == -1) + { + return true; + } + // If we're allowed to overwrite the file, and we deleted it ok, we can continue + else if (OutputSetting->bOverrideExistingOutput && FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*FilePath)) + { + return true; + } + // We can't write to the file + else + { + UE_LOG(LogMovieRenderPipeline, Error, TEXT("Failed to write Final Cut Pro XML to '%s'. Should Overwrite: %d - If we should have overwritten the file, we failed to delete the file. If we shouldn't have overwritten the file the file already exists so we can't replace it."), *FilePath, OutputSetting->bOverrideExistingOutput); + return false; + } +} \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineInProcessExecutor.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineInProcessExecutor.cpp index aad2066b7c0d..230b7e1c18f2 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineInProcessExecutor.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineInProcessExecutor.cpp @@ -9,56 +9,72 @@ #include "Kismet/GameplayStatics.h" #include "MovieRenderPipelineCoreModule.h" #include "Misc/PackageName.h" +#include "Engine/GameEngine.h" #define LOCTEXT_NAMESPACE "MoviePipelineInProcessExecutor" + void UMoviePipelineInProcessExecutor::Start(const UMoviePipelineExecutorJob* InJob) { - FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &UMoviePipelineInProcessExecutor::OnMapLoadFinished); + UWorld* World = FindCurrentWorld(); - // Force the engine into fixed timestep mode. There may be a global delay on the job that passes a fixed - // number of frames, so we want those frames to always pass the same amount of time for determinism. - ULevelSequence* LevelSequence = CastChecked(InJob->Sequence.TryLoad()); - if (LevelSequence) - { - FApp::SetUseFixedTimeStep(true); - FApp::SetFixedDeltaTime(InJob->GetConfiguration()->GetEffectiveFrameRate(LevelSequence).AsInterval()); + if (bUseCurrentLevel) + { + if (!World) + { + UE_LOG(LogMovieRenderPipeline, Warning, TEXT("Unable to start movie pipeline job. No current map.")); + OnIndividualPipelineFinished(nullptr); + return; + } + + if (World != InJob->Map.ResolveObject()) + { + UE_LOG(LogMovieRenderPipeline, Warning, TEXT("Unable to start movie pipeline job. Current map '%s' does not match job's map: '%s'"), *GetNameSafe(World), *InJob->Map.GetAssetPathName().ToString()); + OnIndividualPipelineFinished(nullptr); + return; + } + + UE_LOG(LogMovieRenderPipeline, Log, TEXT("Starting %s"), *GetNameSafe(World)); } - // We were launched into an empty map so we'll look at our job and figure out which map we should load. - // Get the next job in the queue - FString MapOptions; - + BackupState(); + // Initialize the transient settings so that they will exist in time for the GameOverrides check. InJob->GetConfiguration()->InitializeTransientSettings(); - TArray AllSettings = InJob->GetConfiguration()->GetAllSettings(); - UMoviePipelineSetting** GameOverridesPtr = AllSettings.FindByPredicate([](UMoviePipelineSetting* InSetting) { return InSetting->GetClass() == UMoviePipelineGameOverrideSetting::StaticClass(); }); - if (GameOverridesPtr) + ModifyState(InJob); + + if (bUseCurrentLevel) { - UMoviePipelineSetting* Setting = *GameOverridesPtr; - if (Setting) + OnMapLoadFinished(World); + } + else + { + // We were launched into an empty map so we'll look at our job and figure out which map we should load. + // Get the next job in the queue + FString MapOptions; + + TArray AllSettings = InJob->GetConfiguration()->GetAllSettings(); + UMoviePipelineSetting** GameOverridesPtr = AllSettings.FindByPredicate([](UMoviePipelineSetting* InSetting) { return InSetting->GetClass() == UMoviePipelineGameOverrideSetting::StaticClass(); }); + if (GameOverridesPtr) { - UMoviePipelineGameOverrideSetting* GameOverrideSetting = CastChecked(Setting); - if (GameOverrideSetting->GameModeOverride) + UMoviePipelineSetting* Setting = *GameOverridesPtr; + if (Setting) { - FString GameModeOverride = FPackageName::GetShortName(*GameOverrideSetting->GameModeOverride->GetPathName()); - MapOptions = TEXT("?game=") + GameModeOverride; + UMoviePipelineGameOverrideSetting* GameOverrideSetting = CastChecked(Setting); + if (GameOverrideSetting->GameModeOverride) + { + FString GameModeOverride = FPackageName::GetShortName(*GameOverrideSetting->GameModeOverride->GetPathName()); + MapOptions = TEXT("?game=") + GameModeOverride; + } + } - } - } - UWorld* LastLoadedWorld = nullptr; - for (const FWorldContext& WorldContext : GEngine->GetWorldContexts()) - { - if (WorldContext.WorldType == EWorldType::Game) - { - LastLoadedWorld = WorldContext.World(); - } + FString LevelPath = InJob->Map.GetLongPackageName(); + UE_LOG(LogMovieRenderPipeline, Log, TEXT("About to load target map %s"), *LevelPath); + FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &UMoviePipelineInProcessExecutor::OnMapLoadFinished); + UGameplayStatics::OpenLevel(World, FName(*LevelPath), true, MapOptions); } - - UE_LOG(LogMovieRenderPipeline, Log, TEXT("About to load target map %s"), *InJob->Map.GetAssetPathName().ToString()); - UGameplayStatics::OpenLevel(LastLoadedWorld, InJob->Map.GetAssetPathName(), true, MapOptions); } void UMoviePipelineInProcessExecutor::OnMapLoadFinished(UWorld* NewWorld) @@ -77,7 +93,14 @@ void UMoviePipelineInProcessExecutor::OnMapLoadFinished(UWorld* NewWorld) UMoviePipelineExecutorJob* CurrentJob = Queue->GetJobs()[CurrentPipelineIndex]; - ActiveMoviePipeline = NewObject(NewWorld, TargetPipelineClass); + UClass* MoviePipelineClass = TargetPipelineClass.Get(); + if (MoviePipelineClass == nullptr) + { + MoviePipelineClass = UMoviePipeline::StaticClass(); + } + + ActiveMoviePipeline = NewObject(NewWorld, MoviePipelineClass); + ActiveMoviePipeline->DebugWidgetClass = DebugWidgetClass; // We allow users to set a multi-frame delay before we actually run the Initialization function and start thinking. // This solves cases where there are engine systems that need to finish loading before we do anything. @@ -151,8 +174,113 @@ void UMoviePipelineInProcessExecutor::OnMoviePipelineFinished(UMoviePipeline* In // Null these out now since OnIndividualPipelineFinished might invoke something that causes a GC // and we want them to go away with the GC. ActiveMoviePipeline = nullptr; + + RestoreState(); // Now that another frame has passed and we should be OK to start another PIE session, notify our owner. OnIndividualPipelineFinished(MoviePipeline); } + +UWorld* UMoviePipelineInProcessExecutor::FindCurrentWorld() +{ + UWorld* World = nullptr; + for (const FWorldContext& WorldContext : GEngine->GetWorldContexts()) + { + if (WorldContext.WorldType == EWorldType::Game) + { + World = WorldContext.World(); + } +#if WITH_EDITOR + else if (GIsEditor && WorldContext.WorldType == EWorldType::PIE) + { + World = WorldContext.World(); + if (World) + { + return World; + } + } +#endif + } + + return World; +} + +void UMoviePipelineInProcessExecutor::BackupState() +{ + SavedState.bBackedUp = true; + SavedState.bUseFixedTimeStep = FApp::UseFixedTimeStep(); + SavedState.FixedDeltaTime = FApp::GetFixedDeltaTime(); + + UWorld* World = FindCurrentWorld(); + if (World && World->GetGameInstance()) + { + if (APlayerController* PlayerController = World->GetGameInstance()->GetFirstLocalPlayerController()) + { + SavedState.bCinematicMode = PlayerController->bCinematicMode; + SavedState.bHidePlayer = PlayerController->bHidePawnInCinematicMode; + } + } + + SavedState.WindowTitle.Reset(); + if (UGameEngine* GameEngine = Cast(GEngine)) + { + TSharedPtr GameViewportWindow = GameEngine->GameViewportWindow.Pin(); + if (GameViewportWindow.IsValid()) + { + SavedState.WindowTitle = GameViewportWindow->GetTitle(); + } + } +} + +void UMoviePipelineInProcessExecutor::ModifyState(const UMoviePipelineExecutorJob* InJob) +{ + UWorld* World = FindCurrentWorld(); + if (World && World->GetGameInstance()) + { + if (APlayerController* PlayerController = World->GetGameInstance()->GetFirstLocalPlayerController()) + { + const bool bCinematicMode = true; + const bool bHidePlayer = true; + const bool bHideHUD = true; + const bool bPreventMovement = true; + const bool bPreventTurning = true; + PlayerController->SetCinematicMode(bCinematicMode, bHidePlayer, bHideHUD, bPreventMovement, bPreventTurning); + } + } + + // Force the engine into fixed timestep mode. There may be a global delay on the job that passes a fixed + // number of frames, so we want those frames to always pass the same amount of time for determinism. + if (ULevelSequence* LevelSequence = CastChecked(InJob->Sequence.TryLoad())) + { + FApp::SetUseFixedTimeStep(true); + FApp::SetFixedDeltaTime(InJob->GetConfiguration()->GetEffectiveFrameRate(LevelSequence).AsInterval()); + } +} + +void UMoviePipelineInProcessExecutor::RestoreState() +{ + if (SavedState.bBackedUp) + { + SavedState.bBackedUp = false; + FApp::SetUseFixedTimeStep(SavedState.bUseFixedTimeStep); + FApp::SetFixedDeltaTime(SavedState.FixedDeltaTime); + + UWorld* World = FindCurrentWorld(); + if (World && World->GetGameInstance()) + { + if (APlayerController* PlayerController = World->GetGameInstance()->GetFirstLocalPlayerController()) + { + PlayerController->SetCinematicMode(SavedState.bCinematicMode, SavedState.bHidePlayer, true, true, true); + PlayerController->ResetIgnoreInputFlags(); + } + } + + if (SavedState.WindowTitle.IsSet()) + { + UKismetSystemLibrary::SetWindowTitle(SavedState.WindowTitle.GetValue()); + SavedState.WindowTitle.Reset(); + } + } +} + #undef LOCTEXT_NAMESPACE // "MoviePipelineInProcessExecutor" diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineMasterConfig.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineMasterConfig.cpp index 303d47f1a7dc..87c4e3ba7e52 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineMasterConfig.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineMasterConfig.cpp @@ -89,7 +89,7 @@ UMoviePipelineShotConfig* UMoviePipelineMasterConfig::GetConfigForShot(const FSt return OutConfig; } -void UMoviePipelineMasterConfig::GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const +void UMoviePipelineMasterConfig::GetFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs, const bool bIncludeAllSettings) const { // Add "global" ones not specific to a setting. { @@ -106,18 +106,23 @@ void UMoviePipelineMasterConfig::GetFilenameFormatArguments(FMoviePipelineFormat FrameRate = GetEffectiveFrameRate(Cast(InOutFormatArgs.InJob->Sequence.TryLoad())).AsDecimal(); } - InOutFormatArgs.Arguments.Add(TEXT("level_name"), LevelName); - InOutFormatArgs.Arguments.Add(TEXT("sequence_name"), SequenceName); - InOutFormatArgs.Arguments.Add(TEXT("frame_rate"), FrameRate); + InOutFormatArgs.FilenameArguments.Add(TEXT("level_name"), LevelName); + InOutFormatArgs.FilenameArguments.Add(TEXT("sequence_name"), SequenceName); + InOutFormatArgs.FilenameArguments.Add(TEXT("frame_rate"), FrameRate); + + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/levelName"), LevelName); + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/sequenceName"), SequenceName); + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/frameRate"), FrameRate); + // Normally these are filled when resolving the file name by the job (so that the time is shared), but stub them in here so // they show up in the UI with a value. FDateTime CurrentTime = FDateTime::Now(); - InOutFormatArgs.Arguments.Add(TEXT("date"), CurrentTime.ToString(TEXT("%Y.%m.%d"))); - InOutFormatArgs.Arguments.Add(TEXT("year"), CurrentTime.ToString(TEXT("%Y"))); - InOutFormatArgs.Arguments.Add(TEXT("month"), CurrentTime.ToString(TEXT("%m"))); - InOutFormatArgs.Arguments.Add(TEXT("day"), CurrentTime.ToString(TEXT("%d"))); - InOutFormatArgs.Arguments.Add(TEXT("time"), CurrentTime.ToString(TEXT("%H.%M.%S"))); + InOutFormatArgs.FilenameArguments.Add(TEXT("date"), CurrentTime.ToString(TEXT("%Y.%m.%d"))); + InOutFormatArgs.FilenameArguments.Add(TEXT("year"), CurrentTime.ToString(TEXT("%Y"))); + InOutFormatArgs.FilenameArguments.Add(TEXT("month"), CurrentTime.ToString(TEXT("%m"))); + InOutFormatArgs.FilenameArguments.Add(TEXT("day"), CurrentTime.ToString(TEXT("%d"))); + InOutFormatArgs.FilenameArguments.Add(TEXT("time"), CurrentTime.ToString(TEXT("%H.%M.%S"))); // Let the output state fill out some too, since its the keeper of the information. UMoviePipelineOutputSetting* OutputSettings = FindSetting(); @@ -125,14 +130,16 @@ void UMoviePipelineMasterConfig::GetFilenameFormatArguments(FMoviePipelineFormat FMoviePipelineFrameOutputState BlankOutputState; BlankOutputState.OutputFrameNumber = 0; // It gets initialized to -1 but looks funny in the UI since the actual output would be zero. - BlankOutputState.GetFilenameFormatArguments(InOutFormatArgs, OutputSettings->ZeroPadFrameNumbers, OutputSettings->FrameNumberOffset); + BlankOutputState.GetFilenameFormatArguments(InOutFormatArgs, OutputSettings->ZeroPadFrameNumbers, OutputSettings->FrameNumberOffset, false); } // Let each setting provide its own set of key/value pairs. { - for (UMoviePipelineSetting* Setting : GetUserSettings()) + // The UI will only show user customized settings but actually writing to disk should use all. + TArray TargetSettings = bIncludeAllSettings ? GetAllSettings() : GetUserSettings(); + for (UMoviePipelineSetting* Setting : TargetSettings) { - Setting->GetFilenameFormatArguments(InOutFormatArgs); + Setting->GetFormatArguments(InOutFormatArgs); } // ToDo: Should shots be able to provide arguments too? They're only overrides, and diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineOutputBuilder.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineOutputBuilder.cpp index 37688d463e5f..df73018b80c6 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineOutputBuilder.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineOutputBuilder.cpp @@ -81,6 +81,13 @@ void FMoviePipelineOutputMerger::OnCompleteRenderPassDataAvailable_AnyThread(TUn return; } + // Merge the metadata from each output state. Metadata is part of the output state but gets forked when + // we submit different render passes, so we need to merge it again. Doesn't handle conflicts. + for (const TPair& KVP : Payload->SampleState.OutputState.FileMetadata) + { + OutputFrame->FrameOutputState.FileMetadata.Add(KVP.Key, KVP.Value); + } + // If this data was expected and this frame is still in progress, pass the data to the frame. OutputFrame->ImageOutputData.FindOrAdd(Payload->PassIdentifier) = MoveTemp(InData); diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineOutputSetting.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineOutputSetting.cpp index 95ccc987b173..c97ba48ed921 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineOutputSetting.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineOutputSetting.cpp @@ -17,6 +17,8 @@ UMoviePipelineOutputSetting::UMoviePipelineOutputSetting() , bUseCustomPlaybackRange(false) , CustomStartFrame(0) , CustomEndFrame(0) + , VersionNumber(1) + , bAutoVersion(true) , ZeroPadFrameNumbers(4) , FrameNumberOffset(0) , bDisableToneCurve(false) @@ -39,6 +41,7 @@ void UMoviePipelineOutputSetting::PostLoad() } } +#if WITH_EDITOR FText UMoviePipelineOutputSetting::GetFooterText(UMoviePipelineExecutorJob* InJob) const { FTextBuilder TextBuilder; @@ -52,10 +55,10 @@ FText UMoviePipelineOutputSetting::GetFooterText(UMoviePipelineExecutorJob* InJo UMoviePipelineMasterConfig* MasterConfig = GetTypedOuter(); if (MasterConfig) { - MasterConfig->GetFilenameFormatArguments(FormatArgs); + MasterConfig->GetFormatArguments(FormatArgs); } - for (const TPair& KVP : FormatArgs.Arguments) + for (const TPair& KVP : FormatArgs.FilenameArguments) { FStringFormatOrderedArguments OrderedArgs = { KVP.Key, KVP.Value }; FString FormattedArgs = FString::Format(TEXT("{0} => {1}"), OrderedArgs); @@ -65,14 +68,26 @@ FText UMoviePipelineOutputSetting::GetFooterText(UMoviePipelineExecutorJob* InJo return TextBuilder.ToText(); } +#endif -void UMoviePipelineOutputSetting::GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const +void UMoviePipelineOutputSetting::GetFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const { // Resolution Arguments { FString Resolution = FString::Printf(TEXT("%d_%d"), OutputResolution.X, OutputResolution.Y); - InOutFormatArgs.Arguments.Add(TEXT("output_resolution"), Resolution); - InOutFormatArgs.Arguments.Add(TEXT("output_width"), OutputResolution.X); - InOutFormatArgs.Arguments.Add(TEXT("output_height"), OutputResolution.Y); + InOutFormatArgs.FilenameArguments.Add(TEXT("output_resolution"), Resolution); + InOutFormatArgs.FilenameArguments.Add(TEXT("output_width"), OutputResolution.X); + InOutFormatArgs.FilenameArguments.Add(TEXT("output_height"), OutputResolution.Y); } + + if (bAutoVersion) + { + InOutFormatArgs.FilenameArguments.Add(TEXT("version"), TEXT("v00x")); + } + else + { + FString VersionText = FString::Printf(TEXT("v%0*d"), 3, VersionNumber); + InOutFormatArgs.FilenameArguments.Add(TEXT("version"), VersionText); + } + } \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineQueue.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineQueue.cpp index a9b0acf62191..963c04807d0f 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineQueue.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineQueue.cpp @@ -83,6 +83,7 @@ void UMoviePipelineQueue::CopyFrom(UMoviePipelineQueue* InQueue) QueueSerialNumber++; } +#if WITH_EDITOR void UMoviePipelineExecutorJob::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); @@ -98,6 +99,7 @@ void UMoviePipelineExecutorJob::PostEditChangeProperty(FPropertyChangedEvent& Pr // fields that don't change often but do need to be per job. SaveConfig(); } +#endif void UMoviePipelineExecutorJob::SetSequence(FSoftObjectPath InSequence) { diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineQueueEngineSubsystem.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineQueueEngineSubsystem.cpp new file mode 100644 index 000000000000..987a044809d5 --- /dev/null +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineQueueEngineSubsystem.cpp @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MoviePipelineQueueEngineSubsystem.h" +#include "Modules/ModuleManager.h" +#include "MoviePipeline.h" + +UMoviePipelineExecutorBase* UMoviePipelineQueueEngineSubsystem::RenderQueueWithExecutor(TSubclassOf InExecutorType) +{ + RenderQueueWithExecutorInstance(NewObject(this, InExecutorType)); + return ActiveExecutor; +} + +void UMoviePipelineQueueEngineSubsystem::RenderQueueWithExecutorInstance(UMoviePipelineExecutorBase* InExecutor) +{ + if(!ensureMsgf(!IsRendering(), TEXT("RenderQueueWithExecutor cannot be called while already rendering!"))) + { + FFrame::KismetExecutionMessage(TEXT("Render already in progress."), ELogVerbosity::Error); + return; + } + + if (!InExecutor) + { + FFrame::KismetExecutionMessage(TEXT("Invalid executor supplied."), ELogVerbosity::Error); + return; + } + + ActiveExecutor = InExecutor; + ActiveExecutor->OnExecutorFinished().AddUObject(this, &UMoviePipelineQueueEngineSubsystem::OnExecutorFinished); + ActiveExecutor->Execute(GetQueue()); +} + +void UMoviePipelineQueueEngineSubsystem::OnExecutorFinished(UMoviePipelineExecutorBase* InPipelineExecutor, bool bSuccess) +{ + ActiveExecutor = nullptr; +} + diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineRendering.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineRendering.cpp index e7c53e8617e0..4170cfadc003 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineRendering.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineRendering.cpp @@ -180,6 +180,23 @@ void UMoviePipeline::RenderFrame() FrameInfo.PrevViewRotation = FrameInfo.CurrViewRotation; } + // Add appropriate metadata here that is shared by all passes. + { + CachedOutputState.FileMetadata.Add(TEXT("unreal/camera/curPos/x"), FrameInfo.CurrViewLocation.X); + CachedOutputState.FileMetadata.Add(TEXT("unreal/camera/curPos/y"), FrameInfo.CurrViewLocation.Y); + CachedOutputState.FileMetadata.Add(TEXT("unreal/camera/curPos/z"), FrameInfo.CurrViewLocation.Z); + CachedOutputState.FileMetadata.Add(TEXT("unreal/camera/curRot/pitch"), FrameInfo.CurrViewRotation.Pitch); + CachedOutputState.FileMetadata.Add(TEXT("unreal/camera/curRot/yaw"), FrameInfo.CurrViewRotation.Yaw); + CachedOutputState.FileMetadata.Add(TEXT("unreal/camera/curRot/roll"), FrameInfo.CurrViewRotation.Roll); + + CachedOutputState.FileMetadata.Add(TEXT("unreal/camera/prevPos/x"), FrameInfo.PrevViewLocation.X); + CachedOutputState.FileMetadata.Add(TEXT("unreal/camera/prevPos/y"), FrameInfo.PrevViewLocation.Y); + CachedOutputState.FileMetadata.Add(TEXT("unreal/camera/prevPos/z"), FrameInfo.PrevViewLocation.Z); + CachedOutputState.FileMetadata.Add(TEXT("unreal/camera/prevRot/pitch"), FrameInfo.PrevViewRotation.Pitch); + CachedOutputState.FileMetadata.Add(TEXT("unreal/camera/prevRot/yaw"), FrameInfo.PrevViewRotation.Yaw); + CachedOutputState.FileMetadata.Add(TEXT("unreal/camera/prevRot/roll"), FrameInfo.PrevViewRotation.Roll); + } + if (CurrentCameraCut.State != EMovieRenderShotState::Rendering) { // We can optimize some of the settings for 'special' frames we may be rendering, ie: we render once for motion vectors, but @@ -367,6 +384,36 @@ void UMoviePipeline::RenderFrame() SetProgressWidgetVisible(true); } +#if WITH_EDITOR +void UMoviePipeline::AddFrameToOutputMetadata(const FString& ClipName, const FString& ImageSequenceFileName, const FMoviePipelineFrameOutputState& FrameOutputState, const FString& Extension, const bool bHasAlpha) +{ + if (FrameOutputState.ShotIndex < 0 || FrameOutputState.ShotIndex >= ActiveShotList.Num()) + { + UE_LOG(LogMovieRenderPipeline, Error, TEXT("ShotIndex %d out of range"), FrameOutputState.ShotIndex); + return; + } + + FMovieSceneExportMetadataShot& ShotMetadata = OutputMetadata.Shots[FrameOutputState.ShotIndex]; + FMovieSceneExportMetadataClip& ClipMetadata = ShotMetadata.Clips.FindOrAdd(ClipName).FindOrAdd(Extension.ToUpper()); + + if (!ClipMetadata.IsValid()) + { + ClipMetadata.FileName = ImageSequenceFileName; + ClipMetadata.bHasAlpha = bHasAlpha; + } + + if (FrameOutputState.OutputFrameNumber < ClipMetadata.StartFrame) + { + ClipMetadata.StartFrame = FrameOutputState.OutputFrameNumber; + } + + if (FrameOutputState.OutputFrameNumber > ClipMetadata.EndFrame) + { + ClipMetadata.EndFrame = FrameOutputState.OutputFrameNumber; + } +} +#endif + void UMoviePipeline::AddOutputFuture(TFuture&& OutputFuture) { OutputFutures.Add(MoveTemp(OutputFuture)); diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineSurfaceReader.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineSurfaceReader.cpp index 87d5a517a767..e7d8c5fde888 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineSurfaceReader.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineSurfaceReader.cpp @@ -165,19 +165,42 @@ void FMoviePipelineSurfaceReader::CopyReadbackTexture_RenderThread(TUniqueFuncti int32 ActualSizeX = 0, ActualSizeY = 0; RHICmdList.MapStagingSurface(ReadbackTexture, ColorDataBuffer, ActualSizeX, ActualSizeY); - - - TArray OutputPixels; - int32 ExpectedSizeX = Size.X; int32 ExpectedSizeY = Size.Y; - OutputPixels.SetNumUninitialized(ExpectedSizeX * ExpectedSizeY); + + TUniquePtr PixelData; + uint8* TypeErasedPixels = nullptr; + int32 SizeOfColor = 0; + switch (PixelFormat) + { + case EPixelFormat::PF_FloatRGBA: + { + TUniquePtr> NewPixelData = MakeUnique < TImagePixelData>(FIntPoint(Size.X, Size.Y), InFramePayload); + NewPixelData->Pixels.SetNumUninitialized(ExpectedSizeX * ExpectedSizeY); + SizeOfColor = sizeof(FFloat16Color); + TypeErasedPixels = reinterpret_cast(NewPixelData->Pixels.GetData()); + PixelData = MoveTemp(NewPixelData); + break; + } + case EPixelFormat::PF_B8G8R8A8: + { + TUniquePtr> NewPixelData = MakeUnique < TImagePixelData>(FIntPoint(Size.X, Size.Y), InFramePayload); + NewPixelData->Pixels.SetNumUninitialized(ExpectedSizeX * ExpectedSizeY); + SizeOfColor = sizeof(FColor); + TypeErasedPixels = reinterpret_cast(NewPixelData->Pixels.GetData()); + PixelData = MoveTemp(NewPixelData); + break; + } + default: + check(0); // Unsupported, add a new switch statement. + } + // Due to padding, the actual size might be larger than the expected size. If they are the same, do a block copy. Otherwise copy // line by line. if (ExpectedSizeX == ActualSizeX && ExpectedSizeY == ActualSizeY) { - FMemory::BigBlockMemcpy(OutputPixels.GetData(), ColorDataBuffer, (ExpectedSizeX * ExpectedSizeY) * sizeof(FFloat16Color)); + FMemory::BigBlockMemcpy(TypeErasedPixels, ColorDataBuffer, (ExpectedSizeX * ExpectedSizeY) * SizeOfColor); } else { @@ -193,22 +216,20 @@ void FMoviePipelineSurfaceReader::CopyReadbackTexture_RenderThread(TUniqueFuncti int32 SrcPitchElem = ActualSizeX; int32 DstPitchElem = ExpectedSizeX; - const FFloat16Color* SrcColorData = (const FFloat16Color*)ColorDataBuffer; - FFloat16Color* DstColorData = (FFloat16Color*)OutputPixels.GetData(); + const uint8* SrcColorData = reinterpret_cast(ColorDataBuffer); // Copy one line at a time for (int32 RowIndex = 0; RowIndex < ExpectedSizeY; RowIndex++) { - const FFloat16Color* SrcPtr = &SrcColorData[RowIndex * SrcPitchElem]; - FFloat16Color* DstPtr = &DstColorData[RowIndex * DstPitchElem]; - FMemory::Memcpy(DstPtr, SrcPtr, DstPitchElem * sizeof(FFloat16Color)); + const void* SrcPtr = SrcColorData + RowIndex * SrcPitchElem * SizeOfColor; + void* DstPtr = TypeErasedPixels + RowIndex * DstPitchElem * SizeOfColor; + FMemory::Memcpy(DstPtr, SrcPtr, DstPitchElem * SizeOfColor); } } // Enqueue the Unmap before we broadcast the resulting pixels, though the broadcast shouldn't do anything blocking. RHICmdList.UnmapStagingSurface(ReadbackTexture); - TUniquePtr> PixelData = MakeUnique>(FIntPoint(ExpectedSizeX, ExpectedSizeY), TArray64(MoveTemp(OutputPixels)), InFramePayload); InFunctionCallback(MoveTemp(PixelData)); // Now that we've successfully used the surface, we trigger the Available event so that we can reuse this surface. This diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineTiming.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineTiming.cpp index a624d5a42ac4..191c3e3f6528 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineTiming.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineTiming.cpp @@ -470,7 +470,6 @@ void UMoviePipeline::TickProducingFrames() // Now that we've calculated our delta ticks, we need to multiply it by // time dilation so that we advance through the sequence as slow as we // advance through the world. - bool bWasAffectedByTimeDilation = false; if (!FMath::IsNearlyEqual(WorldTimeDilation, 1.f)) { UE_LOG(LogMovieRenderPipeline, VeryVerbose, TEXT("[%d] Modified FrameDeltaTime by a factor of %f to account for World Time Dilation."), GFrameCounter, WorldTimeDilation); @@ -478,7 +477,6 @@ void UMoviePipeline::TickProducingFrames() // This is technically always a frame behind as this function executes before Sequencer evaluates slow-mo tracks // and sets the World Time Dilation, but it is close enough for now. DeltaFrameTime = DeltaFrameTime * WorldTimeDilation; - bWasAffectedByTimeDilation = true; } /* @@ -548,7 +546,7 @@ void UMoviePipeline::TickProducingFrames() double FrameDeltaTime = FrameMetrics.TickResolution.AsSeconds(FFrameTime(DeltaFrameTime.GetFrame())); CachedOutputState.TimeData.FrameDeltaTime = FrameDeltaTime; CachedOutputState.TimeData.WorldSeconds = CachedOutputState.TimeData.WorldSeconds + FrameDeltaTime; - CachedOutputState.TimeData.bWasAffectedByTimeDilation = bWasAffectedByTimeDilation; + CachedOutputState.TimeData.TimeDilation = WorldTimeDilation; // The combination of shutter angle percentage, non-uniform render frame delta times and dividing by sample // count produce the correct length for motion blur in all cases. diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineVideoOutputBase.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineVideoOutputBase.cpp index e9e0268311f8..4f83cca9e958 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineVideoOutputBase.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineVideoOutputBase.cpp @@ -18,9 +18,12 @@ void UMoviePipelineVideoOutputBase::OnRecieveImageDataImpl(FMoviePipelineMergerO FImagePixelDataPayload* Payload = RenderPassData.Value->GetPayload(); // We need to resolve the filename format string. We combine the folder and file name into one long string first + FMoviePipelineFormatArgs FinalFormatArgs; FString FinalFilePath; + FString FinalVideoFileName; + FString ClipName; { - FString FileNameFormatString = OutputDirectory / OutputSettings->FileNameFormat; + FString FileNameFormatString = OutputSettings->FileNameFormat; // If we're writing more than one render pass out, we need to ensure the file name has the format string in it so we don't // overwrite the same file multiple times. Burn In overlays don't count because they get composited on top of an existing file. @@ -37,7 +40,15 @@ void UMoviePipelineVideoOutputBase::OnRecieveImageDataImpl(FMoviePipelineMergerO FormatOverrides.Add(TEXT("render_pass"), RenderPassData.Key.Name); FormatOverrides.Add(TEXT("ext"), GetFilenameExtension()); - FinalFilePath = GetPipeline()->ResolveFilenameFormatArguments(FileNameFormatString, InMergedOutputFrame->FrameOutputState, FormatOverrides); + GetPipeline()->ResolveFilenameFormatArguments(FileNameFormatString, FormatOverrides, FinalVideoFileName, FinalFormatArgs, &InMergedOutputFrame->FrameOutputState); + + FinalFilePath = OutputDirectory / FinalVideoFileName; + + // Create a deterministic clipname by file extension, and any trailing .'s + FMoviePipelineFormatArgs TempFormatArgs; + GetPipeline()->ResolveFilenameFormatArguments(FileNameFormatString, FormatOverrides, ClipName, TempFormatArgs, &InMergedOutputFrame->FrameOutputState); + ClipName.RemoveFromEnd(GetFilenameExtension()); + ClipName.RemoveFromEnd("."); } @@ -62,6 +73,7 @@ void UMoviePipelineVideoOutputBase::OnRecieveImageDataImpl(FMoviePipelineMergerO { AllWriters.Add(MoveTemp(NewWriter)); OutputWriter = AllWriters.Last().Get(); + OutputWriter->FormatArgs = FinalFormatArgs; Initialize_EncodeThread(OutputWriter); } @@ -82,6 +94,10 @@ void UMoviePipelineVideoOutputBase::OnRecieveImageDataImpl(FMoviePipelineMergerO this->WriteFrame_EncodeThread(OutputWriter, RawRenderPassData); // }); //OutstandingTasks.Add(Event); + +#if WITH_EDITOR + GetPipeline()->AddFrameToOutputMetadata(ClipName, FinalVideoFileName, InMergedOutputFrame->FrameOutputState, GetFilenameExtension(), Payload->bRequireTransparentOutput); +#endif } } @@ -133,6 +149,7 @@ void UMoviePipelineVideoOutputBase::FinalizeImpl() AllWriters.Empty(); } +#if WITH_EDITOR FText UMoviePipelineVideoOutputBase::GetFooterText(UMoviePipelineExecutorJob* InJob) const { if (!IsAudioSupported()) @@ -141,4 +158,5 @@ FText UMoviePipelineVideoOutputBase::GetFooterText(UMoviePipelineExecutorJob* In } return FText(); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MovieRenderPipelineCommandLine.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MovieRenderPipelineCommandLine.cpp index e9a833c9bf79..76bc3454d7cb 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MovieRenderPipelineCommandLine.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MovieRenderPipelineCommandLine.cpp @@ -14,11 +14,11 @@ #include "Misc/PackageName.h" #include "Misc/FileHelper.h" #include "UObject/UObjectHash.h" -#include "ObjectTools.h" #include "MoviePipelinePythonHostExecutor.h" #if WITH_EDITOR //#include "Editor.h" +#include "ObjectTools.h" #endif void FMovieRenderPipelineCoreModule::InitializeCommandLineMovieRender() diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipeline.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipeline.h index 729ebfa373b4..8bf21a31bda1 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipeline.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipeline.h @@ -4,6 +4,9 @@ #include "UObject/Object.h" #include "Engine/EngineCustomTimeStep.h" #include "MovieRenderPipelineDataTypes.h" +#if WITH_EDITOR +#include "MovieSceneExportMetadata.h" +#endif #include "MovieSceneTimeController.h" #include "Async/Future.h" #include "MoviePipeline.generated.h" @@ -123,8 +126,15 @@ public: UMoviePipelineExecutorJob* GetCurrentJob() const { return CurrentJob; } EMovieRenderPipelineState GetPipelineState() const { return PipelineState; } +#if WITH_EDITOR + const FMovieSceneExportMetadata& GetOutputMetadata() const { return OutputMetadata; } +#endif + FDateTime GetInitializationTime() const { return InitializationTime; } public: +#if WITH_EDITOR + void AddFrameToOutputMetadata(const FString& ClipName, const FString& ImageSequenceFileName, const FMoviePipelineFrameOutputState& FrameOutputState, const FString& Extension, const bool bHasAlpha); +#endif void AddOutputFuture(TFuture&& OutputFuture); void ProcessOutstandingFinishedFrames(); @@ -142,11 +152,16 @@ public: /** * Resolves the provided InFormatString by converting {format_strings} into settings provided by the master config. * @param InFormatString A format string (in the form of "{format_key1}_{format_key2}") to resolve. - * @param InOutputState The output state for frame information. * @param InFormatOverrides A series of Key/Value pairs to override particular format keys. Useful for things that * change based on the caller such as filename extensions. + * @return OutFinalPath The final filepath based on a combination of the format string, the format overrides, and the current output state. + * @return OutFinalFormatArgs The format arguments that were actually used to fill the format string (including file metadata) + * + * @param InOutputState (optional) The output state for frame information. + * @param InFrameNumberOffset (optional) Frame offset of the frame we want the filename for, if not the current frame + * as specified in InOutputState */ - FString ResolveFilenameFormatArguments(const FString& InFormatString, const FMoviePipelineFrameOutputState& InOutputState, const FStringFormatNamedArguments& InFormatOverrides) const; + void ResolveFilenameFormatArguments(const FString& InFormatString, const FStringFormatNamedArguments& InFormatOverrides, FString& OutFinalPath, FMoviePipelineFormatArgs& OutFinalFormatArgs, const FMoviePipelineFrameOutputState* InOutputState=nullptr, const int32 InFrameNumberOffset=0) const; protected: /** @@ -340,6 +355,9 @@ private: /** The time (in UTC) that Initialize was called. Used to track elapsed time. */ FDateTime InitializationTime; + /** The version number for this render. Detected when job starts to increment to the next version so that image sequences don't think it's a new version for every frame. */ + int32 InitializationVersion; + FMoviePipelineFrameOutputState CachedOutputState; MoviePipeline::FAudioState AudioState; @@ -384,11 +402,20 @@ public: /** A debug image sequence writer in the event they want to dump every sample generated on its own. */ IImageWriteQueue* ImageWriteQueue; + /** Optional widget for feedback during render */ + UPROPERTY(Transient) + TSubclassOf DebugWidgetClass; + private: /** Keep track of which job we're working on. This holds our Configuration + which shots we're supposed to render from it. */ UPROPERTY(Transient) UMoviePipelineExecutorJob* CurrentJob; +#if WITH_EDITOR + /** Keep track of clips we've exported, for building FCPXML and other project files */ + FMovieSceneExportMetadata OutputMetadata; +#endif + TArray> OutputFutures; FMovieSceneChanges SequenceChanges; }; diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineAntiAliasingSetting.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineAntiAliasingSetting.h index cb7fa7e8482f..deb3b9a50231 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineAntiAliasingSetting.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineAntiAliasingSetting.h @@ -61,12 +61,15 @@ protected: } - virtual void GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const override + virtual void GetFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const override { - Super::GetFilenameFormatArguments(InOutFormatArgs); + Super::GetFormatArguments(InOutFormatArgs); - InOutFormatArgs.Arguments.Add(TEXT("ts_count"), TemporalSampleCount); - InOutFormatArgs.Arguments.Add(TEXT("ss_count"), SpatialSampleCount); + InOutFormatArgs.FilenameArguments.Add(TEXT("ts_count"), TemporalSampleCount); + InOutFormatArgs.FilenameArguments.Add(TEXT("ss_count"), SpatialSampleCount); + + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/aa/temporalSampleCount"), TemporalSampleCount); + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/aa/spatialSampleCount"), SpatialSampleCount); } public: @@ -76,7 +79,7 @@ public: * increase the anti-aliasing quality of an sample, or have high quality anti-aliasing if you don't want * any motion blur due to accumulation over time in SampleCount. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = 1, ClampMin = 1), Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = 1, ClampMin = 1), Category = "Render Settings") int32 SpatialSampleCount; /** @@ -86,20 +89,20 @@ public: * samples we average together to produce a sub-step. (This means rendering complexity is * SampleCount * TileCount^2 * SpatialSampleCount * NumPasses). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = 1, ClampMin = 1), Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = 1, ClampMin = 1), Category = "Render Settings") int32 TemporalSampleCount; /** * Should we override the Project's anti-aliasing setting during a movie render? This can be useful to have * TAA on during normal work in the editor but force it off for high quality renders /w many spatial samples. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Render Settings") bool bOverrideAntiAliasing; /** * If we are overriding the AA method, what do we use? None will turn off anti-aliasing. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition="bOverrideAntiAliasing"), Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition="bOverrideAntiAliasing"), Category = "Render Settings") TEnumAsByte AntiAliasingMethod; /** @@ -109,7 +112,7 @@ public: * * This is more expensive than EngineWarmUpCount (which should be used for particle warm-ups, etc.) */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = 0, ClampMin = 0), AdvancedDisplay, Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = 0, ClampMin = 0), AdvancedDisplay, Category = "Render Settings") int32 RenderWarmUpCount; /** @@ -118,7 +121,7 @@ public: * warmup frames is based on how much excess there is in the camera cut track outside of the playback range AND * the sequence is evaluated for each frame which can allow time for skeletal meshes to animate from a bind pose, etc. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Render Settings") bool bUseCameraCutForWarmUp; /** @@ -128,7 +131,7 @@ public: * * This is more cheaper than RenderWarmUpCount and is the preferred way to have time pass at the start of a shot. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = 0, ClampMin = 0, EditCondition = "!bUseCameraCutForWarmUp"), AdvancedDisplay, Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = 0, ClampMin = 0, EditCondition = "!bUseCameraCutForWarmUp"), AdvancedDisplay, Category = "Render Settings") int32 EngineWarmUpCount; /** diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineBlueprintLibrary.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineBlueprintLibrary.h index 72314ae5d038..721448bd1e80 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineBlueprintLibrary.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineBlueprintLibrary.h @@ -74,7 +74,7 @@ public: static void GetOverallOutputFrames(const UMoviePipeline* InMoviePipeline, int32& OutCurrentIndex, int32& OutTotalCount); UFUNCTION(BlueprintPure, Category = "Movie Render Pipeline") - static FText GetCurrentSegmentName(UMoviePipeline* InMoviePipeline); + static void GetCurrentSegmentName(UMoviePipeline* InMoviePipeline, FText& OutOuterName, FText& OutInnerName); UFUNCTION(BlueprintPure, Category = "Movie Render Pipeline") static void GetOverallSegmentCounts(const UMoviePipeline* InMoviePipeline, int32& OutCurrentIndex, int32& OutTotalCount); @@ -105,5 +105,8 @@ public: /** Scan the provided sequence in the job to see which camera cut sections we would try to render and update the job's shotlist. */ UFUNCTION(BlueprintCallable, Category = "Movie Render Pipeline") static void UpdateJobShotListFromSequence(ULevelSequence* InSequence, UMoviePipelineExecutorJob* InJob); - + + /** If version number is manually specifies, returns that, otherwise search the Output Directory for the highest version already existing an increments it by one. */ + UFUNCTION(BlueprintCallable, Category = "Movie Render Pipeline") + static int32 ResolveVersionNumber(const UMoviePipeline* InMoviePipeline); }; \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineCameraSetting.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineCameraSetting.h index 0984b1005c46..e9d3748470ca 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineCameraSetting.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineCameraSetting.h @@ -26,10 +26,16 @@ protected: virtual bool IsValidOnShots() const override { return true; } virtual bool IsValidOnMaster() const override { return true; } - virtual void GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const override + virtual void GetFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const override { - InOutFormatArgs.Arguments.Add(TEXT("shutter_angle"), CameraShutterAngle); - InOutFormatArgs.Arguments.Add(TEXT("shutter_timing"), StaticEnum()->GetNameStringByValue((int64)ShutterTiming)); + Super::GetFormatArguments(InOutFormatArgs); + + InOutFormatArgs.FilenameArguments.Add(TEXT("shutter_angle"), CameraShutterAngle); + InOutFormatArgs.FilenameArguments.Add(TEXT("shutter_timing"), StaticEnum()->GetNameStringByValue((int64)ShutterTiming)); + + + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/camera/shutterAngle"), CameraShutterAngle); + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/camera/shutterTiming"), StaticEnum()->GetNameStringByValue((int64)ShutterTiming)); } public: /** @@ -42,7 +48,7 @@ public: * A shutter angle of 360 means continuous movement, while a shutter angle of zero means no * motion blur. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0", UIMax = "360", ClampMin = "0", ClampMax = "360"), Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0", UIMax = "360", ClampMin = "0", ClampMax = "360"), Category = "Camera Settings") int32 CameraShutterAngle; /** @@ -52,7 +58,7 @@ public: * frame and half the time after the frame. When set to FrameOpen, the motion represents the time from * Frame N onwards. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0", UIMax = "360", ClampMin = "0", ClampMax = "360"), Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0", UIMax = "360", ClampMin = "0", ClampMax = "360"), Category = "Camera Settings") EMoviePipelineShutterTiming ShutterTiming; /** @@ -60,12 +66,12 @@ public: * exposure between the different tiles of the image. Leaving this off lets the camera determine based on the previous frame rendered, * which can require long warm up times between shots to allow the exposure to settle. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0", UIMax = "360", ClampMin = "0", ClampMax = "360"), Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0", UIMax = "360", ClampMin = "0", ClampMax = "360"), Category = "Camera Settings") bool bManualExposure; /** * What exposure should we use when using Manual Exposure? Same behavior as the Post Processing volume. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "-10", UIMax = "10", EditCondition="bManualExposure"), Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "-10", UIMax = "10", EditCondition="bManualExposure"), Category = "Camera Settings") float ExposureCompensation; }; \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineExecutor.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineExecutor.h index 80693a80e104..737102670732 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineExecutor.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineExecutor.h @@ -14,6 +14,7 @@ class UMoviePipelineExecutorBase; class UMoviePipelineExecutorJob; class UMoviePipeline; class UMoviePipelineQueue; +class UMovieRenderDebugWidget; DECLARE_MULTICAST_DELEGATE_TwoParams(FOnMoviePipelineExecutorFinishedNative, UMoviePipelineExecutorBase* /*PipelineExecutor*/, bool /*bSuccess*/); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnMoviePipelineExecutorFinished, UMoviePipelineExecutorBase*, PipelineExecutor, bool, bSuccess); @@ -320,6 +321,10 @@ private: bool ProcessIncomingSocketData(); public: + /** Optional widget for feedback during render */ + UPROPERTY(BlueprintReadWrite, Category = "Movie Render Pipeline") + TSubclassOf DebugWidgetClass; + /** * Arbitrary data that can be associated with the executor. Not used by default implementations, nor read. * This can be used to attach third party metadata such as job ids from remote farms. diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineFCPXMLExporterSetting.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineFCPXMLExporterSetting.h new file mode 100644 index 000000000000..9d267de466b3 --- /dev/null +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineFCPXMLExporterSetting.h @@ -0,0 +1,49 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "MoviePipelineOutputBase.h" +#include "MovieRenderPipelineDataTypes.h" +#include "MoviePipelineFCPXMLExporterSetting.generated.h" + +UENUM(BlueprintType) +enum class FCPXMLExportDataSource : uint8 +{ + OutputMetadata, + SequenceData +}; + +UCLASS(BlueprintType) +class MOVIERENDERPIPELINECORE_API UMoviePipelineFCPXMLExporter : public UMoviePipelineOutputBase +{ + GENERATED_BODY() +public: + UMoviePipelineFCPXMLExporter() + : FileNameFormat(TEXT("{sequence_name}")) + , bHasFinishedExporting(false) + {} + +public: +#if WITH_EDITOR + virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "FCPXMLExporterDisplayName", "Final Cut Pro XML"); } +#endif +protected: + virtual bool HasFinishedExportingImpl() const { return bHasFinishedExporting; } + virtual void BeginExportImpl() override; + + bool EnsureWritableFile(); + bool bOverwriteFile; +public: + /** What format string should the final files use? Can include folder prefixes, and format string ({sequence_name}, etc.) */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "File Output") + FString FileNameFormat; + + /** Whether to build the FCPXML from sequence data directly (for reimporting) or from actual frame output data (for post processing) */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "File Output") + FCPXMLExportDataSource DataSource; + +protected: + /** The file to write to */ + FString FilePath; + + bool bHasFinishedExporting; +}; \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineHighResSetting.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineHighResSetting.h index d6c8a1984915..504e5f53f18d 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineHighResSetting.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineHighResSetting.h @@ -53,12 +53,17 @@ public: } } - virtual void GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const override + virtual void GetFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const override { - InOutFormatArgs.Arguments.Add(TEXT("tile_count"), TileCount); - InOutFormatArgs.Arguments.Add(TEXT("overlap_percent"), OverlapRatio); + Super::GetFormatArguments(InOutFormatArgs); + + InOutFormatArgs.FilenameArguments.Add(TEXT("tile_count"), TileCount); + InOutFormatArgs.FilenameArguments.Add(TEXT("overlap_percent"), OverlapRatio); + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/highres/tileCount"), TileCount); + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/highres/overlapPercent"), OverlapRatio); } +#if WITH_EDITOR virtual FText GetFooterText(UMoviePipelineExecutorJob* InJob) const override { if (!InJob || !InJob->GetConfiguration()) @@ -106,7 +111,7 @@ public: return FText(); } - +#endif virtual void SetupForPipelineImpl(UMoviePipeline* InPipeline) override { @@ -138,7 +143,7 @@ public: * resolution which may help with gpu timeouts. Requires at least 1 tile. Tiling is applied evenly to * both X and Y. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "1", ClampMin = "1", UIMax = "16"), Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "1", ClampMin = "1", UIMax = "16"), Category = "Render Settings") int32 TileCount; /** @@ -147,7 +152,7 @@ public: * (up to the detail resolution of your texture). Too much sharpness will cause visual grain/noise in the * resulting image, but this can be mitigated with more spatial samples. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "-1", UIMax = "0"), Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "-1", UIMax = "0"), Category = "Render Settings") float TextureSharpnessBias; /** @@ -155,27 +160,27 @@ public: * tiles (which means faster renders) but increases the likelyhood of edge-of-screen artifacts showing up which * will become visible in the final image as a "grid" of repeated problem areas. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0", ClampMin = "0", UIMax = "0.5", ClampMax = "1"), Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0", ClampMin = "0", UIMax = "0.5", ClampMax = "1"), Category = "Render Settings") float OverlapRatio; /** * Sub Surface Scattering relies on history which is not available when using tiling. This can be overriden to use more samples * to improve the quality. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Render Settings") bool bOverrideSubSurfaceScattering; /* * How many samples should the Burley Sub Surface Scattering use? */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "64", ClampMin = "0", UIMax = "1024", EditCondition="bOverrideSubSurfaceScattering"), Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "64", ClampMin = "0", UIMax = "1024", EditCondition="bOverrideSubSurfaceScattering"), Category = "Render Settings") int32 BurleySampleCount; /** * If true, we will write all samples that get generated to disk individually. This can be useful for debugging or if you need to accumulate * render passes differently than provided. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = "Render Settings") bool bWriteAllSamples; diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineInProcessExecutor.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineInProcessExecutor.h index 1a817e7c4b67..c81573da0b8f 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineInProcessExecutor.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineInProcessExecutor.h @@ -4,6 +4,8 @@ #include "MoviePipelineLinearExecutor.h" #include "MoviePipelineInProcessExecutor.generated.h" +class UWorld; + /** * This executor implementation can process an array of movie pipelines and * run them inside the currently running process. This is intended for usage @@ -19,10 +21,15 @@ class MOVIERENDERPIPELINECORE_API UMoviePipelineInProcessExecutor : public UMovi public: UMoviePipelineInProcessExecutor() : UMoviePipelineLinearExecutorBase() + , bUseCurrentLevel(false) , RemainingInitializationFrames(-1) { } + /** Use current level instead of opening new level */ + UPROPERTY(BlueprintReadWrite, Category = "Movie Render Pipeline") + bool bUseCurrentLevel; + protected: virtual void Start(const UMoviePipelineExecutorJob* InJob) override; @@ -32,7 +39,29 @@ private: void OnApplicationQuit(); void OnTick(); + void BackupState(); + void ModifyState(const UMoviePipelineExecutorJob* InJob); + void RestoreState(); + + static UWorld* FindCurrentWorld(); + private: /** If using delayed initialization, how many frames are left before we call Initialize. Will be -1 if not actively counting down. */ int32 RemainingInitializationFrames; + + struct FSavedState + { + bool bBackedUp = false; + + // PlayerController + bool bCinematicMode = false; + bool bHidePlayer = false; + + bool bUseFixedTimeStep = false; + double FixedDeltaTime = 1.0; + + TOptional WindowTitle; + }; + + FSavedState SavedState; }; \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineMasterConfig.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineMasterConfig.h index a35f969c3f63..0104930f1695 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineMasterConfig.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineMasterConfig.h @@ -62,7 +62,7 @@ public: /** Returns a pointer to the config specified for the shot, otherwise the default for this pipeline. */ UMoviePipelineShotConfig* GetConfigForShot(const FString& ShotName) const; - void GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const; + void GetFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs, const bool bIncludeAllSettings = false) const; /** diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineOutputSetting.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineOutputSetting.h index 2bbf8a121578..b851b0d67db3 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineOutputSetting.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineOutputSetting.h @@ -16,11 +16,11 @@ public: #if WITH_EDITOR virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "OutputSettingDisplayName", "Output"); } virtual FText GetFooterText(UMoviePipelineExecutorJob* InJob) const override; + virtual bool CanBeDisabled() const override { return false; } #endif virtual bool IsValidOnShots() const override { return false; } virtual bool IsValidOnMaster() const override { return true; } - virtual void GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const override; - virtual bool CanBeDisabled() const override { return false; } + virtual void GetFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const override; // UObject Interface virtual void PostLoad() override; @@ -81,6 +81,15 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(EditCondition=bUseCustomPlaybackRange), Category = "Frames") int32 CustomEndFrame; +public: + /** The value to use for the version token if versions are not automatically incremented. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "!bAutoVersion", UIMin = 1, UIMax = 10), Category = "Versioning") + int32 VersionNumber; + + /** If true, version tokens will automatically be incremented with each local render. If false, the custom version number below will be used. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Versioning") + bool bAutoVersion; + public: /** How many digits should all output frame numbers be padded to? MySequence_1.png -> MySequence_0001.png. Useful for software that struggles to recognize frame ranges when non-padded. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "1", MinValue = "1", UIMax = "5"), AdvancedDisplay, Category = "File Output") diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineQueue.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineQueue.h index 7381d0440f9b..f080b960203f 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineQueue.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineQueue.h @@ -272,7 +272,9 @@ public: public: // UObject Interface +#if WITH_EDITOR void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent); +#endif // ~UObject Interface protected: diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineQueueEngineSubsystem.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineQueueEngineSubsystem.h new file mode 100644 index 000000000000..bcccf3de59d5 --- /dev/null +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineQueueEngineSubsystem.h @@ -0,0 +1,78 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "Subsystems/EngineSubsystem.h" +#include "MoviePipelineQueue.h" +#include "MoviePipelineExecutor.h" +#include "MoviePipelineQueueEngineSubsystem.generated.h" + +UCLASS(BlueprintType) +class MOVIERENDERPIPELINECORE_API UMoviePipelineQueueEngineSubsystem : public UEngineSubsystem +{ + GENERATED_BODY() + +public: + + UMoviePipelineQueueEngineSubsystem() + { + CurrentQueue = CreateDefaultSubobject("EngineMoviePipelineQueue"); + } + + /** Returns the queue of Movie Pipelines that need to be rendered. */ + UFUNCTION(BlueprintPure, Category = "Movie Render Pipeline") + UMoviePipelineQueue* GetQueue() const + { + return CurrentQueue; + } + + /** Returns the active executor (if there is one). This can be used to subscribe to events on an already in-progress render. May be null. */ + UFUNCTION(BlueprintPure, Category = "Movie Render Pipeline") + UMoviePipelineExecutorBase* GetActiveExecutor() const + { + return ActiveExecutor; + } + + /** + * Starts processing the current queue with the supplied executor class. This starts an async process which + * may or may not run in a separate process (or on separate machines), determined by the executor implementation. + * The executor should report progress for jobs depending on the implementation. + * + * @param InExecutorType A subclass of UMoviePipelineExecutorBase. An instance of this class is created and started. + * @return A pointer to the instance of the class created. This instance will be kept alive by the Queue Subsystem + until it has finished (or been canceled). Register for progress reports and various callbacks on this instance. + */ + UFUNCTION(BlueprintCallable, meta = (DeterminesOutputType = "InExecutorType"), Category = "Movie Render Pipeline|Rendering") + UMoviePipelineExecutorBase* RenderQueueWithExecutor(TSubclassOf InExecutorType); + + /** + * Starts processing the current queue with the supplied executor. This starts an async process which + * may or may not run in a separate process (or on separate machines), determined by the executor implementation. + * The executor should report progress for jobs depending on the implementation. + * + * @param InExecutor Instance of a subclass of UMoviePipelineExecutorBase. + */ + UFUNCTION(BlueprintCallable, Category = "Movie Render Pipeline|Rendering") + void RenderQueueWithExecutorInstance(UMoviePipelineExecutorBase* InExecutor); + + /** + * Returns true if there is an active executor working on producing a movie. This could be actively rendering frames, + * or working on post processing (finalizing file writes, etc.). Use GetActiveExecutor() and query it directly for + * more information, progress updates, etc. + */ + UFUNCTION(BlueprintPure, Category = "Movie Render Pipeline|Rendering") + bool IsRendering() const + { + return ActiveExecutor ? ActiveExecutor->IsRendering() : false; + } + +private: + /** Called when the executor is finished so that we can release it and stop reporting IsRendering() == true. */ + void OnExecutorFinished(UMoviePipelineExecutorBase* InPipelineExecutor, bool bSuccess); + +private: + UPROPERTY(Transient) + UMoviePipelineExecutorBase* ActiveExecutor; + + UPROPERTY(Transient, Instanced) + UMoviePipelineQueue* CurrentQueue; +}; \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineSetting.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineSetting.h index 105547050663..bb68b5523354 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineSetting.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineSetting.h @@ -106,8 +106,8 @@ public: /** Get a human-readable text describing what validation errors (if any) the call to ValidateState() produced. */ virtual TArray GetValidationResults() const; - /** Return Key/Value pairs that you wish to be usable in the Output File Name format string. This allows settings to add format strings based on their values. */ - virtual void GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const {} + /** Return Key/Value pairs that you wish to be usable in the Output File Name format string or file metadata. This allows settings to add format strings based on their values. */ + virtual void GetFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const {} /** Modify the Unreal URL and Command Line Arguments when preparing the setting to be run in a new process. */ virtual void BuildNewProcessCommandLineImpl(FString& InOutUnrealURLParams, FString& InOutCommandLineArgs) const { } diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineVideoOutputBase.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineVideoOutputBase.h index 7d618ff3470c..674b5f5a8655 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineVideoOutputBase.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MoviePipelineVideoOutputBase.h @@ -2,6 +2,7 @@ #pragma once #include "MoviePipelineOutputBase.h" +#include "MovieRenderPipelineDataTypes.h" #include "ImagePixelData.h" #include "Async/AsyncWork.h" #include "Async/TaskGraphInterfaces.h" @@ -14,6 +15,7 @@ namespace MovieRenderPipeline struct IVideoCodecWriter { FString FileName; + FMoviePipelineFormatArgs FormatArgs; }; } @@ -62,7 +64,9 @@ protected: virtual bool HasFinishedProcessingImpl() override; virtual void BeginFinalizeImpl() override; virtual void FinalizeImpl() override; +#if WITH_EDITOR virtual FText GetFooterText(UMoviePipelineExecutorJob* InJob) const override; +#endif // ~UMoviePipelineOutputBase Interface // UMoviePipelineVideoOutputBase Interface diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MovieRenderPipelineDataTypes.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MovieRenderPipelineDataTypes.cpp index e0992db88fe6..abc5c2663124 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MovieRenderPipelineDataTypes.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MovieRenderPipelineDataTypes.cpp @@ -173,7 +173,7 @@ static FString GetPaddingFormatString(int32 InZeroPadCount, const int32 InFrameN return FString::Printf(TEXT("%0*d"), InZeroPadCount, InFrameNumber); } -void FMoviePipelineFrameOutputState::GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs, const int32 InZeroPadCount, const int32 InFrameNumberOffset) const +void FMoviePipelineFrameOutputState::GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs, const int32 InZeroPadCount, const int32 InFrameNumberOffset, const bool bForceRelFrameNumbers) const { // Zero-pad our frame numbers when we format the strings. Some programs struggle when ingesting frames that // go 1,2,3,...,10,11. To work around this issue we allow the user to specify how many zeros they want to @@ -185,10 +185,25 @@ void FMoviePipelineFrameOutputState::GetFilenameFormatArguments(FMoviePipelineFo FString FrameNumberRel = GetPaddingFormatString(InZeroPadCount, OutputFrameNumber + InFrameNumberOffset); // Relative to 0 FString FrameNumberShotRel = GetPaddingFormatString(InZeroPadCount, ShotOutputFrameNumber + InFrameNumberOffset); // Relative to 0 within the shot. - InOutFormatArgs.Arguments.Add(TEXT("frame_number"), FrameNumber); - InOutFormatArgs.Arguments.Add(TEXT("frame_number_shot"), FrameNumberShot); - InOutFormatArgs.Arguments.Add(TEXT("frame_number_rel"), FrameNumberRel); - InOutFormatArgs.Arguments.Add(TEXT("frame_number_shot_rel"), FrameNumberShotRel); - InOutFormatArgs.Arguments.Add(TEXT("camera_name"), CameraName.Len() > 0 ? CameraName : TEXT("NoCamera")); - InOutFormatArgs.Arguments.Add(TEXT("shot_name"), ShotName.Len() > 0 ? ShotName : TEXT("NoShot")); + // Ensure they used relative frame numbers in the output so they get the right number of output frames. + if (bForceRelFrameNumbers) + { + FrameNumber = FrameNumberRel; + FrameNumberShot = FrameNumberShotRel; + } + + InOutFormatArgs.FilenameArguments.Add(TEXT("frame_number"), FrameNumber); + InOutFormatArgs.FilenameArguments.Add(TEXT("frame_number_shot"), FrameNumberShot); + InOutFormatArgs.FilenameArguments.Add(TEXT("frame_number_rel"), FrameNumberRel); + InOutFormatArgs.FilenameArguments.Add(TEXT("frame_number_shot_rel"), FrameNumberShotRel); + InOutFormatArgs.FilenameArguments.Add(TEXT("camera_name"), CameraName.Len() > 0 ? CameraName : TEXT("NoCamera")); + InOutFormatArgs.FilenameArguments.Add(TEXT("shot_name"), ShotName.Len() > 0 ? ShotName : TEXT("NoShot")); + + + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/sequenceFrameNumber"), FrameNumber); + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/shotFrameNumber"), FrameNumberShot); + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/sequenceFrameNumberRelative"), FrameNumberRel); + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/shotFrameNumberRelative"), FrameNumberShotRel); + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/cameraName"), CameraName.Len() > 0 ? CameraName : TEXT("NoCamera")); + InOutFormatArgs.FileMetadata.Add(TEXT("unreal/shotName"), ShotName.Len() > 0 ? ShotName : TEXT("NoShot")); } diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MovieRenderPipelineDataTypes.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MovieRenderPipelineDataTypes.h index 4a5a48201769..7527892ee4dc 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MovieRenderPipelineDataTypes.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/MovieRenderPipelineDataTypes.h @@ -447,7 +447,7 @@ public: : MotionBlurFraction(0.f) , FrameDeltaTime(0.0) , WorldSeconds(0.0) - , bWasAffectedByTimeDilation(false) + , TimeDilation(1.f) { } @@ -461,17 +461,19 @@ public: double WorldSeconds; /** - * If true, there was a non-1.0 Time Dilation in effect when this frame was produced. This indicates that there + * Check if there was a non-1.0 Time Dilation in effect when this frame was produced. This indicates that there * may be duplicate frame Source/Effective frame numbers as they find the closest ideal time to the current. */ - bool bWasAffectedByTimeDilation; + float TimeDilation; void ResetPerFrameData() { MotionBlurFraction = 0.f; FrameDeltaTime = 0.0; - bWasAffectedByTimeDilation = false; + TimeDilation = 1.f; } + + FORCEINLINE bool IsTimeDilated() const { return !FMath::IsNearlyEqual(TimeDilation, 1.f); } }; FMoviePipelineFrameOutputState() @@ -545,6 +547,7 @@ public: EffectiveTimeCode = FTimecode(); CurrentShotSourceFrameNumber = 0; CurrentShotSourceTimeCode = FTimecode(); + FileMetadata.Reset(); } void ResetPerShotData() @@ -586,6 +589,9 @@ public: /** The closest time code version of the EffectiveFrameNumber. May be a duplicate in the event of Play Rate tracks. */ FTimecode EffectiveTimeCode; + /** Metadata to attach to the output file (if supported by the output container) */ + FStringFormatNamedArguments FileMetadata; + int32 CurrentShotSourceFrameNumber; @@ -619,7 +625,7 @@ public: { return GetTypeHash(OutputState.OutputFrameNumber); } - void GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs, const int32 InZeroPadCount, const int32 InFrameNumberOffset) const; + void GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs, const int32 InZeroPadCount, const int32 InFrameNumberOffset, const bool bForceRelFrameNumbers) const; }; struct FMoviePipelineFormatArgs @@ -629,8 +635,11 @@ struct FMoviePipelineFormatArgs { } - /** A set of Key/Value pairs for format strings (without {}) and their values. */ - FStringFormatNamedArguments Arguments; + /** A set of Key/Value pairs for output filename format strings (without {}) and their values. */ + FStringFormatNamedArguments FilenameArguments; + + /** A set of Key/Value pairs for file metadata for file formats that support metadata. */ + FStringFormatNamedArguments FileMetadata; /** Which job is this for? Some settings are specific to the level sequence being rendered. */ class UMoviePipelineExecutorJob* InJob; diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineEditor/Private/MoviePipelinePIEExecutor.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineEditor/Private/MoviePipelinePIEExecutor.cpp index 49b25a695d54..e5caef2265f2 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineEditor/Private/MoviePipelinePIEExecutor.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineEditor/Private/MoviePipelinePIEExecutor.cpp @@ -175,6 +175,7 @@ void UMoviePipelinePIEExecutor::OnPIEStartupFinished(bool) // This Pipeline belongs to the world being created so that they have context for things they execute. ActiveMoviePipeline = NewObject(ExecutingWorld, PipelineClass); + ActiveMoviePipeline->DebugWidgetClass = DebugWidgetClass; // We allow users to set a multi-frame delay before we actually run the Initialization function and start thinking. // This solves cases where there are engine systems that need to finish loading before we do anything. diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineEditor/Private/Widgets/SMoviePipelineConfigEditor.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineEditor/Private/Widgets/SMoviePipelineConfigEditor.cpp index 45b95d70103d..b04225fad0a4 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineEditor/Private/Widgets/SMoviePipelineConfigEditor.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineEditor/Private/Widgets/SMoviePipelineConfigEditor.cpp @@ -418,6 +418,12 @@ EVisibility SMoviePipelineConfigEditor::IsValidationWarningVisible() const EMoviePipelineValidationState ValidationResult = EMoviePipelineValidationState::Valid; for (const UMoviePipelineSetting* Setting : SelectedSettings) { + // Don't show warnings for disabled settings as the invalid setting won't be used when rendering. + if (!Setting->IsEnabled() && Setting->GetIsUserCustomized()) + { + continue; + } + if ((int32)Setting->GetValidationState() > (int32)ValidationResult) { ValidationResult = Setting->GetValidationState(); diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineEditor/Private/Widgets/SMoviePipelineQueuePanel.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineEditor/Private/Widgets/SMoviePipelineQueuePanel.cpp index 2a745f9bb3c5..a7230d238b1c 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineEditor/Private/Widgets/SMoviePipelineQueuePanel.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineEditor/Private/Widgets/SMoviePipelineQueuePanel.cpp @@ -580,10 +580,12 @@ void SMoviePipelineQueuePanel::OnSaveAsAsset() // Saving into a new package const FString NewAssetName = FPackageName::GetLongPackageAssetName(PackageName); UPackage* NewPackage = CreatePackage(nullptr, *PackageName); - UMoviePipelineQueue* DuplicateQueue = NewObject(NewPackage, *NewAssetName, RF_Public | RF_Standalone | RF_Transactional, CurrentQueue); + UMoviePipelineQueue* DuplicateQueue = DuplicateObject(CurrentQueue, NewPackage, *NewAssetName); if (DuplicateQueue) { + DuplicateQueue->SetFlags(RF_Public | RF_Standalone | RF_Transactional); + FAssetRegistryModule::AssetCreated(DuplicateQueue); FEditorFileUtils::EPromptReturnCode PromptReturnCode = FEditorFileUtils::PromptForCheckoutAndSave({ NewPackage }, false, false); diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/MovieRenderPipelineRenderPasses.Build.cs b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/MovieRenderPipelineRenderPasses.Build.cs index 2adeee2f1ec8..e37bc27259da 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/MovieRenderPipelineRenderPasses.Build.cs +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/MovieRenderPipelineRenderPasses.Build.cs @@ -6,6 +6,8 @@ public class MovieRenderPipelineRenderPasses : ModuleRules { public MovieRenderPipelineRenderPasses(ReadOnlyTargetRules Target) : base(Target) { + bEnableExceptions = true; + PrivateDependencyModuleNames.AddRange( new string[] { "Core", @@ -14,6 +16,9 @@ public class MovieRenderPipelineRenderPasses : ModuleRules "ImageWriteQueue", "SignalProcessing", // Needed for wave writer. "AudioMixer", + "UEOpenExr", // Needed for multilayer EXRs + "UEOpenExrRTTI", // Needed for EXR metadata + "ImageWrapper", } ); @@ -23,6 +28,9 @@ public class MovieRenderPipelineRenderPasses : ModuleRules "RenderCore", "RHI", } - ); + ); + + // Required for UEOpenExr + AddEngineThirdPartyPrivateStaticDependencies(Target, "zlib"); } } diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineDeferredPasses.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineDeferredPasses.cpp index 05aa255cee0f..b8c67dd77ab4 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineDeferredPasses.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineDeferredPasses.cpp @@ -33,7 +33,6 @@ DECLARE_CYCLE_STAT(TEXT("STAT_MoviePipeline_WaitForAvailableAccumulator"), STAT_ DECLARE_CYCLE_STAT(TEXT("STAT_MoviePipeline_AccumulateSample_TT"), STAT_AccumulateSample_TaskThread, STATGROUP_MoviePipeline); DECLARE_CYCLE_STAT(TEXT("STAT_MoviePipeline_WaitForAvailableSurface"), STAT_MoviePipeline_WaitForAvailableSurface, STATGROUP_MoviePipeline); - // Forward Declare namespace MoviePipeline { @@ -41,17 +40,18 @@ namespace MoviePipeline static bool GetAnyOutputWantsAlpha(UMoviePipelineConfigBase* InConfig); } -void UMoviePipelineDeferredPassBase::GetViewShowFlags(FEngineShowFlags& OutShowFlag, EViewModeIndex& OutViewModeIndex) const +void UMoviePipelineImagePassBase::GetViewShowFlags(FEngineShowFlags& OutShowFlag, EViewModeIndex& OutViewModeIndex) const { OutShowFlag = FEngineShowFlags(EShowFlagInitMode::ESFIM_Game); OutViewModeIndex = EViewModeIndex::VMI_Lit; } -void UMoviePipelineDeferredPassBase::RenderSample_GameThreadImpl(const FMoviePipelineRenderPassMetrics& InSampleState) +void UMoviePipelineImagePassBase::RenderSample_GameThreadImpl(const FMoviePipelineRenderPassMetrics& InSampleState) { Super::RenderSample_GameThreadImpl(InSampleState); - - const FMoviePipelineFrameOutputState::FTimeData& TimeData = InSampleState.OutputState.TimeData; + + FMoviePipelineRenderPassMetrics InOutSampleState = InSampleState; + const FMoviePipelineFrameOutputState::FTimeData& TimeData = InOutSampleState.OutputState.TimeData; FEngineShowFlags ShowFlags = FEngineShowFlags(EShowFlagInitMode::ESFIM_Game); EViewModeIndex ViewModeIndex; @@ -66,15 +66,15 @@ void UMoviePipelineDeferredPassBase::RenderSample_GameThreadImpl(const FMoviePip .SetWorldTimes(TimeData.WorldSeconds, TimeData.FrameDeltaTime, TimeData.WorldSeconds) .SetRealtimeUpdate(true)); - ViewFamily.SceneCaptureSource = InSampleState.SceneCaptureSource; - ViewFamily.SetScreenPercentageInterface(new FLegacyScreenPercentageDriver(ViewFamily, InSampleState.GlobalScreenPercentageFraction, true)); - ViewFamily.bWorldIsPaused = InSampleState.bWorldIsPaused; + ViewFamily.SceneCaptureSource = InOutSampleState.SceneCaptureSource; + ViewFamily.SetScreenPercentageInterface(new FLegacyScreenPercentageDriver(ViewFamily, IsScreenPercentageSupported() ? InOutSampleState.GlobalScreenPercentageFraction : 1.f, IsScreenPercentageSupported())); + ViewFamily.bWorldIsPaused = InOutSampleState.bWorldIsPaused; ViewFamily.ViewMode = ViewModeIndex; EngineShowFlagOverride(ESFIM_Game, ViewFamily.ViewMode, ViewFamily.EngineShowFlags, false); - // View is added as a child of the ViewFamily - FSceneView* View = GetSceneViewForSampleState(&ViewFamily, InSampleState); + // View is added as a child of the ViewFamily. + FSceneView* View = GetSceneViewForSampleState(&ViewFamily, /*InOut*/ InOutSampleState); #if RHI_RAYTRACING View->SetupRayTracedRendering(); #endif @@ -83,23 +83,26 @@ void UMoviePipelineDeferredPassBase::RenderSample_GameThreadImpl(const FMoviePip // Override the view's FrameIndex to be based on our progress through the sequence. This greatly increases // determinism with things like TAA. - View->OverrideFrameIndexValue = InSampleState.FrameIndex; - View->bCameraCut = InSampleState.bCameraCut; + View->OverrideFrameIndexValue = InOutSampleState.FrameIndex; + View->bCameraCut = InOutSampleState.bCameraCut; View->bIsOfflineRender = true; - View->AntiAliasingMethod = InSampleState.AntiAliasingMethod; + View->AntiAliasingMethod = InOutSampleState.AntiAliasingMethod; // Override the Motion Blur settings since these are controlled by the movie pipeline. { FFrameRate OutputFrameRate = GetPipeline()->GetPipelineMasterConfig()->GetEffectiveFrameRate(GetPipeline()->GetTargetSequence()); - View->FinalPostProcessSettings.MotionBlurTargetFPS = FMath::RoundToInt(OutputFrameRate.AsDecimal()); - View->FinalPostProcessSettings.MotionBlurAmount = InSampleState.OutputState.TimeData.MotionBlurFraction; + + // We need to inversly scale the target FPS by time dilation to counteract slowmo. If scaling isn't applied then motion blur length + // stays the same length despite the smaller delta time and the blur ends up too long. + View->FinalPostProcessSettings.MotionBlurTargetFPS = FMath::RoundToInt(OutputFrameRate.AsDecimal() / FMath::Max(SMALL_NUMBER, InOutSampleState.OutputState.TimeData.TimeDilation)); + View->FinalPostProcessSettings.MotionBlurAmount = InOutSampleState.OutputState.TimeData.MotionBlurFraction; View->FinalPostProcessSettings.MotionBlurMax = 100.f; View->FinalPostProcessSettings.bOverride_MotionBlurAmount = true; View->FinalPostProcessSettings.bOverride_MotionBlurTargetFPS = true; View->FinalPostProcessSettings.bOverride_MotionBlurMax = true; // Skip the whole pass if they don't want motion blur. - if (FMath::IsNearlyZero(InSampleState.OutputState.TimeData.MotionBlurFraction)) + if (FMath::IsNearlyZero(InOutSampleState.OutputState.TimeData.MotionBlurFraction)) { ViewFamily.EngineShowFlags.SetMotionBlur(false); } @@ -107,12 +110,12 @@ void UMoviePipelineDeferredPassBase::RenderSample_GameThreadImpl(const FMoviePip // Locked Exposure { - if (InSampleState.ExposureCompensation.IsSet()) + if (InOutSampleState.ExposureCompensation.IsSet()) { View->FinalPostProcessSettings.AutoExposureMethod = EAutoExposureMethod::AEM_Manual; - View->FinalPostProcessSettings.AutoExposureBias = InSampleState.ExposureCompensation.GetValue(); + View->FinalPostProcessSettings.AutoExposureBias = InOutSampleState.ExposureCompensation.GetValue(); } - else if (InSampleState.GetTileCount() > 1 && (View->FinalPostProcessSettings.AutoExposureMethod != EAutoExposureMethod::AEM_Manual)) + else if (InOutSampleState.GetTileCount() > 1 && (View->FinalPostProcessSettings.AutoExposureMethod != EAutoExposureMethod::AEM_Manual)) { // Auto exposure is not allowed UE_LOG(LogMovieRenderPipeline, Warning, TEXT("AutoExposure Method should always be Manual when using tiling!")); @@ -126,7 +129,7 @@ void UMoviePipelineDeferredPassBase::RenderSample_GameThreadImpl(const FMoviePip // inside FSceneRenderer::PreVisibilityFrameSetup.. if (View->AntiAliasingMethod != EAntiAliasingMethod::AAM_TemporalAA) { - View->ViewMatrices.HackAddTemporalAAProjectionJitter(InSampleState.ProjectionMatrixJitterAmount); + View->ViewMatrices.HackAddTemporalAAProjectionJitter(InOutSampleState.ProjectionMatrixJitterAmount); } } @@ -134,7 +137,7 @@ void UMoviePipelineDeferredPassBase::RenderSample_GameThreadImpl(const FMoviePip { // If we're using tiling, we force the reset of histories each frame so that we don't use the previous tile's // object occlusion queries, as that causes things to disappear from some views. - if (InSampleState.GetTileCount() > 1) + if (InOutSampleState.GetTileCount() > 1) { View->bForceCameraVisibilityReset = true; } @@ -143,12 +146,12 @@ void UMoviePipelineDeferredPassBase::RenderSample_GameThreadImpl(const FMoviePip // Bias all mip-mapping to pretend to be working at our target resolution and not our tile resolution // so that the images don't end up soft. { - float EffectivePrimaryResolutionFraction = 1.f / InSampleState.TileCounts.X; + float EffectivePrimaryResolutionFraction = 1.f / InOutSampleState.TileCounts.X; View->MaterialTextureMipBias = FMath::Log2(EffectivePrimaryResolutionFraction); // Add an additional bias per user settings. This allows them to choose to make the textures sharper if it // looks better with their particular settings. - View->MaterialTextureMipBias += InSampleState.TextureSharpnessBias; + View->MaterialTextureMipBias += InOutSampleState.TextureSharpnessBias; } // Wait for a surface to be available to write to. This will stall the game thread while the RHI/Render Thread catch up. @@ -162,7 +165,7 @@ void UMoviePipelineDeferredPassBase::RenderSample_GameThreadImpl(const FMoviePip // Draw the world into this View Family GetRendererModule().BeginRenderingViewFamily(&Canvas, &ViewFamily); - PostRendererSubmission(InSampleState, Canvas); + PostRendererSubmission(InOutSampleState, Canvas); } void UMoviePipelineDeferredPassBase::PostRendererSubmission(const FMoviePipelineRenderPassMetrics& InSampleState, FCanvas& InCanvas) @@ -297,7 +300,7 @@ void UMoviePipelineDeferredPassBase::PostRendererSubmission(const FMoviePipeline }); } -void UMoviePipelineDeferredPassBase::SetupViewForViewModeOverride(FSceneView* View) +void UMoviePipelineImagePassBase::SetupViewForViewModeOverride(FSceneView* View) { if (View->Family->EngineShowFlags.Wireframe) { @@ -344,6 +347,14 @@ void UMoviePipelineDeferredPassBase::MoviePipelineRenderShowFlagOverride(FEngine OutShowFlag.SetBloom(!bDisableBloom); } +void UMoviePipelineImagePassBase::SetupImpl(const MoviePipeline::FMoviePipelineRenderPassInitSettings& InPassInitSettings) +{ + Super::SetupImpl(InPassInitSettings); + + // Allocate + ViewState.Allocate(); +} + void UMoviePipelineDeferredPassBase::SetupImpl(const MoviePipeline::FMoviePipelineRenderPassInitSettings& InPassInitSettings) { Super::SetupImpl(InPassInitSettings); @@ -355,19 +366,29 @@ void UMoviePipelineDeferredPassBase::SetupImpl(const MoviePipeline::FMoviePipeli // Initialize to the tile size (not final size) and use a 16 bit back buffer to avoid precision issues when accumulating later TileRenderTarget->InitCustomFormat(InPassInitSettings.BackbufferResolution.X, InPassInitSettings.BackbufferResolution.Y, EPixelFormat::PF_FloatRGBA, false); - GetPipeline()->SetPreviewTexture(TileRenderTarget.Get()); - - // Allocate - ViewState.Allocate(); + if (GetPipeline()->GetPreviewTexture() == nullptr) + { + GetPipeline()->SetPreviewTexture(TileRenderTarget.Get()); + } SurfaceQueue = MakeShared(InPassInitSettings.BackbufferResolution, EPixelFormat::PF_FloatRGBA, 3); AccumulatorPool = MakeShared(6); } +void UMoviePipelineImagePassBase::TeardownImpl() +{ + FSceneViewStateInterface* Ref = ViewState.GetReference(); + if (Ref) + { + Ref->ClearMIDPool(); + } + ViewState.Destroy(); + + Super::TeardownImpl(); +} + void UMoviePipelineDeferredPassBase::TeardownImpl() { - Super::TeardownImpl(); - GetPipeline()->SetPreviewTexture(nullptr); // This may call FlushRenderingCommands if there are outstanding readbacks that need to happen. @@ -376,20 +397,16 @@ void UMoviePipelineDeferredPassBase::TeardownImpl() // Stall until the task graph has completed any pending accumulations. FTaskGraphInterface::Get().WaitUntilTasksComplete(OutstandingTasks, ENamedThreads::GameThread); OutstandingTasks.Reset(); - - FSceneViewStateInterface* Ref = ViewState.GetReference(); - if (Ref) - { - Ref->ClearMIDPool(); - } - ViewState.Destroy(); + + // Preserve our view state until the rendering thread has been flushed. + Super::TeardownImpl(); } -void UMoviePipelineDeferredPassBase::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) +void UMoviePipelineImagePassBase::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) { Super::AddReferencedObjects(InThis, Collector); - UMoviePipelineDeferredPassBase& This = *CastChecked(InThis); + UMoviePipelineImagePassBase& This = *CastChecked(InThis); FSceneViewStateInterface* Ref = This.ViewState.GetReference(); if (Ref) { @@ -397,24 +414,24 @@ void UMoviePipelineDeferredPassBase::AddReferencedObjects(UObject* InThis, FRefe } } -void UMoviePipelineDeferredPassBase::GatherOutputPassesImpl(TArray& ExpectedRenderPasses) +void UMoviePipelineImagePassBase::GatherOutputPassesImpl(TArray& ExpectedRenderPasses) { Super::GatherOutputPassesImpl(ExpectedRenderPasses); ExpectedRenderPasses.Add(PassIdentifier); } -FSceneView* UMoviePipelineDeferredPassBase::GetSceneViewForSampleState(FSceneViewFamily* ViewFamily, const FMoviePipelineRenderPassMetrics& InSampleState) +FSceneView* UMoviePipelineImagePassBase::GetSceneViewForSampleState(FSceneViewFamily* ViewFamily, FMoviePipelineRenderPassMetrics& InOutSampleState) { APlayerController* LocalPlayerController = GetPipeline()->GetWorld()->GetFirstPlayerController(); - int32 TileSizeX = InSampleState.BackbufferSize.X; - int32 TileSizeY = InSampleState.BackbufferSize.Y; + int32 TileSizeX = InOutSampleState.BackbufferSize.X; + int32 TileSizeY = InOutSampleState.BackbufferSize.Y; FSceneViewInitOptions ViewInitOptions; ViewInitOptions.ViewFamily = ViewFamily; - ViewInitOptions.ViewOrigin = InSampleState.FrameInfo.CurrViewLocation; + ViewInitOptions.ViewOrigin = InOutSampleState.FrameInfo.CurrViewLocation; ViewInitOptions.SetViewRectangle(FIntRect(FIntPoint(0, 0), FIntPoint(TileSizeX, TileSizeY))); - ViewInitOptions.ViewRotationMatrix = FInverseRotationMatrix(InSampleState.FrameInfo.CurrViewRotation); + ViewInitOptions.ViewRotationMatrix = FInverseRotationMatrix(InOutSampleState.FrameInfo.CurrViewRotation); // Rotate the view 90 degrees (reason: unknown) ViewInitOptions.ViewRotationMatrix = ViewInitOptions.ViewRotationMatrix * FMatrix( @@ -516,22 +533,22 @@ FSceneView* UMoviePipelineDeferredPassBase::GetSceneViewForSampleState(FSceneVie float PadRatioX = 1.0f; float PadRatioY = 1.0f; - if (InSampleState.OverlappedPad.X > 0 && InSampleState.OverlappedPad.Y > 0) + if (InOutSampleState.OverlappedPad.X > 0 && InOutSampleState.OverlappedPad.Y > 0) { - PadRatioX = float(InSampleState.OverlappedPad.X * 2 + InSampleState.TileSize.X) / float(InSampleState.TileSize.X); - PadRatioY = float(InSampleState.OverlappedPad.Y * 2 + InSampleState.TileSize.Y) / float(InSampleState.TileSize.Y); + PadRatioX = float(InOutSampleState.OverlappedPad.X * 2 + InOutSampleState.TileSize.X) / float(InOutSampleState.TileSize.X); + PadRatioY = float(InOutSampleState.OverlappedPad.Y * 2 + InOutSampleState.TileSize.Y) / float(InOutSampleState.TileSize.Y); } - float ScaleX = PadRatioX / float(InSampleState.TileCounts.X); - float ScaleY = PadRatioY / float(InSampleState.TileCounts.Y); + float ScaleX = PadRatioX / float(InOutSampleState.TileCounts.X); + float ScaleY = PadRatioY / float(InOutSampleState.TileCounts.Y); BaseProjMatrix.M[0][0] /= ScaleX; BaseProjMatrix.M[1][1] /= ScaleY; DofSensorScale = ScaleX; // this offset would be correct with no pad - float OffsetX = -((float(InSampleState.TileIndexes.X) + 0.5f - float(InSampleState.TileCounts.X) / 2.0f) * 2.0f); - float OffsetY = ((float(InSampleState.TileIndexes.Y) + 0.5f - float(InSampleState.TileCounts.Y) / 2.0f) * 2.0f); + float OffsetX = -((float(InOutSampleState.TileIndexes.X) + 0.5f - float(InOutSampleState.TileCounts.X) / 2.0f) * 2.0f); + float OffsetY = ((float(InOutSampleState.TileIndexes.Y) + 0.5f - float(InOutSampleState.TileCounts.Y) / 2.0f) * 2.0f); BaseProjMatrix.M[2][0] += OffsetX / PadRatioX; BaseProjMatrix.M[2][1] += OffsetY / PadRatioX; @@ -545,10 +562,10 @@ FSceneView* UMoviePipelineDeferredPassBase::GetSceneViewForSampleState(FSceneVie FSceneView* View = new FSceneView(ViewInitOptions); ViewFamily->Views.Add(View); - View->ViewLocation = InSampleState.FrameInfo.CurrViewLocation; - View->ViewRotation = InSampleState.FrameInfo.CurrViewRotation; + View->ViewLocation = InOutSampleState.FrameInfo.CurrViewLocation; + View->ViewRotation = InOutSampleState.FrameInfo.CurrViewRotation; // Override previous/current view transforms so that tiled renders don't use the wrong occlusion/motion blur information. - View->PreviousViewTransform = FTransform(InSampleState.FrameInfo.PrevViewRotation, InSampleState.FrameInfo.PrevViewLocation); + View->PreviousViewTransform = FTransform(InOutSampleState.FrameInfo.PrevViewRotation, InOutSampleState.FrameInfo.PrevViewLocation); View->StartFinalPostprocessSettings(View->ViewLocation); BlendPostProcessSettings(View); @@ -562,20 +579,20 @@ FSceneView* UMoviePipelineDeferredPassBase::GetSceneViewForSampleState(FSceneVie // Starting with a single tile, the middle of the tile in offset screen space is: FVector2D TilePrincipalPointOffset; - TilePrincipalPointOffset.X = (float(InSampleState.TileIndexes.X) + 0.5f - (0.5f * float(InSampleState.TileCounts.X))) * 2.0f; - TilePrincipalPointOffset.Y = (float(InSampleState.TileIndexes.Y) + 0.5f - (0.5f * float(InSampleState.TileCounts.Y))) * 2.0f; + TilePrincipalPointOffset.X = (float(InOutSampleState.TileIndexes.X) + 0.5f - (0.5f * float(InOutSampleState.TileCounts.X))) * 2.0f; + TilePrincipalPointOffset.Y = (float(InOutSampleState.TileIndexes.Y) + 0.5f - (0.5f * float(InOutSampleState.TileCounts.Y))) * 2.0f; // For the tile size ratio, we have to multiply by (1.0 + overlap) and then divide by tile num FVector2D OverlapScale; - OverlapScale.X = (1.0f + float(2 * InSampleState.OverlappedPad.X) / float(InSampleState.TileSize.X)); - OverlapScale.Y = (1.0f + float(2 * InSampleState.OverlappedPad.Y) / float(InSampleState.TileSize.Y)); + OverlapScale.X = (1.0f + float(2 * InOutSampleState.OverlappedPad.X) / float(InOutSampleState.TileSize.X)); + OverlapScale.Y = (1.0f + float(2 * InOutSampleState.OverlappedPad.Y) / float(InOutSampleState.TileSize.Y)); TilePrincipalPointOffset.X /= OverlapScale.X; TilePrincipalPointOffset.Y /= OverlapScale.Y; FVector2D TilePrincipalPointScale; - TilePrincipalPointScale.X = OverlapScale.X / float(InSampleState.TileCounts.X); - TilePrincipalPointScale.Y = OverlapScale.Y / float(InSampleState.TileCounts.Y); + TilePrincipalPointScale.X = OverlapScale.X / float(InOutSampleState.TileCounts.X); + TilePrincipalPointScale.Y = OverlapScale.Y / float(InOutSampleState.TileCounts.Y); TilePrincipalPointOffset.X *= TilePrincipalPointScale.X; TilePrincipalPointOffset.Y *= TilePrincipalPointScale.Y; @@ -585,10 +602,27 @@ FSceneView* UMoviePipelineDeferredPassBase::GetSceneViewForSampleState(FSceneVie View->EndFinalPostprocessSettings(ViewInitOptions); + + // This metadata is per-file and not per-view, but we need the blended result from the view to actually match what we rendered. + // To solve this, we'll insert metadata per renderpass, separated by render pass name. + InOutSampleState.OutputState.FileMetadata.Add(FString::Printf(TEXT("unreal/camera/%s/fstop"), *PassIdentifier.Name), View->FinalPostProcessSettings.DepthOfFieldFstop); + InOutSampleState.OutputState.FileMetadata.Add(FString::Printf(TEXT("unreal/camera/%s/focalLength"), *PassIdentifier.Name), View->FinalPostProcessSettings.DepthOfFieldFocalRegion); + InOutSampleState.OutputState.FileMetadata.Add(FString::Printf(TEXT("unreal/camera/%s/focusDistance"), *PassIdentifier.Name), View->FinalPostProcessSettings.DepthOfFieldFocalDistance); + InOutSampleState.OutputState.FileMetadata.Add(FString::Printf(TEXT("unreal/camera/%s/sensorWidth"), *PassIdentifier.Name), View->FinalPostProcessSettings.DepthOfFieldSensorWidth); + + if (GetWorld()->GetFirstPlayerController()->PlayerCameraManager) + { + const FMinimalViewInfo CameraCache = GetWorld()->GetFirstPlayerController()->PlayerCameraManager->GetCameraCachePOV(); + float AspectRatio = CameraCache.bConstrainAspectRatio ? CameraCache.AspectRatio : (InOutSampleState.BackbufferSize.X / (float)InOutSampleState.BackbufferSize.Y); + InOutSampleState.OutputState.FileMetadata.Add(FString::Printf(TEXT("unreal/camera/%s/sensorAspectRatio"), *PassIdentifier.Name), AspectRatio); + InOutSampleState.OutputState.FileMetadata.Add(FString::Printf(TEXT("unreal/camera/%s/sensorHeight"), *PassIdentifier.Name), View->FinalPostProcessSettings.DepthOfFieldSensorWidth / AspectRatio); + } + + return View; } -void UMoviePipelineDeferredPassBase::BlendPostProcessSettings(FSceneView* InView) +void UMoviePipelineImagePassBase::BlendPostProcessSettings(FSceneView* InView) { check(InView); diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineEXROutput.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineEXROutput.cpp new file mode 100644 index 000000000000..4d3bb76154b9 --- /dev/null +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineEXROutput.cpp @@ -0,0 +1,429 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MoviePipelineEXROutput.h" +#include "HAL/PlatformFileManager.h" +#include "HAL/FileManager.h" +#include "Misc/FileHelper.h" +#include "Async/Async.h" +#include "Misc/Paths.h" +#include "HAL/PlatformTime.h" +#include "Math/Float16.h" +#include "MovieRenderPipelineCoreModule.h" +#include "MoviePipelineOutputSetting.h" +#include "ImageWriteQueue.h" +#include "MoviePipeline.h" +#include "MoviePipelineImageQuantization.h" +#include "MoviePipelineMasterConfig.h" +#include "IOpenExrRTTIModule.h" +#include "Modules/ModuleManager.h" + +THIRD_PARTY_INCLUDES_START +#include "OpenEXR/include/ImfChannelList.h" +THIRD_PARTY_INCLUDES_END + +#if WITH_UNREALEXR + +class FExrMemStreamOut : public Imf::OStream +{ +public: + + FExrMemStreamOut() + : Imf::OStream("") + , Pos(0) + { + } + + // InN must be 32bit to match the abstract interface. + virtual void write(const char c[/*n*/], int32 InN) + { + int64 SrcN = (int64)InN; + int64 DestPost = Pos + SrcN; + if (DestPost > Data.Num()) + { + Data.AddUninitialized(DestPost - Data.Num()); + } + + for (int64 i = 0; i < SrcN; ++i) + { + Data[Pos + i] = c[i]; + } + Pos += SrcN; + } + + + //--------------------------------------------------------- + // Get the current writing position, in bytes from the + // beginning of the file. If the next call to write() will + // start writing at the beginning of the file, tellp() + // returns 0. + //--------------------------------------------------------- + + virtual Imf::Int64 tellp() + { + return Pos; + } + + + //------------------------------------------- + // Set the current writing position. + // After calling seekp(i), tellp() returns i. + //------------------------------------------- + + virtual void seekp(Imf::Int64 pos) + { + Pos = pos; + } + + + int64 Pos; + TArray64 Data; +}; + +bool FEXRImageWriteTask::RunTask() +{ + bool bSuccess = WriteToDisk(); + + if (OnCompleted) + { + AsyncTask(ENamedThreads::GameThread, [bSuccess, LocalOnCompleted = MoveTemp(OnCompleted)] { LocalOnCompleted(bSuccess); }); + } + + return bSuccess; +} + +void FEXRImageWriteTask::OnAbandoned() +{ + if (OnCompleted) + { + AsyncTask(ENamedThreads::GameThread, [LocalOnCompleted = MoveTemp(OnCompleted)] { LocalOnCompleted(false); }); + } +} + +bool FEXRImageWriteTask::WriteToDisk() +{ + // Ensure that the payload filename has the correct extension for the format + const TCHAR* FormatExtension = TEXT(".exr"); + if (FormatExtension && !Filename.EndsWith(FormatExtension)) + { + Filename = FPaths::GetBaseFilename(Filename, false) + FormatExtension; + } + + bool bSuccess = EnsureWritableFile(); + + if (bSuccess) + { + Imf::Compression FileCompression = Imf::Compression::NO_COMPRESSION; + switch(Compression) + { + case EEXRCompressionFormat::None: + FileCompression = Imf::Compression::NO_COMPRESSION; break; + case EEXRCompressionFormat::ZIP: + FileCompression = Imf::Compression::ZIP_COMPRESSION; break; + case EEXRCompressionFormat::PIZ: + FileCompression = Imf::Compression::PIZ_COMPRESSION; break; + default: + checkNoEntry(); + } + + // Data Window specifies how much data is in the actual file, ie: 1920x1080 + IMATH_NAMESPACE::Box2i DataWindow = IMATH_NAMESPACE::Box2i(IMATH_NAMESPACE::V2i(0,0), IMATH_NAMESPACE::V2i(Width - 1, Height - 1)); + + // Display Window specifies the total 'visible' area of the output file. + // The Display Window always starts at 0,0, but Data Window can go negative to + // support having pixels out of bounds (such as camera overscan). + IMATH_NAMESPACE::Box2i DisplayWindow = IMATH_NAMESPACE::Box2i(IMATH_NAMESPACE::V2i(0,0), IMATH_NAMESPACE::V2i(Width - 1, Height - 1)); + + Imf::Header Header(DisplayWindow, DataWindow, 1, Imath::V2f(0, 0), 1, Imf::LineOrder::INCREASING_Y, FileCompression); + + // Insert our key-value pair metadata (if any, can be an arbitrary set of key/value pairs) + AddFileMetadata(Header); + + FExrMemStreamOut OutputFile; + + { + // The FrameBuffer stores all the channels of the resulting image. + Imf::FrameBuffer FrameBuffer; + + // If we have to quantize the data (ie: Upscale 8 bit to 16 bit) we need to store them long enough for the file to get written. + TArray> QuantizedData; + + for (TUniquePtr& Layer : Layers) + { + uint8 RawBitDepth = Layer->GetBitDepth(); + check(RawBitDepth == 8 || RawBitDepth == 16 || RawBitDepth == 32); + + if (!ensureAlwaysMsgf(Layer->GetSize().X == Width && Layer->GetSize().Y == Height, TEXT("Skipping layer due to mismatched width/height from rest of EXR file!"))) + { + continue; + } + + void const* RawDataPtr; + int64 RawDataSize; + bSuccess = Layer->GetRawData(RawDataPtr, RawDataSize); + if (!bSuccess) + { + UE_LOG(LogMovieRenderPipeline, Error, TEXT("Failed to retrieve raw data from image data for writing. Bailing.")); + break; + } + + int64 BytesWritten = 0; + switch (RawBitDepth) + { + case 8: + { + TUniquePtr QuantizedPixelData = UE::MoviePipeline::QuantizeImagePixelDataToBitDepth(Layer.Get(), 16); + QuantizedData.Add(MoveTemp(QuantizedPixelData)); + BytesWritten = CompressRaw(Header, FrameBuffer, QuantizedData.Last().Get()); + } + break; + case 16: + BytesWritten = CompressRaw(Header, FrameBuffer, Layer.Get()); + break; + case 32: + BytesWritten = CompressRaw(Header, FrameBuffer, Layer.Get()); + break; + default: + checkNoEntry(); + } + + // Reserve enough space in the output file for the whole layer so we don't keep reallocating. + OutputFile.Data.Reserve(BytesWritten); + } + + // This scope ensures that IMF::Outputfile creates a complete file by closing the file when it goes out of scope. + // To complete the file, EXR seeks back into the file and writes the scanline offsets when the file is closed, + // which moves the tellp location. So file length is stored in advance for later use. The output file needs to be + // created after the header information is filled. + Imf::OutputFile ImfFile(OutputFile, Header); +#if WITH_EDITOR + try +#endif + { + ImfFile.setFrameBuffer(FrameBuffer); + ImfFile.writePixels(Height); + } +#if WITH_EDITOR + catch (const IEX_NAMESPACE::BaseExc& Exception) + { + UE_LOG(LogMovieRenderPipeline, Error, TEXT("Caught exception: %s"), Exception.message().c_str()); + } +#endif + } + + // Now that the scope has closed for the Imf::OutputFile, now we can write the data to disk. + if (bSuccess) + { + bSuccess = FFileHelper::SaveArrayToFile(OutputFile.Data, *Filename); + } + } + + if (!bSuccess) + { + UE_LOG(LogMovieRenderPipeline, Error, TEXT("Failed to write image to '%s'. The pixel format may not be compatible with this image type, or there was an error writing to that filename."), *Filename); + } + + return bSuccess; +} + +static FString GetChannelName(const FString& InLayerName, const int32 InChannelIndex, const ERGBFormat InFormat) +{ + const int32 MaxChannels = 4; + static const char* RGBAChannelNames[] = { "R", "G", "B", "A" }; + static const char* BGRAChannelNames[] = { "B", "G", "R", "A" }; + static const char* GrayChannelNames[] = { "G" }; + check(InChannelIndex < MaxChannels); + + const char** ChannelNames = BGRAChannelNames; + + switch (InFormat) + { + case ERGBFormat::RGBA: + { + ChannelNames = RGBAChannelNames; + } + break; + case ERGBFormat::BGRA: + { + ChannelNames = BGRAChannelNames; + } + break; + case ERGBFormat::Gray: + { + check(InChannelIndex < UE_ARRAY_COUNT(GrayChannelNames)); + ChannelNames = GrayChannelNames; + } + break; + default: + checkNoEntry(); + } + + if (InLayerName.Len() > 0) + { + return FString::Printf(TEXT("%s.%s"), *InLayerName, ChannelNames[InChannelIndex]); + } + else + { + return FString(ChannelNames[InChannelIndex]); + } +} + +static int32 GetComponentWidth(const EImagePixelType InPixelType) +{ + switch (InPixelType) + { + case EImagePixelType::Color: return 1; + case EImagePixelType::Float16: return 2; + case EImagePixelType::Float32: return 4; + default: + checkNoEntry(); + } + return 1; +} + +template +int64 FEXRImageWriteTask::CompressRaw(Imf::Header& InHeader, Imf::FrameBuffer& InFrameBuffer, FImagePixelData* InLayer) +{ + void const* RawDataPtr; + int64 RawDataSize; + InLayer->GetRawData(RawDataPtr, RawDataSize); + + // Look up our layer name (if any). + FString& LayerName = LayerNames.FindOrAdd(InLayer); + int32 NumChannels = InLayer->GetNumChannels(); + int32 ComponentWidth = GetComponentWidth(InLayer->GetType()); + + for (int32 Channel = 0; Channel < NumChannels; Channel++) + { + FString ChannelName = GetChannelName(LayerName, Channel, InLayer->GetPixelLayout()); + + // Insert the channel into the header with the right datatype. + InHeader.channels().insert(TCHAR_TO_ANSI(*ChannelName), Imf::Channel(OutputFormat)); + + // Now insert the data for this channel. Unreal stores them interleaved. + InFrameBuffer.insert(TCHAR_TO_ANSI(*ChannelName), // Name + Imf::Slice(OutputFormat, // Type + (char*)RawDataPtr + (ComponentWidth * Channel), // Data Start (offset by component to match interleave) + ComponentWidth * NumChannels, // xStride + Width * ComponentWidth * NumChannels)); // yStride + } + + return int64(Width) * int64(Height) * NumChannels * int64(OutputFormat == 2 ? 4 : 2); +} + +bool FEXRImageWriteTask::EnsureWritableFile() +{ + FString Directory = FPaths::GetPath(Filename); + + if (!IFileManager::Get().DirectoryExists(*Directory)) + { + IFileManager::Get().MakeDirectory(*Directory); + } + + // If the file doesn't exist, we're ok to continue + if (IFileManager::Get().FileSize(*Filename) == -1) + { + return true; + } + // If we're allowed to overwrite the file, and we deleted it ok, we can continue + else if (bOverwriteFile && FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*Filename)) + { + return true; + } + // We can't write to the file + else + { + UE_LOG(LogMovieRenderPipeline, Error, TEXT("Failed to write image to '%s'. Should Overwrite: %d - If we should have overwritten the file, we failed to delete the file. If we shouldn't have overwritten the file the file already exists so we can't replace it."), *Filename, bOverwriteFile); + return false; + } +} + +void FEXRImageWriteTask::AddFileMetadata(Imf::Header& InHeader) +{ + static const FName RTTIExtensionModuleName("UEOpenExrRTTI"); + IOpenExrRTTIModule* OpenExrModule = FModuleManager::LoadModulePtr(RTTIExtensionModuleName); + if (OpenExrModule) + { + OpenExrModule->AddFileMetadata(FileMetadata, InHeader); + } +} + +#endif // WITH_UNREALEXR + +void UMoviePipelineImageSequenceOutput_EXR::OnRecieveImageDataImpl(FMoviePipelineMergerOutputFrame* InMergedOutputFrame) +{ + // Super::OnRecieveImageDataImpl(...) is skipped because we don't want to use the generic handling, which outputs one + // file per render pass. EXRs specifically support multiple layers so we need to handle them separately. + check(InMergedOutputFrame); + + // Ensure our OpenExrRTTI module gets loaded. This needs to happen from the main thread, if it's not loaded then metadata silently fails when writing. + static const FName RTTIExtensionModuleName("UEOpenExrRTTI"); + FModuleManager::Get().LoadModule(RTTIExtensionModuleName); + + + UMoviePipelineOutputSetting* OutputSettings = GetPipeline()->GetPipelineMasterConfig()->FindSetting(); + check(OutputSettings); + + FString OutputDirectory = OutputSettings->OutputDirectory.Path; + // We need to resolve the filename format string. We combine the folder and file name into one long string first + FString FinalFilePath; + FMoviePipelineFormatArgs FinalFormatArgs; + FString FinalImageSequenceFileName; + FString ClipName; + const TCHAR* Extension = TEXT("exr"); + { + FString FileNameFormatString = OutputSettings->FileNameFormat; + + // If we're writing more than one render pass out, we need to ensure the file name has the format string in it so we don't + // overwrite the same file multiple times. Burn In overlays don't count because they get composited on top of an existing file. + const bool bIncludeRenderPass = false; + const bool bTestFrameNumber = true; + + UE::MoviePipeline::ValidateOutputFormatString(FileNameFormatString, bIncludeRenderPass, bTestFrameNumber); + + // Create specific data that needs to override + FStringFormatNamedArguments FormatOverrides; + FormatOverrides.Add(TEXT("render_pass"), TEXT("")); // Render Passes are included inside the exr file by named layers. + FormatOverrides.Add(TEXT("ext"), Extension); + + // This resolves the filename format and gathers metadata from the settings at the same time. + GetPipeline()->ResolveFilenameFormatArguments(FileNameFormatString, FormatOverrides, FinalImageSequenceFileName, FinalFormatArgs, &InMergedOutputFrame->FrameOutputState, -InMergedOutputFrame->FrameOutputState.ShotOutputFrameNumber); + + FString FilePathFormatString = OutputDirectory / FileNameFormatString; + GetPipeline()->ResolveFilenameFormatArguments(FilePathFormatString, FormatOverrides, FinalFilePath, FinalFormatArgs, &InMergedOutputFrame->FrameOutputState); + + // Create a deterministic clipname by removing frame numbers, file extension, and any trailing .'s + UE::MoviePipeline::RemoveFrameNumberFormatStrings(FileNameFormatString, true); + GetPipeline()->ResolveFilenameFormatArguments(FileNameFormatString, FormatOverrides, ClipName, FinalFormatArgs, &InMergedOutputFrame->FrameOutputState); + ClipName.RemoveFromEnd(Extension); + ClipName.RemoveFromEnd("."); + } + + TUniquePtr MultiLayerImageTask = MakeUnique(); + MultiLayerImageTask->Filename = FinalFilePath; + MultiLayerImageTask->Compression = Compression; + MultiLayerImageTask->FileMetadata = FinalFormatArgs.FileMetadata; // This is already merged by ResolveFilenameFormatArgs with the FrameOutputState. + + int32 LayerIndex = 0; + for (TPair>& RenderPassData : InMergedOutputFrame->ImageOutputData) + { + // No quantization required, just copy the data as we will move it into the image write task. + TUniquePtr PixelData = RenderPassData.Value->CopyImageData(); + + // If there is more than one layer, then we will prefix the layer. The first layer is not prefixed (and gets inserted as RGBA) + // as most programs that handle EXRs expect the main image data to be in an unnamed layer. + if (LayerIndex > 0) + { + MultiLayerImageTask->LayerNames.FindOrAdd(PixelData.Get(), RenderPassData.Key.Name); + } + MultiLayerImageTask->Width = PixelData->GetSize().X; + MultiLayerImageTask->Height = PixelData->GetSize().Y; + MultiLayerImageTask->Layers.Add(MoveTemp(PixelData)); + LayerIndex++; + } + + ImageWriteQueue->Enqueue(MoveTemp(MultiLayerImageTask)); + +#if WITH_EDITOR + GetPipeline()->AddFrameToOutputMetadata(ClipName, FinalImageSequenceFileName, InMergedOutputFrame->FrameOutputState, Extension, bOutputAlpha); +#endif +} diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineEXROutput.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineEXROutput.h new file mode 100644 index 000000000000..d3633558975b --- /dev/null +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineEXROutput.h @@ -0,0 +1,137 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "ImageWriteTask.h" +#include "ImagePixelData.h" +#include "MoviePipelineImageSequenceOutput.h" +#include "Misc/StringFormatArg.h" + +#if WITH_UNREALEXR +THIRD_PARTY_INCLUDES_START +#include "OpenEXR/include/ImfIO.h" +#include "OpenEXR/include/ImathBox.h" +#include "OpenEXR/include/ImfChannelList.h" +#include "OpenEXR/include/ImfInputFile.h" +#include "OpenEXR/include/ImfOutputFile.h" +#include "OpenEXR/include/ImfArray.h" +#include "OpenEXR/include/ImfHeader.h" +#include "OpenEXR/include/ImfStdIO.h" +#include "OpenEXR/include/ImfChannelList.h" +#include "OpenEXR/include/ImfRgbaFile.h" +THIRD_PARTY_INCLUDES_END +#endif // WITH_UNREALEXR + +#include "MoviePipelineEXROutput.generated.h" + +UENUM(BlueprintType) +enum class EEXRCompressionFormat : uint8 +{ + /** No compression is applied. */ + None, + /** Good compression quality for grainy images. Lossless.*/ + PIZ, + /** Good compression quality for images with low amounts of noise. Lossless. */ + ZIP +}; + +#if WITH_UNREALEXR +class FEXRImageWriteTask : public IImageWriteTaskBase +{ +public: + + /** The filename to write to */ + FString Filename; + + /** True if this task is allowed to overwrite an existing file, false otherwise. */ + bool bOverwriteFile; + + /** Compression method used for the resulting EXR files. */ + EEXRCompressionFormat Compression; + + /** A function to invoke on the game thread when the task has completed */ + TFunction OnCompleted; + + /** Width/Height of the image data. All samples should match this. */ + int32 Width; + + int32 Height; + + /** A set of key/value pairs to write into the exr file as metadata. */ + FStringFormatNamedArguments FileMetadata; + + /** The image data to write. Supports multiple layers of different bitdepths. */ + TArray> Layers; + + /** Optional. A mapping between the FImagePixelData and a name. The standard is that the default layer is nameless (at which point it would be omitted) and other layers are prefixed. */ + TMap LayerNames; + + FEXRImageWriteTask() + : bOverwriteFile(true) + , Compression(EEXRCompressionFormat::PIZ) + {} + +public: + + virtual bool RunTask() override final; + virtual void OnAbandoned() override final; + +private: + + /** + * Run the task, attempting to write out the raw data using the currently specified parameters + * + * @return true on success, false on any failure + */ + bool WriteToDisk(); + + /** + * Ensures that the desired output filename is writable, deleting an existing file if bOverwriteFile is true + * + * @return True if the file is writable and the task can proceed, false otherwise + */ + bool EnsureWritableFile(); + + /** + * Adds arbitrary key/value pair metadata to the header of the file. + */ + void AddFileMetadata(Imf::Header& InHeader); + + template + int64 CompressRaw(Imf::Header& InHeader, Imf::FrameBuffer& InFrameBuffer, FImagePixelData* InLayer); +}; +#endif // WITH_UNREALEXR + +UCLASS() +class MOVIERENDERPIPELINERENDERPASSES_API UMoviePipelineImageSequenceOutput_EXR : public UMoviePipelineImageSequenceOutputBase +{ + GENERATED_BODY() +public: +#if WITH_EDITOR + virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "ImgSequenceEXRSettingDisplayName", ".exr Sequence [16bit]"); } +#endif +public: + UMoviePipelineImageSequenceOutput_EXR() + { + OutputFormat = EImageFormat::EXR; + Compression = EEXRCompressionFormat::PIZ; + } + + virtual bool IsAlphaSupportedImpl() const override { return bOutputAlpha; } + virtual void OnRecieveImageDataImpl(FMoviePipelineMergerOutputFrame* InMergedOutputFrame) override; + +public: + /** + * Should we accumulate the alpha channel and write it into the resulting image? This requires r.PostProcessing.PropagateAlpha + * to be set to 1 or 2 (see "Enable Alpha Channel Support in Post Processing" under Project Settings > Rendering). This adds + * ~30% cost to the accumulation so you should not enable it unless necessary. You must delete both the sky and fog to ensure + * that they do not make all pixels opaque. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EXR") + bool bOutputAlpha; + + /** + * Which compression method should the resulting EXR file be compressed with + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EXR") + EEXRCompressionFormat Compression; +}; \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineImageSequenceOutput.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineImageSequenceOutput.cpp index 8d44fa26c0b2..5f03dd72cac7 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineImageSequenceOutput.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineImageSequenceOutput.cpp @@ -124,8 +124,10 @@ void UMoviePipelineImageSequenceOutputBase::OnRecieveImageDataImpl(FMoviePipelin // We need to resolve the filename format string. We combine the folder and file name into one long string first FString FinalFilePath; + FString FinalImageSequenceFileName; + FString ClipName; { - FString FileNameFormatString = OutputDirectory / OutputSettings->FileNameFormat; + FString FileNameFormatString = OutputSettings->FileNameFormat; // If we're writing more than one render pass out, we need to ensure the file name has the format string in it so we don't // overwrite the same file multiple times. Burn In overlays don't count because they get composited on top of an existing file. @@ -139,7 +141,18 @@ void UMoviePipelineImageSequenceOutputBase::OnRecieveImageDataImpl(FMoviePipelin FormatOverrides.Add(TEXT("render_pass"), RenderPassData.Key.Name); FormatOverrides.Add(TEXT("ext"), Extension); - FinalFilePath = GetPipeline()->ResolveFilenameFormatArguments(FileNameFormatString, InMergedOutputFrame->FrameOutputState, FormatOverrides); + // We don't support metadata on the generic file writing. + FMoviePipelineFormatArgs FinalFormatArgs; + GetPipeline()->ResolveFilenameFormatArguments(FileNameFormatString, FormatOverrides, FinalImageSequenceFileName, FinalFormatArgs, &InMergedOutputFrame->FrameOutputState, -InMergedOutputFrame->FrameOutputState.ShotOutputFrameNumber); + + FString FilePathFormatString = OutputDirectory / FileNameFormatString; + GetPipeline()->ResolveFilenameFormatArguments(FilePathFormatString, FormatOverrides, FinalFilePath, FinalFormatArgs, &InMergedOutputFrame->FrameOutputState); + + // Create a deterministic clipname by removing frame numbers, file extension, and any trailing .'s + UE::MoviePipeline::RemoveFrameNumberFormatStrings(FileNameFormatString, true); + GetPipeline()->ResolveFilenameFormatArguments(FileNameFormatString, FormatOverrides, ClipName, FinalFormatArgs, &InMergedOutputFrame->FrameOutputState); + ClipName.RemoveFromEnd(Extension); + ClipName.RemoveFromEnd("."); } TUniquePtr TileImageTask = MakeUnique(); @@ -199,15 +212,19 @@ void UMoviePipelineImageSequenceOutputBase::OnRecieveImageDataImpl(FMoviePipelin } TileImageTask->PixelData = MoveTemp(QuantizedPixelData); + +#if WITH_EDITOR + GetPipeline()->AddFrameToOutputMetadata(ClipName, FinalImageSequenceFileName, InMergedOutputFrame->FrameOutputState, Extension, Payload->bRequireTransparentOutput); +#endif GetPipeline()->AddOutputFuture(ImageWriteQueue->Enqueue(MoveTemp(TileImageTask))); } } -void UMoviePipelineImageSequenceOutputBase::GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const +void UMoviePipelineImageSequenceOutputBase::GetFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const { // Stub in a dummy extension (so people know it exists) // InOutFormatArgs.Arguments.Add(TEXT("ext"), TEXT("jpg/png/exr")); Hidden since we just always post-pend with an extension. - InOutFormatArgs.Arguments.Add(TEXT("render_pass"), TEXT("RenderPassName")); + InOutFormatArgs.FilenameArguments.Add(TEXT("render_pass"), TEXT("RenderPassName")); } diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineWaveOutput.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineWaveOutput.cpp index 81de10e02866..58cb016698ae 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineWaveOutput.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Private/MoviePipelineWaveOutput.cpp @@ -157,7 +157,9 @@ void UMoviePipelineWaveOutput::BeginFinalizeImpl() FormatOverrides.Add(TEXT("camera_name"), TEXT("Unsupported_Camera_Name_For_Output_File_BugIt")); // Create a full absolute path - FString FinalFilePath = GetPipeline()->ResolveFilenameFormatArguments(FileNameFormatString, Segment.OutputState, FormatOverrides); + FMoviePipelineFormatArgs FinalFormatArgs; + FString FinalFilePath; + GetPipeline()->ResolveFilenameFormatArguments(FileNameFormatString, FormatOverrides, /*Out*/ FinalFilePath, /*Out*/ FinalFormatArgs, &Segment.OutputState); // Remove the .{ext} string added by resolving the name format. Works for all the other types, but incompatible with our API. if (FinalFilePath.EndsWith(TEXT(".{ext}"))) diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Public/MoviePipelineDeferredPasses.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Public/MoviePipelineDeferredPasses.h index 32d4dfc24c60..f5bdd58d58be 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Public/MoviePipelineDeferredPasses.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Public/MoviePipelineDeferredPasses.h @@ -46,39 +46,82 @@ public: } }; -UCLASS(BlueprintType) -class MOVIERENDERPIPELINERENDERPASSES_API UMoviePipelineDeferredPassBase : public UMoviePipelineRenderPass +UCLASS(BlueprintType, Abstract) +class MOVIERENDERPIPELINERENDERPASSES_API UMoviePipelineImagePassBase : public UMoviePipelineRenderPass { GENERATED_BODY() public: - UMoviePipelineDeferredPassBase() + UMoviePipelineImagePassBase() : UMoviePipelineRenderPass() { - PassIdentifier = FMoviePipelinePassIdentifier("FinalImage"); + PassIdentifier = FMoviePipelinePassIdentifier("ImagePassBase"); } protected: // UMoviePipelineRenderPass API virtual void GatherOutputPassesImpl(TArray& ExpectedRenderPasses) override; + virtual void RenderSample_GameThreadImpl(const FMoviePipelineRenderPassMetrics& InSampleState) override; virtual void SetupImpl(const MoviePipeline::FMoviePipelineRenderPassInitSettings& InPassInitSettings) override; virtual void TeardownImpl() override; - virtual void RenderSample_GameThreadImpl(const FMoviePipelineRenderPassMetrics& InSampleState) override; - virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "DeferredBasePassSetting_DisplayName_Lit", "Deferred Rendering"); } // ~UMovieRenderPassAPI // FGCObject Interface static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector); // ~FGCObject Interface - FSceneView* GetSceneViewForSampleState(FSceneViewFamily* ViewFamily, const FMoviePipelineRenderPassMetrics& InSampleState); + FSceneView* GetSceneViewForSampleState(FSceneViewFamily* ViewFamily, FMoviePipelineRenderPassMetrics& InOutSampleState); void OnBackbufferSampleReady(TArray& InPixelData, FMoviePipelineRenderPassMetrics InSampleState); protected: virtual void GetViewShowFlags(FEngineShowFlags& OutShowFlag, EViewModeIndex& OutViewModeIndex) const; virtual void BlendPostProcessSettings(FSceneView* InView); virtual void SetupViewForViewModeOverride(FSceneView* View); - virtual void MoviePipelineRenderShowFlagOverride(FEngineShowFlags& OutShowFlag); - virtual void PostRendererSubmission(const FMoviePipelineRenderPassMetrics& InSampleState, FCanvas& InCanvas); + virtual void MoviePipelineRenderShowFlagOverride(FEngineShowFlags& OutShowFlag) {} + virtual void PostRendererSubmission(const FMoviePipelineRenderPassMetrics& InSampleState, FCanvas& InCanvas) {} + virtual bool IsScreenPercentageSupported() const { return true; } +public: + + +protected: + /** A temporary render target that we render the view to. */ + TWeakObjectPtr TileRenderTarget; + + /** The history for the view */ + FSceneViewStateReference ViewState; + + /** A queue of surfaces that the render targets can be copied to. If no surface is available the game thread should hold off on submitting more samples. */ + TSharedPtr SurfaceQueue; + + FMoviePipelinePassIdentifier PassIdentifier; + + /** Accessed by the Render Thread when starting up a new task. */ + FGraphEventArray OutstandingTasks; + + FGraphEventRef TaskPrereq; +}; + +UCLASS(BlueprintType) +class MOVIERENDERPIPELINERENDERPASSES_API UMoviePipelineDeferredPassBase : public UMoviePipelineImagePassBase +{ + GENERATED_BODY() + +public: + UMoviePipelineDeferredPassBase() : UMoviePipelineImagePassBase() + { + PassIdentifier = FMoviePipelinePassIdentifier("FinalImage"); + } + +protected: + // UMoviePipelineRenderPass API + virtual void SetupImpl(const MoviePipeline::FMoviePipelineRenderPassInitSettings& InPassInitSettings) override; + virtual void TeardownImpl() override; +#if WITH_EDITOR + virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "DeferredBasePassSetting_DisplayName_Lit", "Deferred Rendering"); } +#endif + virtual void PostRendererSubmission(const FMoviePipelineRenderPassMetrics& InSampleState, FCanvas& InCanvas) override; + virtual void MoviePipelineRenderShowFlagOverride(FEngineShowFlags& OutShowFlag) override; + // ~UMoviePipelineRenderPass + public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Show Flags") bool bDisableAntiAliasing; @@ -94,24 +137,10 @@ public: protected: TSharedPtr AccumulatorPool; - - /** A queue of surfaces that the render targets can be copied to. If no surface is available the game thread should hold off on submitting more samples. */ - TSharedPtr SurfaceQueue; - - /** A temporary render target that we render the view to. */ - TWeakObjectPtr TileRenderTarget; - - /** The history for the view */ - FSceneViewStateReference ViewState; - - FMoviePipelinePassIdentifier PassIdentifier; - - /** Accessed by the Render Thread when starting up a new task. */ - FGraphEventArray OutstandingTasks; - - FGraphEventRef TaskPrereq; }; + + UCLASS(BlueprintType) class UMoviePipelineDeferredPass_Unlit : public UMoviePipelineDeferredPassBase { @@ -122,7 +151,9 @@ public: { PassIdentifier = FMoviePipelinePassIdentifier("Unlit"); } +#if WITH_EDITOR virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "DeferredBasePassSetting_DisplayName_Unlit", "Deferred Rendering (Unlit)"); } +#endif virtual void GetViewShowFlags(FEngineShowFlags& OutShowFlag, EViewModeIndex& OutViewModeIndex) const override { OutShowFlag = FEngineShowFlags(EShowFlagInitMode::ESFIM_Game); @@ -140,7 +171,9 @@ public: { PassIdentifier = FMoviePipelinePassIdentifier("DetailLightingOnly"); } +#if WITH_EDITOR virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "DeferredBasePassSetting_DisplayName_DetailLighting", "Deferred Rendering (Detail Lighting)"); } +#endif virtual void GetViewShowFlags(FEngineShowFlags& OutShowFlag, EViewModeIndex& OutViewModeIndex) const override { OutShowFlag = FEngineShowFlags(EShowFlagInitMode::ESFIM_Game); @@ -159,7 +192,9 @@ public: { PassIdentifier = FMoviePipelinePassIdentifier("LightingOnly"); } +#if WITH_EDITOR virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "DeferredBasePassSetting_DisplayName_LightingOnly", "Deferred Rendering (Lighting Only)"); } +#endif virtual void GetViewShowFlags(FEngineShowFlags& OutShowFlag, EViewModeIndex& OutViewModeIndex) const override { OutShowFlag = FEngineShowFlags(EShowFlagInitMode::ESFIM_Game); @@ -178,7 +213,9 @@ public: { PassIdentifier = FMoviePipelinePassIdentifier("ReflectionsOnly"); } +#if WITH_EDITOR virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "DeferredBasePassSetting_DisplayName_ReflectionsOnly", "Deferred Rendering (Reflections Only)"); } +#endif virtual void GetViewShowFlags(FEngineShowFlags& OutShowFlag, EViewModeIndex& OutViewModeIndex) const override { OutShowFlag = FEngineShowFlags(EShowFlagInitMode::ESFIM_Game); @@ -198,7 +235,9 @@ public: { PassIdentifier = FMoviePipelinePassIdentifier("PathTracer"); } +#if WITH_EDITOR virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "DeferredBasePassSetting_DisplayName_PathTracer", "Deferred Rendering (Path Tracer)"); } +#endif virtual void GetViewShowFlags(FEngineShowFlags& OutShowFlag, EViewModeIndex& OutViewModeIndex) const override { OutShowFlag = FEngineShowFlags(EShowFlagInitMode::ESFIM_Game); diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Public/MoviePipelineImageSequenceOutput.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Public/MoviePipelineImageSequenceOutput.h index 0fb9c2fc5b73..8bd09ea95f30 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Public/MoviePipelineImageSequenceOutput.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Public/MoviePipelineImageSequenceOutput.h @@ -23,14 +23,14 @@ protected: virtual bool HasFinishedProcessingImpl() override; // ~UMovieRenderPipelineOutputContainer interface - virtual void GetFilenameFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const override; + virtual void GetFormatArguments(FMoviePipelineFormatArgs& InOutFormatArgs) const override; protected: /** The format of the image to write out */ EImageFormat OutputFormat; -private: /** A pointer to the image write queue used for asynchronously writing images */ IImageWriteQueue* ImageWriteQueue; +private: /** A fence to keep track of when the Image Write queue has fully flushed. */ TFuture FinalizeFence; @@ -93,32 +93,7 @@ public: } }; -UCLASS() -class MOVIERENDERPIPELINERENDERPASSES_API UMoviePipelineImageSequenceOutput_EXR : public UMoviePipelineImageSequenceOutputBase -{ - GENERATED_BODY() -public: -#if WITH_EDITOR - virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "ImgSequenceEXRSettingDisplayName", ".exr Sequence [16bit]"); } -#endif -public: - UMoviePipelineImageSequenceOutput_EXR() - { - OutputFormat = EImageFormat::EXR; - } - virtual bool IsAlphaSupportedImpl() const override { return bOutputAlpha; } - -public: - /** - * Should we accumulate the alpha channel and write it into the resulting image? This requires r.PostProcessing.PropagateAlpha - * to be set to 1 or 2 (see "Enable Alpha Channel Support in Post Processing" under Project Settings > Rendering). This adds - * ~30% cost to the accumulation so you should not enable it unless necessary. You must delete both the sky and fog to ensure - * that they do not make all pixels opaque. - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EXR") - bool bOutputAlpha; -}; /** * A pixel preprocessor for use with FImageWriteTask::PixelPreProcessor that does a simple alpha blend of the provided image onto the diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Public/MoviePipelineWaveOutput.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Public/MoviePipelineWaveOutput.h index a2db2c0b36c3..0bbb4e4dc679 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Public/MoviePipelineWaveOutput.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineRenderPasses/Public/MoviePipelineWaveOutput.h @@ -17,7 +17,9 @@ class MOVIERENDERPIPELINERENDERPASSES_API UMoviePipelineWaveOutput : public UMov } public: +#if WITH_EDITOR virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "AudioSettingDisplayName", ".wav Audio"); } +#endif protected: virtual void BeginFinalizeImpl() override; diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Private/MoviePipelineBurnInSetting.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Private/MoviePipelineBurnInSetting.cpp index 7df6e1f0dbb6..ffeaac407ed4 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Private/MoviePipelineBurnInSetting.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Private/MoviePipelineBurnInSetting.cpp @@ -1,6 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "MoviePipelineBurnInSetting.h" +#include "Framework/Application/SlateApplication.h" #include "Slate/WidgetRenderer.h" #include "MovieRenderPipelineDataTypes.h" #include "MoviePipelineBurnInWidget.h" diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Private/MoviePipelineWidgetRenderSetting.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Private/MoviePipelineWidgetRenderSetting.cpp index 03cb23af7e98..9178d5c11e8b 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Private/MoviePipelineWidgetRenderSetting.cpp +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Private/MoviePipelineWidgetRenderSetting.cpp @@ -114,7 +114,9 @@ void UMoviePipelineWidgetRenderer::TeardownImpl() RenderTarget = nullptr; } +#if WITH_EDITOR FText UMoviePipelineWidgetRenderer::GetFooterText(UMoviePipelineExecutorJob* InJob) const { return NSLOCTEXT("MovieRenderPipeline", "WidgetRenderSetting_NoCompositeWarning", "This will render widgets added to the Viewport to a separate texture with alpha. This is currently not composited onto the final image, and will need to be combined in post."); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Public/MoviePipelineBurnInSetting.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Public/MoviePipelineBurnInSetting.h index b00556272866..07156c83299c 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Public/MoviePipelineBurnInSetting.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Public/MoviePipelineBurnInSetting.h @@ -40,7 +40,7 @@ public: virtual bool IsValidOnShots() const override { return false; } virtual bool IsValidOnMaster() const override { return true; } public: - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(MetaClass="MoviePipelineBurnInWidget"), Category = "Movie Pipeline") + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(MetaClass="MoviePipelineBurnInWidget"), Category = "Widget Settings") FSoftClassPath BurnInClass; private: diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Public/MoviePipelineConsoleVariableSetting.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Public/MoviePipelineConsoleVariableSetting.h index 66b74c11c1db..93fb4aed52e9 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Public/MoviePipelineConsoleVariableSetting.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Public/MoviePipelineConsoleVariableSetting.h @@ -38,8 +38,11 @@ protected: return; } - PreviousConsoleVariableValues.Reset(); - PreviousConsoleVariableValues.SetNumZeroed(ConsoleVariables.Num()); + if (bOverrideValues) + { + PreviousConsoleVariableValues.Reset(); + PreviousConsoleVariableValues.SetNumZeroed(ConsoleVariables.Num()); + } int32 Index = 0; for(const TPair& KVP : ConsoleVariables) @@ -93,7 +96,7 @@ public: * An array of key/value pairs for console variable name and the value you wish to set for that cvar. * The existing value will automatically be cached and restored afterwards. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Game") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings") TMap ConsoleVariables; /** @@ -101,14 +104,14 @@ public: * after the shot, add a matching entry in the EndConsoleCommands array. Because they are commands * and not values we cannot save the preivous value automatically. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Game") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings") TArray StartConsoleCommands; /** * An array of console commands to execute when this shot is finished. Used to restore changes made by * StartConsoleCommands. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Game") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings") TArray EndConsoleCommands; private: diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Public/MoviePipelineWidgetRenderSetting.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Public/MoviePipelineWidgetRenderSetting.h index f5ecfbae6d67..510b9efd95a5 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Public/MoviePipelineWidgetRenderSetting.h +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineSettings/Public/MoviePipelineWidgetRenderSetting.h @@ -22,7 +22,6 @@ protected: virtual void TeardownImpl() override; virtual void GatherOutputPassesImpl(TArray& ExpectedRenderPasses) override; virtual void RenderSample_GameThreadImpl(const FMoviePipelineRenderPassMetrics& InSampleState) override; - virtual FText GetFooterText(UMoviePipelineExecutorJob* InJob) const override; // ~UMoviePipelineRenderPass Interface @@ -30,6 +29,7 @@ public: #if WITH_EDITOR virtual FText GetDisplayText() const override { return NSLOCTEXT("MovieRenderPipeline", "WidgetRendererSettingDisplayName", "UI Renderer (Non-Composited)"); } virtual FText GetCategoryText() const { return NSLOCTEXT("MovieRenderPipeline", "WidgetRendererSettingCategoryName", "Rendering"); } + virtual FText GetFooterText(UMoviePipelineExecutorJob* InJob) const override; #endif virtual bool IsValidOnShots() const override { return false; } virtual bool IsValidOnMaster() const override { return true; } diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/openexrRTTI/Private/OpenExrRTTIModule.cpp b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/openexrRTTI/Private/OpenExrRTTIModule.cpp new file mode 100644 index 000000000000..41927a01a4b9 --- /dev/null +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/openexrRTTI/Private/OpenExrRTTIModule.cpp @@ -0,0 +1,52 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "IOpenExrRTTIModule.h" +#include "Modules/ModuleManager.h" + +THIRD_PARTY_INCLUDES_START +#include "OpenEXR/include/ImfStringAttribute.h" +#include "OpenEXR/include/ImfIntAttribute.h" +#include "OpenEXR/include/ImfFloatAttribute.h" +THIRD_PARTY_INCLUDES_END + + +class FOpenExrRTTIModule : public IOpenExrRTTIModule +{ +public: + virtual void AddFileMetadata(const TMap& InMetadata, Imf::Header& InHeader) override + { + for (const TPair& KVP : InMetadata) + { + OPENEXR_IMF_INTERNAL_NAMESPACE::Attribute* Attribute = nullptr; + switch (KVP.Value.Type) + { + case FStringFormatArg::EType::Int: + Attribute = new OPENEXR_IMF_INTERNAL_NAMESPACE::IntAttribute(KVP.Value.IntValue); + break; + case FStringFormatArg::EType::Double: + // Not all EXR readers support the double attribute, and we can't tell double from float in the FStringFormatArgs, + // so unfortunately we have to downgrade them here. + Attribute = new OPENEXR_IMF_INTERNAL_NAMESPACE::FloatAttribute(KVP.Value.DoubleValue); + break; + case FStringFormatArg::EType::String: + Attribute = new OPENEXR_IMF_INTERNAL_NAMESPACE::StringAttribute(std::string(TCHAR_TO_ANSI(*KVP.Value.StringValue))); + break; + case FStringFormatArg::EType::StringLiteral: + Attribute = new OPENEXR_IMF_INTERNAL_NAMESPACE::StringAttribute(std::string(TCHAR_TO_ANSI(KVP.Value.StringLiteralValue))); + break; + + case FStringFormatArg::EType::UInt: + default: + ensureMsgf(false, TEXT("Failed to add Metadata to EXR file, unsupported type.")); + } + + if (Attribute) + { + InHeader.insert(std::string(TCHAR_TO_ANSI(*KVP.Key)), *Attribute); + delete Attribute; + } + } + } +}; + +IMPLEMENT_MODULE(FOpenExrRTTIModule, UEOpenExrRTTI); diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/openexrRTTI/Public/IOpenExrRTTIModule.h b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/openexrRTTI/Public/IOpenExrRTTIModule.h new file mode 100644 index 000000000000..1ce59f79c5d4 --- /dev/null +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/openexrRTTI/Public/IOpenExrRTTIModule.h @@ -0,0 +1,18 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Modules/ModuleInterface.h" +#include "Containers/Map.h" +#include "Misc/StringFormatArg.h" +#include "Containers/UnrealString.h" + +THIRD_PARTY_INCLUDES_START +#include "OpenEXR/include/ImfHeader.h" +THIRD_PARTY_INCLUDES_END + + +class UEOPENEXRRTTI_API IOpenExrRTTIModule : public IModuleInterface +{ +public: + virtual void AddFileMetadata(const TMap& InMetadata, Imf::Header& InHeader) = 0; +}; diff --git a/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/openexrRTTI/UEOpenExrRTTI.Build.cs b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/openexrRTTI/UEOpenExrRTTI.Build.cs new file mode 100644 index 000000000000..2b65f4914d9d --- /dev/null +++ b/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/openexrRTTI/UEOpenExrRTTI.Build.cs @@ -0,0 +1,34 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class UEOpenExrRTTI : ModuleRules +{ + public UEOpenExrRTTI(ReadOnlyTargetRules Target) : base(Target) + { + if (Target.Platform.IsInGroup(UnrealPlatformGroup.Windows) || Target.Platform == UnrealTargetPlatform.Mac || Target.IsInPlatformGroup(UnrealPlatformGroup.Unix)) + { + bEnableExceptions = true; + + // To write metadata into EXR files, we need to enable RTTI. To limit the spread of RTTI modules + // this module can selectively expose the functionality that needs RTTI. + bUseRTTI = true; + + PrivateDependencyModuleNames.AddRange( + new string[] { + "Core" + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] { + "UEOpenExr" + } + ); + + // Required for UEOpenExr + AddEngineThirdPartyPrivateStaticDependencies(Target, "zlib"); + } + } +} + diff --git a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/MovieRenderPipelineEncoders.uplugin b/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/MovieRenderPipelineEncoders.uplugin deleted file mode 100644 index 9121d70e819e..000000000000 --- a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/MovieRenderPipelineEncoders.uplugin +++ /dev/null @@ -1,42 +0,0 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "1.0", - "FriendlyName": "Movie Render Pipeline Video Encoders", - "Description": "This plugin provides video codec export types for the Movie Render Pipeline system. Not all codecs are supported on all platforms.", - "Category": "Other", - "CreatedBy": "Epic Games, Inc.", - "CreatedByURL": "http://epicgames.com", - "DocsURL": "", - "MarketplaceURL": "", - "SupportURL": "", - "CanContainContent": true, - "IsBetaVersion": false, - "IsExperimentalVersion": false, - "EnabledByDefault": false, - "Installed": false, - "SupportedTargetPlatforms": [ "Win64" ], - "Modules": [ - { - "Name": "MovieRenderPipelineEncoders", - "Type": "UncookedOnly", - "LoadingPhase": "Default", - "WhitelistPlatforms": [ "Win64" ], - "BlacklistTargets": [ "Server" ] - } - ], - "Plugins": [ - { - "Name": "MovieRenderPipeline", - "Enabled": true - }, - { - "Name": "AppleProResMedia", - "Enabled": true - }, - { - "Name": "AvidDNxMedia", - "Enabled": true - } - ] -} \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/MovieRenderPipelineEncoders.Build.cs b/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/MovieRenderPipelineEncoders.Build.cs deleted file mode 100644 index 7a7f10d92894..000000000000 --- a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/MovieRenderPipelineEncoders.Build.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; - -public class MovieRenderPipelineEncoders : ModuleRules -{ - public MovieRenderPipelineEncoders(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "MovieRenderPipelineCore", - "AppleProResMedia", - "AvidDNxMedia", - "SignalProcessing" - } - ); - - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "CoreUObject", - "Engine", - } - ); - - } -} diff --git a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneBindingExtensions.cpp b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneBindingExtensions.cpp index b8698b55be20..d557175fd9bd 100644 --- a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneBindingExtensions.cpp +++ b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneBindingExtensions.cpp @@ -34,12 +34,13 @@ FText UMovieSceneBindingExtensions::GetDisplayName(const FSequencerBindingProxy& return FText(); } -void UMovieSceneBindingExtensions::SetDisplayName(const FSequencerBindingProxy& InBinding, const FText& InName) +void UMovieSceneBindingExtensions::SetDisplayName(const FSequencerBindingProxy& InBinding, const FText& InDisplayName) { UMovieScene* MovieScene = InBinding.Sequence ? InBinding.Sequence->GetMovieScene() : nullptr; if (MovieScene && InBinding.BindingID.IsValid()) { - MovieScene->SetObjectDisplayName(InBinding.BindingID, InName); + MovieScene->Modify(); + MovieScene->SetObjectDisplayName(InBinding.BindingID, InDisplayName); } } @@ -69,6 +70,8 @@ void UMovieSceneBindingExtensions::SetName(const FSequencerBindingProxy& InBindi UMovieScene* MovieScene = InBinding.Sequence ? InBinding.Sequence->GetMovieScene() : nullptr; if (MovieScene && InBinding.BindingID.IsValid()) { + MovieScene->Modify(); + FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(InBinding.BindingID); if (Spawnable) { @@ -254,7 +257,40 @@ void UMovieSceneBindingExtensions::SetParent(const FSequencerBindingProxy& InBin FMovieScenePossessable* Possessable = MovieScene->FindPossessable(InBinding.BindingID); if (Possessable) { + MovieScene->Modify(); Possessable->SetParent(InParentBinding.BindingID); } } } + +void UMovieSceneBindingExtensions::MoveBindingContents(const FSequencerBindingProxy& SourceBindingId, const FSequencerBindingProxy& DestinationBindingId) +{ + UMovieScene* MovieScene = SourceBindingId.GetMovieScene(); + if (MovieScene) + { + MovieScene->Modify(); + + FMovieScenePossessable* SourcePossessable = MovieScene->FindPossessable(SourceBindingId.BindingID); + FMovieSceneSpawnable* SourceSpawnable = MovieScene->FindSpawnable(SourceBindingId.BindingID); + + FMovieScenePossessable* DestinationPossessable = MovieScene->FindPossessable(DestinationBindingId.BindingID); + FMovieSceneSpawnable* DestinationSpawnable = MovieScene->FindSpawnable(DestinationBindingId.BindingID); + + if (SourcePossessable && DestinationPossessable) + { + MovieScene->MoveBindingContents(SourcePossessable->GetGuid(), DestinationPossessable->GetGuid()); + } + else if (SourcePossessable && DestinationSpawnable) + { + MovieScene->MoveBindingContents(SourcePossessable->GetGuid(), DestinationSpawnable->GetGuid()); + } + else if (SourceSpawnable && DestinationPossessable) + { + MovieScene->MoveBindingContents(SourceSpawnable->GetGuid(), DestinationPossessable->GetGuid()); + } + else if (SourceSpawnable && DestinationSpawnable) + { + MovieScene->MoveBindingContents(SourceSpawnable->GetGuid(), DestinationSpawnable->GetGuid()); + } + } +} diff --git a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieScenePropertyTrackExtensions.cpp b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieScenePropertyTrackExtensions.cpp index 3dffdda85969..330fc0d06e83 100644 --- a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieScenePropertyTrackExtensions.cpp +++ b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieScenePropertyTrackExtensions.cpp @@ -6,6 +6,7 @@ void UMovieScenePropertyTrackExtensions::SetPropertyNameAndPath(UMovieScenePropertyTrack* Track, const FName& InPropertyName, const FString& InPropertyPath) { + Track->Modify(); Track->SetPropertyNameAndPath(InPropertyName, InPropertyPath); } diff --git a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneSectionExtensions.cpp b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneSectionExtensions.cpp index 4887172c18b7..15da50f2a101 100644 --- a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneSectionExtensions.cpp +++ b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneSectionExtensions.cpp @@ -25,6 +25,11 @@ FSequencerScriptingRange UMovieSceneSectionExtensions::GetRange(UMovieSceneSecti return FSequencerScriptingRange::FromNative(Section->GetRange(), MovieScene->GetTickResolution()); } +bool UMovieSceneSectionExtensions::HasStartFrame(UMovieSceneSection* Section) +{ + return Section->HasStartFrame(); +} + int32 UMovieSceneSectionExtensions::GetStartFrame(UMovieSceneSection* Section) { if (!Section->HasStartFrame()) @@ -63,6 +68,12 @@ float UMovieSceneSectionExtensions::GetStartFrameSeconds(UMovieSceneSection* Sec return -1.f; } +bool UMovieSceneSectionExtensions::HasEndFrame(UMovieSceneSection* Section) +{ + return Section->HasEndFrame(); +} + + int32 UMovieSceneSectionExtensions::GetEndFrame(UMovieSceneSection* Section) { if (!Section->HasEndFrame()) diff --git a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneSequenceExtensions.cpp b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneSequenceExtensions.cpp index 71ab7cd42039..3720d18df839 100644 --- a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneSequenceExtensions.cpp +++ b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneSequenceExtensions.cpp @@ -113,6 +113,26 @@ UMovieSceneTrack* UMovieSceneSequenceExtensions::AddMasterTrack(UMovieSceneSeque return nullptr; } +bool UMovieSceneSequenceExtensions::RemoveMasterTrack(UMovieSceneSequence* Sequence, UMovieSceneTrack* MasterTrack) +{ + UMovieScene* MovieScene = GetMovieScene(Sequence); + if (MovieScene) + { + if (MasterTrack->IsA(UMovieSceneCameraCutTrack::StaticClass())) + { + MovieScene->RemoveCameraCutTrack(); + return true; + } + else + { + return MovieScene->RemoveMasterTrack(*MasterTrack); + } + } + + return false; +} + + FFrameRate UMovieSceneSequenceExtensions::GetDisplayRate(UMovieSceneSequence* Sequence) { UMovieScene* MovieScene = GetMovieScene(Sequence); @@ -124,6 +144,8 @@ void UMovieSceneSequenceExtensions::SetDisplayRate(UMovieSceneSequence* Sequence UMovieScene* MovieScene = GetMovieScene(Sequence); if (MovieScene) { + MovieScene->Modify(); + MovieScene->SetDisplayRate(DisplayRate); } } @@ -139,6 +161,8 @@ void UMovieSceneSequenceExtensions::SetTickResolution(UMovieSceneSequence* Seque UMovieScene* MovieScene = GetMovieScene(Sequence); if (MovieScene) { + MovieScene->Modify(); + MovieScene->SetTickResolutionDirectly(TickResolution); } } @@ -514,6 +538,24 @@ FMovieSceneObjectBindingID UMovieSceneSequenceExtensions::MakeBindingID(UMovieSc return FMovieSceneObjectBindingID(InBinding.BindingID, SequenceID, Space); } +FSequencerBindingProxy UMovieSceneSequenceExtensions::ResolveBindingID(UMovieSceneSequence* MasterSequence, FMovieSceneObjectBindingID InObjectBindingID) +{ + UMovieSceneSequence* Sequence = MasterSequence; + + FMovieSceneCompiledDataID DataID = UMovieSceneCompiledDataManager::GetPrecompiledData()->Compile(MasterSequence); + + const FMovieSceneSequenceHierarchy* Hierarchy = UMovieSceneCompiledDataManager::GetPrecompiledData()->FindHierarchy(DataID); + if (Hierarchy) + { + if (UMovieSceneSequence* SubSequence = Hierarchy->FindSubSequence(InObjectBindingID.GetSequenceID())) + { + Sequence = SubSequence; + } + } + + return FSequencerBindingProxy(InObjectBindingID.GetGuid(), Sequence); +} + TArray UMovieSceneSequenceExtensions::GetRootFoldersInSequence(UMovieSceneSequence* Sequence) { TArray Result; @@ -539,6 +581,7 @@ UMovieSceneFolder* UMovieSceneSequenceExtensions::AddRootFolderToSequence(UMovie UMovieScene* MovieScene = Sequence->GetMovieScene(); if (MovieScene) { + MovieScene->Modify(); NewFolder = NewObject(MovieScene); NewFolder->SetFolderName(FName(*NewFolderName)); MovieScene->GetRootFolders().Add(NewFolder); @@ -564,6 +607,8 @@ int32 UMovieSceneSequenceExtensions::AddMarkedFrame(UMovieSceneSequence* Sequenc UMovieScene* MovieScene = Sequence->GetMovieScene(); if (MovieScene) { + MovieScene->Modify(); + return MovieScene->AddMarkedFrame(InMarkedFrame); } return INDEX_NONE; @@ -574,6 +619,8 @@ void UMovieSceneSequenceExtensions::DeleteMarkedFrame(UMovieSceneSequence* Seque UMovieScene* MovieScene = Sequence->GetMovieScene(); if (MovieScene) { + MovieScene->Modify(); + MovieScene->DeleteMarkedFrame(DeleteIndex); } } @@ -583,6 +630,8 @@ void UMovieSceneSequenceExtensions::DeleteMarkedFrames(UMovieSceneSequence* Sequ UMovieScene* MovieScene = Sequence->GetMovieScene(); if (MovieScene) { + MovieScene->Modify(); + MovieScene->DeleteMarkedFrames(); } } diff --git a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneTrackExtensions.cpp b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneTrackExtensions.cpp index c539a124efe3..7448a8f4f505 100644 --- a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneTrackExtensions.cpp +++ b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneTrackExtensions.cpp @@ -14,6 +14,8 @@ UMovieSceneSection* UMovieSceneTrackExtensions::AddSection(UMovieSceneTrack* Tra if (NewSection) { + Track->Modify(); + Track->AddSection(*NewSection); } @@ -29,6 +31,39 @@ void UMovieSceneTrackExtensions::RemoveSection(UMovieSceneTrack* Track, UMovieSc { if (Section) { + Track->Modify(); + Track->RemoveSection(*Section); } -} \ No newline at end of file +} + +int32 UMovieSceneTrackExtensions::GetSortingOrder(UMovieSceneTrack* Track) +{ +#if WITH_EDITORONLY_DATA + return Track->GetSortingOrder(); +#endif + return 0; +} + +void UMovieSceneTrackExtensions::SetSortingOrder(UMovieSceneTrack* Track, int32 SortingOrder) +{ +#if WITH_EDITORONLY_DATA + Track->SetSortingOrder(SortingOrder); +#endif +} + +FColor UMovieSceneTrackExtensions::GetColorTint(UMovieSceneTrack* Track) +{ +#if WITH_EDITORONLY_DATA + return Track->GetColorTint(); +#endif + return FColor(); +} + +void UMovieSceneTrackExtensions::SetColorTint(UMovieSceneTrack* Track, const FColor& ColorTint) +{ +#if WITH_EDITORONLY_DATA + Track->SetColorTint(ColorTint); +#endif +} + diff --git a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneVectorTrackExtensions.cpp b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneVectorTrackExtensions.cpp index 653489652851..8b9e8684aea7 100644 --- a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneVectorTrackExtensions.cpp +++ b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Private/ExtensionLibraries/MovieSceneVectorTrackExtensions.cpp @@ -5,6 +5,8 @@ void UMovieSceneVectorTrackExtensions::SetNumChannelsUsed(UMovieSceneVectorTrack* Track, int32 InNumChannelsUsed) { + Track->Modify(); + Track->SetNumChannelsUsed(InNumChannelsUsed); } diff --git a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneBindingExtensions.h b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneBindingExtensions.h index 107cf1ea3d5b..10522e00e4e0 100644 --- a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneBindingExtensions.h +++ b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneBindingExtensions.h @@ -51,7 +51,7 @@ public: * @param InName The display name of the binding */ UFUNCTION(BlueprintCallable, Category=Sequence, meta=(ScriptMethod)) - static void SetDisplayName(const FSequencerBindingProxy& InBinding, const FText& InName); + static void SetDisplayName(const FSequencerBindingProxy& InBinding, const FText& InDisplayName); /** * Get this binding's object non-display name @@ -87,7 +87,7 @@ public: * @param TrackType A UMovieSceneTrack class type specifying which types of track to return * @return An array containing any tracks that match the type specified */ - UFUNCTION(BlueprintCallable, Category=Sequence, meta=(ScriptMethod)) + UFUNCTION(BlueprintCallable, Category=Sequence, meta=(ScriptMethod, DeterminesOutputType="TrackType")) static TArray FindTracksByType(const FSequencerBindingProxy& InBinding, TSubclassOf TrackType); /** @@ -97,7 +97,7 @@ public: * @param TrackType A UMovieSceneTrack class type specifying the exact types of track to return * @return An array containing any tracks that are exactly the same as the type specified */ - UFUNCTION(BlueprintCallable, Category=Sequence, meta=(ScriptMethod)) + UFUNCTION(BlueprintCallable, Category=Sequence, meta=(ScriptMethod, DeterminesOutputType="TrackType")) static TArray FindTracksByExactType(const FSequencerBindingProxy& InBinding, TSubclassOf TrackType); /** @@ -171,4 +171,13 @@ public: */ UFUNCTION(BlueprintCallable, Category = Sequence, meta = (ScriptMethod)) static void SetParent(const FSequencerBindingProxy& InBinding, const FSequencerBindingProxy& InParentBinding); + + /** + * Move all the contents (tracks, child bindings) of the specified binding ID onto another + * + * @param SourceBindingId The identifier of the binding ID to move all tracks and children from + * @param DestinationBindingId The identifier of the binding ID to move the contents to + */ + UFUNCTION(BlueprintCallable, Category = Sequence, meta = (ScriptMethod)) + static void MoveBindingContents(const FSequencerBindingProxy& SourceBindingId, const FSequencerBindingProxy& DestinationBindingId); }; \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneSectionExtensions.h b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneSectionExtensions.h index 01df3d27dd53..01584a218704 100644 --- a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneSectionExtensions.h +++ b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneSectionExtensions.h @@ -24,6 +24,15 @@ public: UE_DEPRECATED(4.22, "Please use GetStartFrame and GetEndFrame instead.") static FSequencerScriptingRange GetRange(UMovieSceneSection* Section); + /** + * Has start frame + * + * @param Section The section being queried + * @return Whether this section has a valid start frame (else infinite) + */ + UFUNCTION(BlueprintCallable, Category = "Section", meta = (ScriptMethod)) + static bool HasStartFrame(UMovieSceneSection* Section); + /** * Get start frame * @@ -42,6 +51,15 @@ public: UFUNCTION(BlueprintCallable, Category = "Section", meta = (ScriptMethod)) static float GetStartFrameSeconds(UMovieSceneSection* Section); + /** + * Has end frame + * + * @param Section The section being queried + * @return Whether this section has a valid end frame (else infinite) + */ + UFUNCTION(BlueprintCallable, Category = "Section", meta = (ScriptMethod)) + static bool HasEndFrame(UMovieSceneSection* Section); + /** * Get end frame * @@ -153,7 +171,7 @@ public: * @param ChannelType The class type to look for. * @return An array containing any key channels that match the type specified */ - UFUNCTION(BlueprintCallable, Category = Section, meta = (ScriptMethod)) + UFUNCTION(BlueprintCallable, Category = Section, meta = (ScriptMethod, DeterminesOutputType="TrackType")) static TArray FindChannelsByType(UMovieSceneSection* Section, TSubclassOf ChannelType); /** diff --git a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneSequenceExtensions.h b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneSequenceExtensions.h index d0e8a9325d3a..87286e7ebd88 100644 --- a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneSequenceExtensions.h +++ b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneSequenceExtensions.h @@ -46,7 +46,7 @@ public: * @param TrackType A UMovieSceneTrack class type specifying which types of track to return * @return An array containing any tracks that match the type specified */ - UFUNCTION(BlueprintCallable, Category="Sequence", meta=(ScriptMethod)) + UFUNCTION(BlueprintCallable, Category="Sequence", meta=(ScriptMethod, DeterminesOutputType="TrackType")) static TArray FindMasterTracksByType(UMovieSceneSequence* Sequence, TSubclassOf TrackType); /** @@ -56,7 +56,7 @@ public: * @param TrackType A UMovieSceneTrack class type specifying the exact types of track to return * @return An array containing any tracks that are exactly the same as the type specified */ - UFUNCTION(BlueprintCallable, Category="Sequence", meta=(ScriptMethod)) + UFUNCTION(BlueprintCallable, Category="Sequence", meta=(ScriptMethod, DeterminesOutputType="TrackType")) static TArray FindMasterTracksByExactType(UMovieSceneSequence* Sequence, TSubclassOf TrackType); /** @@ -66,9 +66,19 @@ public: * @param TrackType A UMovieSceneTrack class type to create * @return The newly created track, if successful */ - UFUNCTION(BlueprintCallable, Category="Sequence", meta=(ScriptMethod)) + UFUNCTION(BlueprintCallable, Category="Sequence", meta=(ScriptMethod, DeterminesOutputType="TrackType")) static UMovieSceneTrack* AddMasterTrack(UMovieSceneSequence* Sequence, TSubclassOf TrackType); + /** + * Removes a master track + * + * @param Sequence The sequence to use + * @param MasterTrack The master track to remove + * @return Whether the master track was successfully removed + */ + UFUNCTION(BlueprintCallable, Category="Sequence", meta=(ScriptMethod)) + static bool RemoveMasterTrack(UMovieSceneSequence* Sequence, UMovieSceneTrack* MasterTrack); + /** * Gets this sequence's display rate * @@ -389,6 +399,18 @@ public: UFUNCTION(BlueprintCallable, Category = "Sequence", meta = (ScriptMethod)) static FMovieSceneObjectBindingID MakeBindingID(UMovieSceneSequence* MasterSequence, const FSequencerBindingProxy& InBinding, EMovieSceneObjectBindingSpace Space = EMovieSceneObjectBindingSpace::Root); + + /** + * Make a binding for the given binding ID + * + * @param MasterSequence The master sequence that contains the sequence + * @param ObjectBindingID The object binding id that has the guid and the sequence id + * @return The new binding proxy + */ + UFUNCTION(BlueprintCallable, Category = "Sequence", meta = (ScriptMethod)) + static FSequencerBindingProxy ResolveBindingID(UMovieSceneSequence* MasterSequence, FMovieSceneObjectBindingID InObjectBindingID); + + /** * Get the root folders in the provided sequence * diff --git a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneTrackExtensions.h b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneTrackExtensions.h index 70772580eec3..28bc0d98f97e 100644 --- a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneTrackExtensions.h +++ b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScripting/Public/ExtensionLibraries/MovieSceneTrackExtensions.h @@ -58,4 +58,40 @@ public: */ UFUNCTION(BlueprintCallable, Category="Track", meta=(ScriptMethod)) static void RemoveSection(UMovieSceneTrack* Track, UMovieSceneSection* Section); -}; \ No newline at end of file + + /** + * Get the sorting order for this track + * + * @param Track The track to get the sorting order from + * @return The sorting order of the requested track + */ + UFUNCTION(BlueprintCallable, Category="Track", meta=(ScriptMethod)) + static int32 GetSortingOrder(UMovieSceneTrack* Track); + + /** + * Set the sorting order for this track + * + * @param Track The track to get the sorting order from + * @param SortingOrder The sorting order to set + */ + UFUNCTION(BlueprintCallable, Category="Track", meta=(ScriptMethod)) + static void SetSortingOrder(UMovieSceneTrack* Track, int32 SortingOrder); + + /** + * Get the color tint for this track + * + * @param Track The track to get the color tint from + * @return The color tint of the requested track + */ + UFUNCTION(BlueprintCallable, Category="Track", meta=(ScriptMethod)) + static FColor GetColorTint(UMovieSceneTrack* Track); + + /** + * Set the color tint for this track + * + * @param Track The track to get the color tint from + * @param ColorTint The color tint to set + */ + UFUNCTION(BlueprintCallable, Category="Track", meta=(ScriptMethod)) + static void SetColorTint(UMovieSceneTrack* Track, const FColor& ColorTint); + }; \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScriptingEditor/Private/SequencerTools.cpp b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScriptingEditor/Private/SequencerTools.cpp index 0f9c49695313..c7b1e9eb3492 100644 --- a/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScriptingEditor/Private/SequencerTools.cpp +++ b/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScriptingEditor/Private/SequencerTools.cpp @@ -404,7 +404,7 @@ void ImportFBXCamera(UnFbx::FFbxImporter* FbxImporter, UWorld* World, UMovieScen } } //everything created now import it in. - MovieSceneToolHelpers::ImportFBXCameraToExisting(FbxImporter, InMovieScene, Player, TemplateID, InObjectBindingMap, bMatchByNameOnly, true); + MovieSceneToolHelpers::ImportFBXCameraToExisting(FbxImporter, Sequence, Player, TemplateID, InObjectBindingMap, bMatchByNameOnly, true); } bool USequencerToolsFunctionLibrary::ImportFBX(UWorld* World, ULevelSequence* Sequence, const TArray& InBindings, UMovieSceneUserImportFBXSettings* ImportFBXSettings, const FString& ImportFilename) @@ -452,7 +452,7 @@ bool USequencerToolsFunctionLibrary::ImportFBX(UWorld* World, ULevelSequence* Se ImportFBXCamera(FbxImporter, World, Sequence, MovieScene, Player, MovieSceneSequenceID::Root, ObjectBindingMap, bMatchByNameOnly, ImportFBXSettings->bCreateCameras); - bResult = MovieSceneToolHelpers::ImportFBXIfReady(World, MovieScene, Player, MovieSceneSequenceID::Root, ObjectBindingMap, ImportFBXSettings, InOutParams); + bResult = MovieSceneToolHelpers::ImportFBXIfReady(World, Sequence, Player, MovieSceneSequenceID::Root, ObjectBindingMap, ImportFBXSettings, InOutParams); } Player->Stop(); diff --git a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Evaluation/TemplateSequenceInstanceData.cpp b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Evaluation/TemplateSequenceInstanceData.cpp deleted file mode 100644 index ce287d1d927c..000000000000 --- a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Evaluation/TemplateSequenceInstanceData.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "Evaluation/TemplateSequenceInstanceData.h" diff --git a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Evaluation/TemplateSequenceSectionTemplate.cpp b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Evaluation/TemplateSequenceSectionTemplate.cpp deleted file mode 100644 index cec8b563a735..000000000000 --- a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Evaluation/TemplateSequenceSectionTemplate.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "Evaluation/TemplateSequenceSectionTemplate.h" -#include "IMovieScenePlaybackClient.h" -#include "TemplateSequence.h" -#include "Evaluation/MovieSceneEvaluationTemplateInstance.h" -#include "Sections/TemplateSequenceSection.h" - -FTemplateSequenceSectionTemplate::FTemplateSequenceSectionTemplate() -{ -} - -FTemplateSequenceSectionTemplate::FTemplateSequenceSectionTemplate(const UTemplateSequenceSection& Section) - : SectionStartTime(Section.HasStartFrame() ? Section.GetInclusiveStartFrame() : 0) -{ - if (const UTemplateSequence* InnerSequence = Cast(Section.GetSequence())) - { - if (InnerSequence->GetMovieScene() != nullptr) - { - const FGuid RootBindingID = InnerSequence->GetRootObjectBindingID(); - if (RootBindingID.IsValid()) - { - InnerOperand = FMovieSceneEvaluationOperand(Section.GetSequenceID(), RootBindingID); - } - } - } -} - -void FTemplateSequenceSectionTemplate::SetupOverrides() -{ - EnableOverrides(RequiresSetupFlag | RequiresTearDownFlag); -} - -void FTemplateSequenceSectionTemplate::Evaluate(const FMovieSceneEvaluationOperand& Operand, const FMovieSceneContext& Context, const FPersistentEvaluationData& PersistentData, FMovieSceneExecutionTokens& ExecutionTokens) const -{ -} - -void FTemplateSequenceSectionTemplate::Setup(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const -{ - if (InnerOperand.IsValid()) - { - // Add the override that will tell the child template sequence to use our object binding. - const FMovieSceneSequenceID SequenceID = PersistentData.GetSectionKey().SequenceID; - const FMovieSceneObjectBindingID AbsoluteBinding = GetAbsoluteInnerBindingID(SequenceID, Player); - const FMovieSceneEvaluationOperand AbsoluteInnerOperand(AbsoluteBinding.GetSequenceID(), AbsoluteBinding.GetGuid()); - - const FMovieSceneEvaluationOperand Operand(SequenceID, OuterBindingId); - Player.BindingOverrides.Add(AbsoluteInnerOperand, Operand); - } -} - -void FTemplateSequenceSectionTemplate::TearDown(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const -{ - if (InnerOperand.IsValid()) - { - // Clear the override. - const FMovieSceneSequenceID SequenceID = PersistentData.GetSectionKey().SequenceID; - const FMovieSceneObjectBindingID AbsoluteBinding = GetAbsoluteInnerBindingID(SequenceID, Player); - const FMovieSceneEvaluationOperand AbsoluteInnerOperand(AbsoluteBinding.GetSequenceID(), AbsoluteBinding.GetGuid()); - Player.BindingOverrides.Remove(AbsoluteInnerOperand); - } -} - -FMovieSceneObjectBindingID FTemplateSequenceSectionTemplate::GetAbsoluteInnerBindingID(FMovieSceneSequenceID LocalSequenceID, IMovieScenePlayer& Player) const -{ - // Convert the binding ID that we have, which is local to the child template sequence, into - // an absolute ID that fits in the current hierarchy. - const FMovieSceneObjectBindingID LocalBinding(InnerOperand.ObjectBindingID, InnerOperand.SequenceID, EMovieSceneObjectBindingSpace::Local); - return LocalBinding.ResolveLocalToRoot(LocalSequenceID, Player); -} diff --git a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Section/TemplateSequenceSection.cpp b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Section/TemplateSequenceSection.cpp index de9715f717df..70759dc40bb0 100644 --- a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Section/TemplateSequenceSection.cpp +++ b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Section/TemplateSequenceSection.cpp @@ -2,12 +2,11 @@ #include "Sections/TemplateSequenceSection.h" #include "TemplateSequence.h" -#include "Evaluation/TemplateSequenceInstanceData.h" -#include "UObject/SequencerObjectVersion.h" - +#include "Systems/TemplateSequenceSystem.h" UTemplateSequenceSection::UTemplateSequenceSection() { + SetBlendType(EMovieSceneBlendType::Absolute); } void UTemplateSequenceSection::OnDilated(float DilationFactor, FFrameNumber Origin) @@ -16,14 +15,32 @@ void UTemplateSequenceSection::OnDilated(float DilationFactor, FFrameNumber Orig Parameters.TimeScale /= DilationFactor; } -FMovieSceneSubSequenceData UTemplateSequenceSection::GenerateSubSequenceData(const FSubSequenceInstanceDataParams& Params) const +void UTemplateSequenceSection::ImportEntityImpl(UMovieSceneEntitySystemLinker* EntityLinker, const FEntityImportParams& Params, FImportedEntity* OutImportedEntity) { - FMovieSceneSubSequenceData SubData(*this); + using namespace UE::MovieScene; - FTemplateSequenceInstanceData InstanceData; - InstanceData.Operand = Params.Operand; - SubData.InstanceData = FMovieSceneSequenceInstanceDataPtr(InstanceData); + FTemplateSequenceComponentData ComponentData; - return SubData; + if (UTemplateSequence* TemplateSubSequence = Cast(GetSequence())) + { + const FMovieSceneObjectBindingID InnerObjectBindingID( + TemplateSubSequence->GetRootObjectBindingID(), GetSequenceID(), EMovieSceneObjectBindingSpace::Local); + + const FSequenceInstance& SequenceInstance = EntityLinker->GetInstanceRegistry()->GetInstance(Params.Sequence.InstanceHandle); + const FMovieSceneObjectBindingID AbsoluteInnerObjectBindingID( + InnerObjectBindingID.ResolveLocalToRoot(SequenceInstance.GetSequenceID(), *SequenceInstance.GetPlayer())); + + ComponentData.InnerOperand = FMovieSceneEvaluationOperand( + AbsoluteInnerObjectBindingID.GetSequenceID(), AbsoluteInnerObjectBindingID.GetGuid()); + } + + FGuid ObjectBindingID = Params.GetObjectBindingID(); + + OutImportedEntity->AddBuilder( + FEntityBuilder() + .AddConditional(FBuiltInComponentTypes::Get()->GenericObjectBinding, ObjectBindingID, ObjectBindingID.IsValid()) + .Add(FTemplateSequenceComponentTypes::Get()->TemplateSequence, ComponentData) + ); + + BuildDefaultSubSectionComponents(EntityLinker, Params, OutImportedEntity); } - diff --git a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Systems/TemplateSequenceSystem.cpp b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Systems/TemplateSequenceSystem.cpp new file mode 100644 index 000000000000..06a2eaffa999 --- /dev/null +++ b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Systems/TemplateSequenceSystem.cpp @@ -0,0 +1,120 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Systems/TemplateSequenceSystem.h" +#include "IMovieScenePlaybackClient.h" +#include "IMovieScenePlayer.h" +#include "TemplateSequence.h" +#include "Compilation/MovieSceneCompiledDataManager.h" +#include "Evaluation/MovieSceneEvaluationTemplateInstance.h" +#include "Sections/TemplateSequenceSection.h" +#include "EntitySystem/MovieSceneSpawnablesSystem.h" + +namespace UE +{ +namespace MovieScene +{ + +static TUniquePtr GTemplateSequenceComponentTypes; + +FTemplateSequenceComponentTypes* FTemplateSequenceComponentTypes::Get() +{ + if (!GTemplateSequenceComponentTypes.IsValid()) + { + GTemplateSequenceComponentTypes.Reset(new FTemplateSequenceComponentTypes); + } + return GTemplateSequenceComponentTypes.Get(); +} + +FTemplateSequenceComponentTypes::FTemplateSequenceComponentTypes() +{ + using namespace UE::MovieScene; + + FComponentRegistry* ComponentRegistry = UMovieSceneEntitySystemLinker::GetComponents(); + + ComponentRegistry->NewComponentType(&TemplateSequence, TEXT("Template Sequence")); + + ComponentRegistry->Factories.DuplicateChildComponent(TemplateSequence); +} + +} // namespace MovieScene +} // namespace UE + +UTemplateSequenceSystem::UTemplateSequenceSystem(const FObjectInitializer& ObjInit) + : UMovieSceneEntitySystem(ObjInit) +{ + using namespace UE::MovieScene; + + FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get(); + FTemplateSequenceComponentTypes* TemplateSequenceComponents = FTemplateSequenceComponentTypes::Get(); + + Phase = UE::MovieScene::ESystemPhase::Spawn; + RelevantComponent = TemplateSequenceComponents->TemplateSequence; + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + DefineImplicitPrerequisite(GetClass(), UMovieSceneSpawnablesSystem::StaticClass()); + } + + // We only need to run if there are template sequence sections starting or stopping. + ApplicableFilter.Filter.All({ TemplateSequenceComponents->TemplateSequence }); + ApplicableFilter.Filter.Any({ BuiltInComponents->Tags.NeedsLink, BuiltInComponents->Tags.NeedsUnlink }); +} + +void UTemplateSequenceSystem::OnRun(FSystemTaskPrerequisites& InPrerequisites, FSystemSubsequentTasks& Subsequents) +{ + using namespace UE::MovieScene; + + // Only run if we must + if (!ApplicableFilter.Matches(Linker->EntityManager)) + { + return; + } + + FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get(); + FTemplateSequenceComponentTypes* TemplateSequenceComponents = FTemplateSequenceComponentTypes::Get(); + + FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry(); + + auto SetupTeardownBindingOverrides = [BuiltInComponents, InstanceRegistry]( + const FEntityAllocation* Allocation, + TRead InstanceHandleAccessor, + TRead ObjectBindingIDAccessor, + TRead TemplateSequenceDataAccessor) + { + const bool bHasNeedsLink = Allocation->HasComponent(BuiltInComponents->Tags.NeedsLink); + const bool bHasNeedsUnlink = Allocation->HasComponent(BuiltInComponents->Tags.NeedsUnlink); + + TArrayView InstanceHandles = InstanceHandleAccessor.ResolveAsArray(Allocation); + TArrayView ObjectBindingIDs = ObjectBindingIDAccessor.ResolveAsArray(Allocation); + TArrayView TemplateSequenceDatas = TemplateSequenceDataAccessor.ResolveAsArray(Allocation); + + for (int32 Index = 0; Index < Allocation->Num(); ++Index) + { + const FSequenceInstance& SequenceInstance = InstanceRegistry->GetInstance(InstanceHandles[Index]); + const FGuid& ObjectBindingID = ObjectBindingIDs[Index]; + const FTemplateSequenceComponentData& TemplateSequenceData = TemplateSequenceDatas[Index]; + + IMovieScenePlayer* Player = SequenceInstance.GetPlayer(); + if (ensure(Player)) + { + if (bHasNeedsLink) + { + const FMovieSceneSequenceID SequenceID = SequenceInstance.GetSequenceID(); + const FMovieSceneEvaluationOperand OuterOperand(SequenceID, ObjectBindingID); + Player->BindingOverrides.Add(TemplateSequenceData.InnerOperand, OuterOperand); + } + else if (bHasNeedsUnlink) + { + Player->BindingOverrides.Remove(TemplateSequenceData.InnerOperand); + } + } + } + }; + + FEntityTaskBuilder() + .Read(BuiltInComponents->InstanceHandle) + .Read(BuiltInComponents->GenericObjectBinding) + .Read(TemplateSequenceComponents->TemplateSequence) + .FilterAny({ BuiltInComponents->Tags.NeedsLink, BuiltInComponents->Tags.NeedsUnlink }) + .Iterate_PerAllocation(&Linker->EntityManager, SetupTeardownBindingOverrides); +} diff --git a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Systems/TemplateSequenceSystem.h b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Systems/TemplateSequenceSystem.h new file mode 100644 index 000000000000..a84922739ebe --- /dev/null +++ b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Systems/TemplateSequenceSystem.h @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "MovieSceneObjectBindingID.h" +#include "EntitySystem/BuiltInComponentTypes.h" +#include "EntitySystem/MovieSceneCachedEntityFilterResult.h" +#include "EntitySystem/MovieSceneEntitySystem.h" +#include "Evaluation/MovieSceneEvaluationOperand.h" +#include "UObject/ObjectMacros.h" +#include "TemplateSequenceSystem.generated.h" + +namespace UE +{ +namespace MovieScene +{ + +struct FTemplateSequenceComponentData +{ + FMovieSceneEvaluationOperand InnerOperand; +}; + +struct FTemplateSequenceComponentTypes +{ +public: + static FTemplateSequenceComponentTypes* Get(); + + TComponentTypeID TemplateSequence; + +private: + FTemplateSequenceComponentTypes(); +}; + +} // namespace MovieScene +} // namespace UE + +UCLASS(MinimalAPI) +class UTemplateSequenceSystem : public UMovieSceneEntitySystem +{ +public: + GENERATED_BODY() + + UTemplateSequenceSystem(const FObjectInitializer& ObjInit); + +private: + virtual void OnRun(FSystemTaskPrerequisites& InPrerequisites, FSystemSubsequentTasks& Subsequents) override final; + +private: + UE::MovieScene::FCachedEntityFilterResult_Match ApplicableFilter; +}; diff --git a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Tracks/TemplateSequenceTrack.cpp b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Tracks/TemplateSequenceTrack.cpp index d7d6252109d9..4f6be91c5f7c 100644 --- a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Tracks/TemplateSequenceTrack.cpp +++ b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Private/Tracks/TemplateSequenceTrack.cpp @@ -6,7 +6,6 @@ #include "MovieSceneTimeHelpers.h" #include "Sections/TemplateSequenceSection.h" #include "Compilation/IMovieSceneTemplateGenerator.h" -#include "Evaluation/TemplateSequenceSectionTemplate.h" #include "Evaluation/MovieSceneEvaluationTrack.h" #define LOCTEXT_NAMESPACE "TemplateSequenceTrack" @@ -27,16 +26,6 @@ UMovieSceneSection* UTemplateSequenceTrack::CreateNewSection() return NewObject(this, NAME_None, RF_Transactional); } -FMovieSceneEvalTemplatePtr UTemplateSequenceTrack::CreateTemplateForSection(const UMovieSceneSection& InSection) const -{ - const UTemplateSequenceSection* TemplateSection = CastChecked(&InSection); - if (TemplateSection->GetSequence() != nullptr) - { - return FTemplateSequenceSectionTemplate(*TemplateSection); - } - return FMovieSceneEvalTemplatePtr(); -} - UMovieSceneSection* UTemplateSequenceTrack::AddNewTemplateSequenceSection(FFrameNumber KeyTime, UTemplateSequence* InSequence) { UTemplateSequenceSection* NewSection = Cast(CreateNewSection()); @@ -56,23 +45,6 @@ UMovieSceneSection* UTemplateSequenceTrack::AddNewTemplateSequenceSection(FFrame return NewSection; } -void UTemplateSequenceTrack::PostCompile(FMovieSceneEvaluationTrack& OutTrack, const FMovieSceneTrackCompilerArgs& Args) const -{ - // Make sure out evaluation template runs before the spawn tracks because it will have to setup the overrides. - OutTrack.SetEvaluationGroup(IMovieSceneTracksModule::GetEvaluationGroupName(EBuiltInEvaluationGroup::SpawnObjects)); - OutTrack.SetEvaluationPriority(GetEvaluationPriority()); - - // Cache our parent binding ID onto our templates. - for (FMovieSceneEvalTemplatePtr& BaseTemplate : OutTrack.GetChildTemplates()) - { - if (BaseTemplate.IsValid()) - { - FTemplateSequenceSectionTemplate* Template = static_cast(BaseTemplate.GetPtr()); - Template->OuterBindingId = Args.ObjectBindingId; - } - } -} - #if WITH_EDITORONLY_DATA FText UTemplateSequenceTrack::GetDisplayName() const diff --git a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Evaluation/TemplateSequenceInstanceData.h b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Evaluation/TemplateSequenceInstanceData.h deleted file mode 100644 index d05f39f8ba33..000000000000 --- a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Evaluation/TemplateSequenceInstanceData.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "Evaluation/MovieSceneSequenceInstanceData.h" -#include "Evaluation/MovieSceneEvaluationOperand.h" -#include "TemplateSequenceInstanceData.generated.h" - -USTRUCT() -struct FTemplateSequenceInstanceData : public FMovieSceneSequenceInstanceData -{ - GENERATED_BODY() - - UPROPERTY() - FMovieSceneEvaluationOperand Operand; - -private: - - virtual UScriptStruct& GetScriptStructImpl() const { return *StaticStruct(); } -}; diff --git a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Evaluation/TemplateSequenceSectionTemplate.h b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Evaluation/TemplateSequenceSectionTemplate.h deleted file mode 100644 index b66892ace0c0..000000000000 --- a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Evaluation/TemplateSequenceSectionTemplate.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "Evaluation/MovieSceneEvalTemplate.h" -#include "TemplateSequenceSectionTemplate.generated.h" - -class UTemplateSequenceSection; - -USTRUCT() -struct FTemplateSequenceSectionTemplate : public FMovieSceneEvalTemplate -{ - GENERATED_BODY() - - FTemplateSequenceSectionTemplate(); - FTemplateSequenceSectionTemplate(const UTemplateSequenceSection& Section); - - // FMovieSceneEvalTemplate interface. - virtual UScriptStruct& GetScriptStructImpl() const override { return *StaticStruct(); } - virtual void SetupOverrides() override; - virtual void Evaluate(const FMovieSceneEvaluationOperand& Operand, const FMovieSceneContext& Context, const FPersistentEvaluationData& PersistentData, FMovieSceneExecutionTokens& ExecutionTokens) const override; - virtual void Setup(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const override; - virtual void TearDown(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const override; - -private: - FMovieSceneObjectBindingID GetAbsoluteInnerBindingID(FMovieSceneSequenceID LocalSequenceID, IMovieScenePlayer& Player) const; - - UPROPERTY() - FFrameNumber SectionStartTime; - - UPROPERTY() - FGuid OuterBindingId; - - UPROPERTY() - FMovieSceneEvaluationOperand InnerOperand; - - friend class UTemplateSequenceTrack; -}; diff --git a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Sections/TemplateSequenceSection.h b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Sections/TemplateSequenceSection.h index 3bca2937e16a..586cd98ca5cc 100644 --- a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Sections/TemplateSequenceSection.h +++ b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Sections/TemplateSequenceSection.h @@ -4,11 +4,15 @@ #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" +#include "EntitySystem/IMovieSceneEntityProvider.h" +#include "EntitySystem/MovieSceneEntityIDs.h" #include "Sections/MovieSceneSubSection.h" #include "TemplateSequenceSection.generated.h" UCLASS(MinimalAPI) -class UTemplateSequenceSection : public UMovieSceneSubSection +class UTemplateSequenceSection + : public UMovieSceneSubSection + , public IMovieSceneEntityProvider { public: @@ -16,9 +20,10 @@ public: UTemplateSequenceSection(); - // UMovieSceneSubSection interface - virtual FMovieSceneSubSequenceData GenerateSubSequenceData(const FSubSequenceInstanceDataParams& Params) const override; - // UMovieSceneSection interface virtual void OnDilated(float DilationFactor, FFrameNumber Origin) override; + +private: + + virtual void ImportEntityImpl(UMovieSceneEntitySystemLinker* EntityLinker, const FEntityImportParams& Params, FImportedEntity* OutImportedEntity) override; }; diff --git a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Tracks/TemplateSequenceTrack.h b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Tracks/TemplateSequenceTrack.h index d296c530b75f..35e09ae25b55 100644 --- a/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Tracks/TemplateSequenceTrack.h +++ b/Engine/Plugins/MovieScene/TemplateSequence/Source/TemplateSequence/Public/Tracks/TemplateSequenceTrack.h @@ -7,7 +7,6 @@ #include "Misc/InlineValue.h" #include "Compilation/MovieSceneSegmentCompiler.h" #include "Tracks/MovieSceneSubTrack.h" -#include "Compilation/IMovieSceneTrackTemplateProducer.h" #include "TemplateSequenceTrack.generated.h" class UTemplateSequence; @@ -17,7 +16,6 @@ struct FMovieSceneEvaluationTrack; UCLASS(MinimalAPI) class UTemplateSequenceTrack : public UMovieSceneSubTrack - , public IMovieSceneTrackTemplateProducer { public: GENERATED_BODY() @@ -29,8 +27,6 @@ public: // UMovieSceneTrack interface virtual bool SupportsType(TSubclassOf SectionClass) const override; virtual UMovieSceneSection* CreateNewSection() override; - virtual FMovieSceneEvalTemplatePtr CreateTemplateForSection(const UMovieSceneSection& InSection) const override; - virtual void PostCompile(FMovieSceneEvaluationTrack& OutTrack, const FMovieSceneTrackCompilerArgs& Args) const override; #if WITH_EDITORONLY_DATA virtual FText GetDisplayName() const override; diff --git a/Engine/Plugins/NetcodeUnitTest/NUTUnrealEngine4/NUTUnrealEngine4.uplugin b/Engine/Plugins/NetcodeUnitTest/NUTUnrealEngine4/NUTUnrealEngine4.uplugin index e64f4f60bfc6..c3ab48ba12e6 100644 --- a/Engine/Plugins/NetcodeUnitTest/NUTUnrealEngine4/NUTUnrealEngine4.uplugin +++ b/Engine/Plugins/NetcodeUnitTest/NUTUnrealEngine4/NUTUnrealEngine4.uplugin @@ -10,7 +10,7 @@ "DocsURL" : "", "MarketplaceURL" : "", "SupportURL" : "", - "EnabledByDefault" : true, + "EnabledByDefault" : false, "CanContainContent" : false, "IsBetaVersion" : false, "Installed" : false, diff --git a/Engine/Plugins/NetcodeUnitTest/NetcodeUnitTest/NetcodeUnitTest.uplugin b/Engine/Plugins/NetcodeUnitTest/NetcodeUnitTest/NetcodeUnitTest.uplugin index ffd5362ee9b3..e360ddea67a3 100644 --- a/Engine/Plugins/NetcodeUnitTest/NetcodeUnitTest/NetcodeUnitTest.uplugin +++ b/Engine/Plugins/NetcodeUnitTest/NetcodeUnitTest/NetcodeUnitTest.uplugin @@ -10,7 +10,7 @@ "DocsURL" : "", "MarketplaceURL" : "", "SupportURL" : "", - "EnabledByDefault" : true, + "EnabledByDefault" : false, "CanContainContent" : false, "IsBetaVersion" : false, "Installed" : false, diff --git a/Engine/Plugins/Online/Android/OnlineSubsystemGameCircle/Source/OnlineSubsystemGameCircle/Private/OnlineSubsystemGameCircle.cpp b/Engine/Plugins/Online/Android/OnlineSubsystemGameCircle/Source/OnlineSubsystemGameCircle/Private/OnlineSubsystemGameCircle.cpp index 35dd0c31e156..525e317785ee 100644 --- a/Engine/Plugins/Online/Android/OnlineSubsystemGameCircle/Source/OnlineSubsystemGameCircle/Private/OnlineSubsystemGameCircle.cpp +++ b/Engine/Plugins/Online/Android/OnlineSubsystemGameCircle/Source/OnlineSubsystemGameCircle/Private/OnlineSubsystemGameCircle.cpp @@ -2,6 +2,7 @@ #include "OnlineSubsystemGameCircle.h" #include "Misc/ConfigCacheIni.h" +#include "Stats/Stats.h" #include FOnlineSubsystemGameCircle::FOnlineSubsystemGameCircle(FName InInstanceName) @@ -113,6 +114,8 @@ bool FOnlineSubsystemGameCircle::Init() bool FOnlineSubsystemGameCircle::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FOnlineSubsystemGameCircle_Tick); + if (!FOnlineSubsystemImpl::Tick(DeltaTime)) { return false; diff --git a/Engine/Plugins/Online/Android/OnlineSubsystemGooglePlay/Source/Private/OnlineSubsystemGooglePlay.cpp b/Engine/Plugins/Online/Android/OnlineSubsystemGooglePlay/Source/Private/OnlineSubsystemGooglePlay.cpp index cb0e0e7fd3a0..f96dcebb9946 100644 --- a/Engine/Plugins/Online/Android/OnlineSubsystemGooglePlay/Source/Private/OnlineSubsystemGooglePlay.cpp +++ b/Engine/Plugins/Online/Android/OnlineSubsystemGooglePlay/Source/Private/OnlineSubsystemGooglePlay.cpp @@ -18,6 +18,7 @@ #include "OnlineAsyncTaskGooglePlayShowLoginUI.h" #include "Misc/ConfigCacheIni.h" #include "Async/TaskGraphInterfaces.h" +#include "Stats/Stats.h" THIRD_PARTY_INCLUDES_START #include @@ -172,6 +173,8 @@ bool FOnlineSubsystemGooglePlay::Init() bool FOnlineSubsystemGooglePlay::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FOnlineSubsystemGooglePlay_Tick); + if (!FOnlineSubsystemImpl::Tick(DeltaTime)) { return false; diff --git a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlinePurchaseIOS.cpp b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlinePurchaseIOS.cpp index 4f5ef42d5723..d72c1d3e4dd4 100644 --- a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlinePurchaseIOS.cpp +++ b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlinePurchaseIOS.cpp @@ -3,6 +3,7 @@ #include "OnlinePurchaseIOS.h" #include "OnlineError.h" #include "OnlineSubsystemIOS.h" +#include "Stats/Stats.h" #import "OnlineStoreKitHelper.h" /** Take successful transactions and route them through deferred pipeline */ @@ -47,32 +48,25 @@ void FOnlinePurchaseIOS::InitStoreKit(FStoreKitHelperV2* InStoreKit) { StoreHelper = InStoreKit; - FOnProductsRequestResponseDelegate OnProductsRequestResponseDelegate; - OnProductsRequestResponseDelegate.BindRaw(this, &FOnlinePurchaseIOS::OnProductPurchaseRequestResponse); + FOnProductsRequestResponseDelegate OnProductsRequestResponseDelegate = FOnProductsRequestResponseDelegate::CreateThreadSafeSP(this, &FOnlinePurchaseIOS::OnProductPurchaseRequestResponse); [StoreHelper AddOnProductRequestResponse: OnProductsRequestResponseDelegate]; - FOnTransactionCompleteIOSDelegate OnTransactionCompleteResponseDelegate = FOnTransactionCompleteIOSDelegate::CreateRaw(this, &FOnlinePurchaseIOS::OnTransactionCompleteResponse); + FOnTransactionCompleteIOSDelegate OnTransactionCompleteResponseDelegate = FOnTransactionCompleteIOSDelegate::CreateThreadSafeSP(this, &FOnlinePurchaseIOS::OnTransactionCompleteResponse); [StoreHelper AddOnTransactionComplete: OnTransactionCompleteResponseDelegate]; - FOnTransactionRestoredIOSDelegate OnTransactionRestoredDelegate = FOnTransactionRestoredIOSDelegate::CreateRaw(this, &FOnlinePurchaseIOS::OnTransactionRestored); + FOnTransactionRestoredIOSDelegate OnTransactionRestoredDelegate = FOnTransactionRestoredIOSDelegate::CreateThreadSafeSP(this, &FOnlinePurchaseIOS::OnTransactionRestored); [StoreHelper AddOnTransactionRestored: OnTransactionRestoredDelegate]; - FOnRestoreTransactionsCompleteIOSDelegate OnRestoreTransactionsCompleteDelegate = FOnRestoreTransactionsCompleteIOSDelegate::CreateRaw(this, &FOnlinePurchaseIOS::OnRestoreTransactionsComplete); + FOnRestoreTransactionsCompleteIOSDelegate OnRestoreTransactionsCompleteDelegate = FOnRestoreTransactionsCompleteIOSDelegate::CreateThreadSafeSP(this, &FOnlinePurchaseIOS::OnRestoreTransactionsComplete); [StoreHelper AddOnRestoreTransactionsComplete: OnRestoreTransactionsCompleteDelegate]; - FOnTransactionProgressDelegate OnTransactionPurchasingDelegate = FOnTransactionProgressDelegate::CreateRaw(this, &FOnlinePurchaseIOS::OnTransactionInProgress); + FOnTransactionProgressDelegate OnTransactionPurchasingDelegate = FOnTransactionProgressDelegate::CreateThreadSafeSP(this, &FOnlinePurchaseIOS::OnTransactionInProgress); [StoreHelper AddOnPurchaseInProgress: OnTransactionPurchasingDelegate]; - FOnTransactionProgressDelegate OnTransactionDeferredDelegate = FOnTransactionProgressDelegate::CreateRaw(this, &FOnlinePurchaseIOS::OnTransactionDeferred); + FOnTransactionProgressDelegate OnTransactionDeferredDelegate = FOnTransactionProgressDelegate::CreateThreadSafeSP(this, &FOnlinePurchaseIOS::OnTransactionDeferred); [StoreHelper AddOnTransactionDeferred: OnTransactionDeferredDelegate]; } -void FOnlinePurchaseIOS::ManuallyIteratePaymentQueue() -{ - NSArray *Transactions = [[SKPaymentQueue defaultQueue] transactions]; - [StoreHelper paymentQueue : [SKPaymentQueue defaultQueue] updatedTransactions : Transactions]; -} - bool FOnlinePurchaseIOS::IsAllowedToPurchase(const FUniqueNetId& UserId) { bool bCanMakePurchases = [SKPaymentQueue canMakePayments]; @@ -207,28 +201,13 @@ void FOnlinePurchaseIOS::QueryReceipts(const FUniqueNetId& UserId, bool bRestore bSuccess = false; } } - else - { - // We don't always seem to get events from our payment queue observer, - // so manually iterate the transactions in the queue and handle them. - ManuallyIteratePaymentQueue(); - } if (bTriggerDelegate) { // Query receipts comes dynamically from the StoreKit observer - // Re-entrant WaitNextTick happening here because we want to wait 2 frames, to ensure - // Async delegates fired as a result of ManuallyIteratePaymentQueue have been handled. - TWeakPtr WeakThis(AsShared()); - Subsystem->ExecuteNextTick([WeakThis, Delegate, bSuccess]() { - FOnlinePurchaseIOSPtr StrongThis = WeakThis.Pin(); - if (StrongThis.IsValid()) - { - StrongThis->Subsystem->ExecuteNextTick([Delegate, bSuccess]() { - FOnlineError Result(bSuccess); - Delegate.ExecuteIfBound(Result); - }); - } + Subsystem->ExecuteNextTick([Delegate, bSuccess]() { + FOnlineError Result(bSuccess); + Delegate.ExecuteIfBound(Result); }); } } @@ -416,6 +395,7 @@ void FOnlinePurchaseIOS::OnTransactionDeferred(const FStoreKitTransactionData& T TWeakPtr WeakThis(AsShared()); FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([WeakThis, TransactionData](float) -> bool { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FOnlinePurchaseIOS_TestDeferredTransactions); FOnlinePurchaseIOSPtr StrongThis = WeakThis.Pin(); if (StrongThis.IsValid()) { diff --git a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreIOS.cpp b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreIOS.cpp index ed101f429faf..2ed35dd7ed09 100644 --- a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreIOS.cpp +++ b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreIOS.cpp @@ -29,15 +29,6 @@ void FOnlineStoreIOS::InitStoreKit(FStoreKitHelperV2* InStoreKit) FOnProductsRequestResponseDelegate OnProductsRequestResponseDelegate; OnProductsRequestResponseDelegate.BindRaw(this, &FOnlineStoreIOS::OnProductPurchaseRequestResponse); [StoreHelper AddOnProductRequestResponse: OnProductsRequestResponseDelegate]; - - // FOnTransactionCompleteIOSDelegate OnTransactionCompleteResponseDelegate = FOnTransactionCompleteIOSDelegate::CreateRaw(this, &FOnlineStoreIOS::OnTransactionCompleteResponse); - // [StoreHelper AddOnTransactionComplete: OnTransactionCompleteResponseDelegate]; - - // FOnTransactionRestoredIOSDelegate OnTransactionRestoredDelegate = FOnTransactionRestoredIOSDelegate::CreateRaw(this, &FOnlineStoreIOS::OnTransactionRestored); - // [StoreHelper AddOnTransactionRestored: OnTransactionRestoredDelegate]; - - // FOnRestoreTransactionsCompleteIOSDelegate OnRestoreTransactionsCompleteDelegate = FOnRestoreTransactionsCompleteIOSDelegate::CreateRaw(this, &FOnlineStoreIOS::OnRestoreTransactionsComplete); - // [StoreHelper AddOnRestoreTransactionsComplete: OnRestoreTransactionsCompleteDelegate]; } FOnlineStoreIOS::~FOnlineStoreIOS() diff --git a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreInterfaceIOS.cpp b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreInterfaceIOS.cpp index 41f543d3352a..dbd9f4e11084 100644 --- a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreInterfaceIOS.cpp +++ b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreInterfaceIOS.cpp @@ -13,13 +13,13 @@ FOnlineStoreInterfaceIOS::FOnlineStoreInterfaceIOS() { UE_LOG_ONLINE_STORE(Verbose, TEXT( "FOnlineStoreInterfaceIOS::FOnlineStoreInterfaceIOS" )); - StoreHelper = [[FStoreKitHelper alloc] init]; - - [[SKPaymentQueue defaultQueue] addTransactionObserver:StoreHelper]; bIsPurchasing = false; bIsProductRequestInFlight = false; bIsRestoringPurchases = false; + + StoreHelper = [[FStoreKitHelper alloc] init]; + [StoreHelper pumpObserverEventQueue]; } diff --git a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreKitHelper.cpp b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreKitHelper.cpp index 91fe3468c465..4eaa49f83922 100644 --- a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreKitHelper.cpp +++ b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreKitHelper.cpp @@ -1,6 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "OnlineStoreKitHelper.h" +#include "Interfaces/OnlinePurchaseInterface.h" #include "Interfaces/OnlineStoreInterface.h" #include "OnlineSubsystemIOS.h" #include "IOS/IOSAppDelegate.h" @@ -104,160 +105,201 @@ FStoreKitTransactionData::FStoreKitTransactionData(const SKPaymentTransaction* T - (id)init { self = [super init]; + + [FPaymentTransactionObserver sharedInstance].eventReceivedDelegate = self; + return self; } -(void)dealloc { + [FPaymentTransactionObserver sharedInstance].eventReceivedDelegate = nil; + [Request release]; [AvailableProducts release]; [super dealloc]; } --(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions : (NSArray *)transactions +-(void)onPaymentTransactionObserverEventReceived { - // Parse the generic transaction update into appropriate execution paths - UE_LOG_ONLINE_STORE(Log, TEXT("FStoreKitHelper::updatedTransactions")); - for (SKPaymentTransaction *transaction in transactions) - { - switch ([transaction transactionState]) - { - case SKPaymentTransactionStatePurchased: - if (FParse::Param(FCommandLine::Get(), TEXT("disableiosredeem"))) - { - UE_LOG_ONLINE_STORE(Log, TEXT("FStoreKitHelper::completeTransaction (disabled)")); - } - else - { - [self completeTransaction : transaction]; - } - break; - case SKPaymentTransactionStateFailed: - [self failedTransaction : transaction]; - break; - case SKPaymentTransactionStateRestored: - [self restoreTransaction : transaction]; - break; - case SKPaymentTransactionStatePurchasing: - [self purchaseInProgress : transaction]; - continue; - case SKPaymentTransactionStateDeferred: - [self purchaseDeferred : transaction]; - continue; - default: - UE_LOG_ONLINE_STORE(Warning, TEXT("FStoreKitHelper unhandled state: %d"), [transaction transactionState]); - break; - } - } -} - --(void)paymentQueue:(SKPaymentQueue *)queue removedTransactions : (NSArray *)transactions -{ - UE_LOG_ONLINE_STORE(Log, TEXT("FStoreKitHelper::removedTransactions")); -} - --(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue -{ - UE_LOG_ONLINE_STORE(Log, TEXT("FStoreKitHelper::paymentQueueRestoreCompletedTransactionsFinished")); - [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) { - IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(IOS_SUBSYSTEM); + [self pumpObserverEventQueue]; - FOnlineStoreInterfaceIOS* StoreInterface = (FOnlineStoreInterfaceIOS*)OnlineSub->GetStoreInterface().Get(); - if (StoreInterface->CachedPurchaseRestoreObject.IsValid()) - { - StoreInterface->CachedPurchaseRestoreObject->ReadState = EOnlineAsyncTaskState::Done; - } - StoreInterface->ProcessRestorePurchases(EInAppPurchaseState::Restored); - return true; }]; } --(void)paymentQueue: (SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError : (NSError *)error +-(void)pumpObserverEventQueue { - UE_LOG_ONLINE_STORE(Log, TEXT("FStoreKitHelper::failedRestore - %s"), *FString([error localizedDescription])); - - EInAppPurchaseState::Type CompletionState = EInAppPurchaseState::Unknown; - switch (error.code) + FPaymentTransactionObserver* Observer = [FPaymentTransactionObserver sharedInstance]; + + FPaymentTransactionObserverEvent ObserverEvent; + while ([Observer getEventQueue].Dequeue(ObserverEvent)) { - case SKErrorPaymentCancelled: - CompletionState = EInAppPurchaseState::Cancelled; - break; - case SKErrorClientInvalid: - case SKErrorStoreProductNotAvailable: - case SKErrorPaymentInvalid: - CompletionState = EInAppPurchaseState::Invalid; - break; - case SKErrorPaymentNotAllowed: - CompletionState = EInAppPurchaseState::NotAllowed; - break; - } - - [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) - { - IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(IOS_SUBSYSTEM); - FOnlineStoreInterfaceIOS* StoreInterface = (FOnlineStoreInterfaceIOS*)OnlineSub->GetStoreInterface().Get(); - if (StoreInterface->CachedPurchaseRestoreObject.IsValid()) + switch (ObserverEvent.Type) { - StoreInterface->CachedPurchaseRestoreObject->ReadState = EOnlineAsyncTaskState::Done; + case EPaymentTransactionObserverEventType::UpdatedTransaction: + [self updatedTransaction : ObserverEvent.Transaction]; + break; + case EPaymentTransactionObserverEventType::RemovedTransaction: + [self removedTransaction : ObserverEvent.Transaction]; + break; + case EPaymentTransactionObserverEventType::RestoreCompletedTransactionsFailed: + [self restoreCompletedTransactionsFailedWithError : ObserverEvent.ErrorCode]; + break; + case EPaymentTransactionObserverEventType::RestoreCompletedTransactionsFinished: + [self restoreCompletedTransactionsFinished]; + break; + default: + break; } - - StoreInterface->ProcessRestorePurchases(CompletionState); - - return true; - }]; + } +} + +-(void)updatedTransaction : (SKPaymentTransaction*)transaction +{ + UE_LOG_ONLINE_STORE(Log, TEXT("FStoreKitHelper::updatedTransaction")); + + // Parse the generic transaction update into appropriate execution paths + switch ([transaction transactionState]) + { + case SKPaymentTransactionStatePurchased: + if (FParse::Param(FCommandLine::Get(), TEXT("disableiosredeem"))) + { + UE_LOG_ONLINE_STORE(Log, TEXT("FStoreKitHelper::completeTransaction (disabled)")); + } + else + { + [self completeTransaction : transaction] ; + } + break; + case SKPaymentTransactionStateFailed: + [self failedTransaction : transaction] ; + break; + case SKPaymentTransactionStateRestored: + [self restoreTransaction : transaction] ; + break; + case SKPaymentTransactionStatePurchasing: + [self purchaseInProgress : transaction] ; + break; + case SKPaymentTransactionStateDeferred: + [self purchaseDeferred : transaction] ; + break; + default: + UE_LOG_ONLINE_STORE(Warning, TEXT("FStoreKitHelper unhandled state: %d"), [transaction transactionState]); + break; + } +} + +-(void)removedTransaction : (SKPaymentTransaction*)transaction +{ + UE_LOG_ONLINE_STORE(Log, TEXT("FStoreKitHelper::removedTransaction")); +} + +-(void)restoreCompletedTransactionsFinished +{ + UE_LOG_ONLINE_STORE(Log, TEXT("FStoreKitHelper::paymentQueueRestoreCompletedTransactionsFinished")); + + IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(IOS_SUBSYSTEM); + if (ensure(OnlineSub)) + { + FOnlineStoreInterfaceIOSPtr StoreInterface = StaticCastSharedPtr(OnlineSub->GetStoreInterface()); + if (ensure(StoreInterface.IsValid())) + { + if (StoreInterface->CachedPurchaseRestoreObject.IsValid()) + { + StoreInterface->CachedPurchaseRestoreObject->ReadState = EOnlineAsyncTaskState::Done; + } + StoreInterface->ProcessRestorePurchases(EInAppPurchaseState::Restored); + } + } +} + +-(void)restoreCompletedTransactionsFailedWithError : (int)error +{ + UE_LOG_ONLINE_STORE(Log, TEXT("FStoreKitHelper::failedRestore")); + + EInAppPurchaseState::Type CompletionState = EInAppPurchaseState::Unknown; + switch (error) + { + case SKErrorPaymentCancelled: + CompletionState = EInAppPurchaseState::Cancelled; + break; + case SKErrorClientInvalid: + case SKErrorStoreProductNotAvailable: + case SKErrorPaymentInvalid: + CompletionState = EInAppPurchaseState::Invalid; + break; + case SKErrorPaymentNotAllowed: + CompletionState = EInAppPurchaseState::NotAllowed; + break; + } + + IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(IOS_SUBSYSTEM); + if (ensure(OnlineSub)) + { + FOnlineStoreInterfaceIOSPtr StoreInterface = StaticCastSharedPtr(OnlineSub->GetStoreInterface()); + if (ensure(StoreInterface.IsValid())) + { + if (StoreInterface->CachedPurchaseRestoreObject.IsValid()) + { + StoreInterface->CachedPurchaseRestoreObject->ReadState = EOnlineAsyncTaskState::Done; + } + + StoreInterface->ProcessRestorePurchases(CompletionState); + } + } } -(void)completeTransaction: (SKPaymentTransaction *)transaction { UE_LOG_ONLINE_STORE(Log, TEXT("FStoreKitHelper::completeTransaction")); - [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) + IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(IOS_SUBSYSTEM); + if (ensure(OnlineSub)) { - IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(IOS_SUBSYSTEM); - FOnlineStoreInterfaceIOS* StoreInterface = (FOnlineStoreInterfaceIOS*)OnlineSub->GetStoreInterface().Get(); - - if (StoreInterface->CachedPurchaseStateObject.IsValid()) + FOnlineStoreInterfaceIOSPtr StoreInterface = StaticCastSharedPtr(OnlineSub->GetStoreInterface()); + if (ensure(StoreInterface.IsValid())) { - const FString ReceiptData = convertReceiptToString(transaction); - - StoreInterface->CachedPurchaseStateObject->ProvidedProductInformation.ReceiptData = ReceiptData; - StoreInterface->CachedPurchaseStateObject->ProvidedProductInformation.TransactionIdentifier = transaction.transactionIdentifier; - StoreInterface->CachedPurchaseStateObject->ReadState = EOnlineAsyncTaskState::Done; + if (StoreInterface->CachedPurchaseStateObject.IsValid()) + { + const FString ReceiptData = convertReceiptToString(transaction); + + StoreInterface->CachedPurchaseStateObject->ProvidedProductInformation.ReceiptData = ReceiptData; + StoreInterface->CachedPurchaseStateObject->ProvidedProductInformation.TransactionIdentifier = transaction.transactionIdentifier; + StoreInterface->CachedPurchaseStateObject->ReadState = EOnlineAsyncTaskState::Done; + } + + StoreInterface->TriggerOnInAppPurchaseCompleteDelegates(EInAppPurchaseState::Success); } - - StoreInterface->TriggerOnInAppPurchaseCompleteDelegates(EInAppPurchaseState::Success); - - return true; - }]; + } // Remove the transaction from the payment queue. - [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; + [[SKPaymentQueue defaultQueue]finishTransaction:transaction]; } -(void)restoreTransaction: (SKPaymentTransaction *)transaction { UE_LOG_ONLINE_STORE(Log, TEXT("FStoreKitHelper::restoreTransaction")); - [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) + IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(IOS_SUBSYSTEM); + if (ensure(OnlineSub)) { - IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(IOS_SUBSYSTEM); - FOnlineStoreInterfaceIOS* StoreInterface = (FOnlineStoreInterfaceIOS*)OnlineSub->GetStoreInterface().Get(); - - if (StoreInterface->CachedPurchaseRestoreObject.IsValid()) + FOnlineStoreInterfaceIOSPtr StoreInterface = StaticCastSharedPtr(OnlineSub->GetStoreInterface()); + if (ensure(StoreInterface.IsValid())) { - const FString ReceiptData = convertReceiptToString(transaction); - - FInAppPurchaseRestoreInfo RestoreInfo; - RestoreInfo.Identifier = FString(transaction.originalTransaction.payment.productIdentifier); - RestoreInfo.ReceiptData = ReceiptData; - StoreInterface->CachedPurchaseRestoreObject->ProvidedRestoreInformation.Add(RestoreInfo); + if (StoreInterface->CachedPurchaseRestoreObject.IsValid()) + { + const FString ReceiptData = convertReceiptToString(transaction); + + FInAppPurchaseRestoreInfo RestoreInfo; + RestoreInfo.Identifier = FString(transaction.originalTransaction.payment.productIdentifier); + RestoreInfo.ReceiptData = ReceiptData; + StoreInterface->CachedPurchaseRestoreObject->ProvidedRestoreInformation.Add(RestoreInfo); + } } - - return true; - }]; + } [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } @@ -282,18 +324,20 @@ FStoreKitTransactionData::FStoreKitTransactionData(const SKPaymentTransaction* T break; } - [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) + IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(IOS_SUBSYSTEM); + if (ensure(OnlineSub)) { - IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(IOS_SUBSYSTEM); - FOnlineStoreInterfaceIOS* StoreInterface = (FOnlineStoreInterfaceIOS*)OnlineSub->GetStoreInterface().Get(); - if (StoreInterface->CachedPurchaseStateObject.IsValid()) + FOnlineStoreInterfaceIOSPtr StoreInterface = StaticCastSharedPtr(OnlineSub->GetStoreInterface()); + if (ensure(StoreInterface.IsValid())) { - StoreInterface->CachedPurchaseStateObject->ReadState = EOnlineAsyncTaskState::Done; - } + if (StoreInterface->CachedPurchaseStateObject.IsValid()) + { + StoreInterface->CachedPurchaseStateObject->ReadState = EOnlineAsyncTaskState::Done; + } - StoreInterface->TriggerOnInAppPurchaseCompleteDelegates(CompletionState); - return true; - }]; + StoreInterface->TriggerOnInAppPurchaseCompleteDelegates(CompletionState); + } + } [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } @@ -335,8 +379,14 @@ FStoreKitTransactionData::FStoreKitTransactionData(const SKPaymentTransaction* T [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) { IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(IOS_SUBSYSTEM); - FOnlineStoreInterfaceIOS* StoreInterface = (FOnlineStoreInterfaceIOS*)OnlineSub->GetStoreInterface().Get(); - StoreInterface->ProcessProductsResponse(response); + if (ensure(OnlineSub)) + { + FOnlineStoreInterfaceIOSPtr StoreInterface = StaticCastSharedPtr(OnlineSub->GetStoreInterface()); + if (ensure(StoreInterface.IsValid())) + { + StoreInterface->ProcessProductsResponse(response); + } + } return true; }]; @@ -359,7 +409,7 @@ FStoreKitTransactionData::FStoreKitTransactionData(const SKPaymentTransaction* T #ifdef __IPHONE_7_0 if ([Request isKindOfClass : [SKReceiptRefreshRequest class]]) { - [self paymentQueue : [SKPaymentQueue defaultQueue] restoreCompletedTransactionsFailedWithError : error]; + [self restoreCompletedTransactionsFailedWithError : error.code]; [Request release]; Request = nullptr; } @@ -392,23 +442,19 @@ FStoreKitTransactionData::FStoreKitTransactionData(const SKPaymentTransaction* T [super dealloc]; } --(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue +-(void)restoreCompletedTransactionsFinished { - UE_LOG_ONLINE_STOREV2(Log, TEXT("FStoreKitHelperV2::paymentQueueRestoreCompletedTransactionsFinished")); + UE_LOG_ONLINE_STOREV2(Log, TEXT("FStoreKitHelperV2::restoreCompletedTransactionsFinished")); - [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) - { - [self OnRestoreTransactionsComplete].Broadcast(EPurchaseTransactionState::Restored); - return true; - }]; + self.OnRestoreTransactionsComplete.Broadcast(EPurchaseTransactionState::Restored); } --(void)paymentQueue: (SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError : (NSError *)error +-(void)restoreCompletedTransactionsFailedWithError : (int)errorCode { - UE_LOG_ONLINE_STOREV2(Log, TEXT("FStoreKitHelperV2::failedRestore - %s"), *FString([error localizedDescription])); + UE_LOG_ONLINE_STOREV2(Log, TEXT("FStoreKitHelperV2::failedRestore")); EPurchaseTransactionState CompletionState = EPurchaseTransactionState::Failed; - switch (error.code) + switch (errorCode) { case SKErrorPaymentCancelled: CompletionState = EPurchaseTransactionState::Canceled; @@ -423,11 +469,7 @@ FStoreKitTransactionData::FStoreKitTransactionData(const SKPaymentTransaction* T break; } - [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) - { - self.OnRestoreTransactionsComplete.Broadcast(CompletionState); - return true; - }]; + self.OnRestoreTransactionsComplete.Broadcast(CompletionState); } -(void)makePurchase:(NSArray*)products WithUserId: (const FString&) userId SimulateAskToBuy: (bool) bAskToBuy; @@ -496,7 +538,6 @@ FStoreKitTransactionData::FStoreKitTransactionData(const SKPaymentTransaction* T -(void)completeTransaction: (SKPaymentTransaction *)transaction { - FStoreKitTransactionData TransactionData(transaction); UE_LOG_ONLINE_STOREV2(Log, TEXT("FStoreKitHelperV2::completeTransaction")); EPurchaseTransactionState Result = EPurchaseTransactionState::Failed; @@ -507,12 +548,8 @@ FStoreKitTransactionData::FStoreKitTransactionData(const SKPaymentTransaction* T Result = EPurchaseTransactionState::Purchased; } - [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) - { - // Notify listeners of the request completion - self.OnTransactionCompleteResponse.Broadcast(Result, TransactionData); - return true; - }]; + // Notify listeners of the request completion + self.OnTransactionCompleteResponse.Broadcast(Result, FStoreKitTransactionData(transaction)); // Transaction must be finalized before removed from the queue [self.PendingTransactions addObject:transaction]; @@ -538,13 +575,8 @@ FStoreKitTransactionData::FStoreKitTransactionData(const SKPaymentTransaction* T UE_LOG_ONLINE_STOREV2(Log, TEXT("FStoreKitHelperV2::failedTransaction State=%s"), LexToString(CompletionState)); - FStoreKitTransactionData TransactionData(transaction); - [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) - { - // Notify listeners of the request completion - self.OnTransactionCompleteResponse.Broadcast(CompletionState, TransactionData); - return true; - }]; + // Notify listeners of the request completion + self.OnTransactionCompleteResponse.Broadcast(CompletionState, FStoreKitTransactionData(transaction)); // Remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; @@ -552,14 +584,9 @@ FStoreKitTransactionData::FStoreKitTransactionData(const SKPaymentTransaction* T -(void)restoreTransaction: (SKPaymentTransaction *)transaction { - FStoreKitTransactionData TransactionData(transaction); UE_LOG_ONLINE_STOREV2(Log, TEXT("FStoreKitHelperV2::restoreTransaction")); - [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) - { - self.OnTransactionRestored.Broadcast(TransactionData); - return true; - }]; + self.OnTransactionRestored.Broadcast(FStoreKitTransactionData(transaction)); // @todo Transaction must be finalized before removed from the queue? //[self.PendingTransactions addObject:transaction]; @@ -569,28 +596,18 @@ FStoreKitTransactionData::FStoreKitTransactionData(const SKPaymentTransaction* T -(void)purchaseInProgress: (SKPaymentTransaction *)transaction { - FStoreKitTransactionData TransactionData(transaction); UE_LOG_ONLINE_STOREV2(Log, TEXT("FStoreKitHelperV2::purchaseInProgress")); - [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) - { - // Notify listeners a purchase is in progress - self.OnTransactionPurchaseInProgress.Broadcast(TransactionData); - return true; - }]; + // Notify listeners a purchase is in progress + self.OnTransactionPurchaseInProgress.Broadcast(FStoreKitTransactionData(transaction)); } -(void)purchaseDeferred: (SKPaymentTransaction *)transaction { - FStoreKitTransactionData TransactionData(transaction); UE_LOG_ONLINE_STOREV2(Log, TEXT("FStoreKitHelperV2::purchaseDeferred")); - [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) - { - // Notify listeners a purchase has been deferred - self.OnTransactionDeferred.Broadcast(TransactionData); - return true; - }]; + // Notify listeners a purchase has been deferred + self.OnTransactionDeferred.Broadcast(FStoreKitTransactionData(transaction)); } -(void)finalizeTransaction: (const FString&) receiptId diff --git a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreKitHelper.h b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreKitHelper.h index 456d05c4a3b7..5c293fcc7f74 100644 --- a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreKitHelper.h +++ b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineStoreKitHelper.h @@ -14,6 +14,7 @@ #include "OnlineSubsystemTypes.h" #include "OnlineStoreIOS.h" +#include "IOS/IOSPaymentTransactionObserver.h" /** * Holds in a common format the data that comes out of an SKPaymentTransaction @@ -132,7 +133,7 @@ typedef FOnProductsRequestResponse::FDelegate FOnProductsRequestResponseDelegate * Helper class, which allows us to manage IAP product information requests, AND transactions * (legacy version, used by OnlineStoreInterface.h, mutually exclusive with FStoreKitHelperV2) */ -@interface FStoreKitHelper : NSObject +@interface FStoreKitHelper : NSObject { }; @@ -141,6 +142,9 @@ typedef FOnProductsRequestResponse::FDelegate FOnProductsRequestResponseDelegate /** collection of available products attaced through a store kit request */ @property (nonatomic, strong) NSArray *AvailableProducts; +/** Pump any events that are enqueued on the observer. */ +-(void)pumpObserverEventQueue; + /** Helper fn to start a store kit purchase request */ -(void)makePurchase:(NSMutableSet*)productIDs; diff --git a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineSubsystemIOS.cpp b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineSubsystemIOS.cpp index 0645b6fbe4f5..d9b511f39756 100644 --- a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineSubsystemIOS.cpp +++ b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Private/OnlineSubsystemIOS.cpp @@ -5,6 +5,7 @@ #import "OnlineStoreKitHelper.h" #import "OnlineAppStoreUtils.h" #include "Misc/ConfigCacheIni.h" +#include "Stats/Stats.h" FOnlineSubsystemIOS::FOnlineSubsystemIOS(FName InInstanceName) : FOnlineSubsystemImpl(IOS_SUBSYSTEM, InInstanceName) @@ -210,15 +211,15 @@ void FOnlineSubsystemIOS::InitStoreKitHelper() // Give each interface a chance to bind to the store kit helper StoreV2Interface->InitStoreKit(StoreHelper); PurchaseInterface->InitStoreKit(StoreHelper); - - // Bind the observer after the interfaces have bound their delegates - [[SKPaymentQueue defaultQueue] addTransactionObserver:StoreHelper]; + + // Pump the event queue to handle any events that came in between launch and now. + [StoreHelper pumpObserverEventQueue]; } void FOnlineSubsystemIOS::CleanupStoreKitHelper() { - // @todo MetalMRT: This needs to be analyzed with ASAN - but this pointer is garbage on shutdown... - // [StoreHelper release]; + [StoreHelper release]; + StoreHelper = nil; } void FOnlineSubsystemIOS::InitAppStoreHelper() @@ -240,6 +241,8 @@ FAppStoreUtils* FOnlineSubsystemIOS::GetAppStoreUtils() bool FOnlineSubsystemIOS::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FOnlineSubsystemIOS_Tick); + if (!FOnlineSubsystemImpl::Tick(DeltaTime)) { return false; diff --git a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Public/OnlinePurchaseIOS.h b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Public/OnlinePurchaseIOS.h index 70e6cd89ac12..3fdcdf8b63f0 100644 --- a/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Public/OnlinePurchaseIOS.h +++ b/Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Source/Public/OnlinePurchaseIOS.h @@ -104,9 +104,6 @@ public: /** Initialize the FStoreKitHelper for interaction with the app store */ void InitStoreKit(FStoreKitHelperV2* InStoreKit); - /** Manually iterate the payment transaction queue and fire off appropriate callbacks in StoreKitHelper. */ - void ManuallyIteratePaymentQueue(); - private: /** delegate fired when a product request completes */ diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Hotfix/Private/UpdateManager.cpp b/Engine/Plugins/Online/OnlineFramework/Source/Hotfix/Private/UpdateManager.cpp index 7273f4581ffa..6440c1c6331f 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Hotfix/Private/UpdateManager.cpp +++ b/Engine/Plugins/Online/OnlineFramework/Source/Hotfix/Private/UpdateManager.cpp @@ -12,6 +12,7 @@ #include "Misc/CoreDelegates.h" #include "InstallBundleManagerInterface.h" #include "InstallBundleUtils.h" +#include "Stats/Stats.h" #include "OnlineHotfixManager.h" @@ -622,6 +623,7 @@ void UUpdateManager::SetUpdateState(EUpdateState NewState) bool UUpdateManager::Tick(float InDeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_UUpdateManager_Tick); if (CurrentUpdateState == EUpdateState::WaitingOnInitialLoad) { WorstNumFilesPendingLoadViewed = FMath::Max(GetNumAsyncPackages(), WorstNumFilesPendingLoadViewed); diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/Party/PartyPlatformSessionMonitor.cpp b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/Party/PartyPlatformSessionMonitor.cpp index f0ffec996be3..0e8380060503 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/Party/PartyPlatformSessionMonitor.cpp +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/Party/PartyPlatformSessionMonitor.cpp @@ -16,6 +16,7 @@ #include "OnlineSubsystemUtils.h" #include "Containers/Ticker.h" #include "Engine/LocalPlayer.h" +#include "Stats/Stats.h" static bool IsTencentPlatform() { @@ -105,10 +106,14 @@ static FAutoConsoleVariableRef CVar_EstablishSessionRetryDelay( ////////////////////////////////////////////////////////////////////////// bool FPartyPlatformSessionManager::DoesOssNeedPartySession(FName OssName) { +#ifdef OSS_PARTY_PLATFORM_SESSION_REQUIRED + return OSS_PARTY_PLATFORM_SESSION_REQUIRED; +#else const bool bIsPS4 = OssName.IsEqual(PS4_SUBSYSTEM); const bool bIsXB1 = OssName.IsEqual(LIVE_SUBSYSTEM); const bool bIsTencent = OssName.IsEqual(TENCENT_SUBSYSTEM); return bIsPS4 || bIsXB1 || bIsTencent; +#endif } TSharedRef FPartyPlatformSessionManager::Create(USocialManager& InSocialManager) @@ -194,6 +199,7 @@ bool FPartyPlatformSessionManager::FindSessionInternal(const FSessionId& Session FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda( [AsWeakPtr, SessionId, SessionOwnerId, LocalUserPlatformId, OnAttemptComplete, this](float) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FPartyPlatformSessionManager_FindSessionAttempt); if (AsWeakPtr.IsValid()) { if (ForcePlatformSessionFindFailure != 0) @@ -509,6 +515,7 @@ void FPartyPlatformSessionMonitor::CreateSession(const FUniqueNetIdRepl& LocalUs FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda( [AsWeakPtr, SessionSettings, LocalUserPlatformId, this] (float) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FPartyPlatformSessionManager_CreateSessionAttempt); if (AsWeakPtr.IsValid()) { if (ForcePlatformSessionCreationFailure != 0) @@ -631,6 +638,7 @@ void FPartyPlatformSessionMonitor::JoinSession(const FOnlineSessionSearchResult& FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda( [AsWeakPtr, SearchResultCopy, LocalUserPlatformId, this] (float) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FPartyPlatformSessionManager_JoinSessionAttempt); if (AsWeakPtr.IsValid()) { if (ForcePlatformSessionCreationFailure != 0) @@ -1058,6 +1066,8 @@ bool FPartyPlatformSessionMonitor::ConfigurePlatformSessionSettings(FOnlineSessi bool FPartyPlatformSessionMonitor::HandleRetryEstablishingSession(float) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FPartyPlatformSessionMonitor_HandleRetryEstablishingSession); + RetryTickerHandle.Reset(); // Do a full re-evaluation of our target session, since things may have changed substantially since the last attempt @@ -1085,7 +1095,7 @@ void FPartyPlatformSessionMonitor::ProcessJoinFailure() bool FPartyPlatformSessionMonitor::HandleQueuedSessionUpdate(float) { - QUICK_SCOPE_CYCLE_COUNTER(STAT_USocialParty_HandleQueuedSessionUpdate); + QUICK_SCOPE_CYCLE_COUNTER(STAT_FPartyPlatformSessionMonitor_HandleQueuedSessionUpdate); bHasQueuedSessionUpdate = false; if (ShutdownState == EMonitorShutdownState::None && DoesLocalUserOwnPlatformSession()) diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/SocialManager.cpp b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/SocialManager.cpp index 8cdc84f69127..5e83a7a38893 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/SocialManager.cpp +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/SocialManager.cpp @@ -1246,6 +1246,15 @@ bool USocialManager::Exec(class UWorld* InWorld, const TCHAR* Cmd, FOutputDevice { return true; } + + for (USocialToolkit* Toolkit : SocialToolkits) + { + if (Toolkit && + Toolkit->Exec(InWorld, Cmd, Out)) + { + return true; + } + } return true; } return false; diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/SocialToolkit.cpp b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/SocialToolkit.cpp index f82208ccf065..c3371fa6328f 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/SocialToolkit.cpp +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/SocialToolkit.cpp @@ -7,6 +7,7 @@ #include "User/SocialUser.h" #include "User/SocialUserList.h" #include "Chat/SocialChatManager.h" +#include "Stats/Stats.h" #include "OnlineSubsystem.h" #include "OnlineSubsystemUtils.h" @@ -1098,6 +1099,11 @@ void USocialToolkit::HandleExistingPartyInvites(ESocialSubsystem SubsystemType) } } +bool USocialToolkit::Exec(class UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Out) +{ + return false; +} + #if WITH_EDITOR void USocialToolkit::Debug_OnStartRandomizeUserPresence(uint8 NumRandomUser, float TickerTimer) { @@ -1136,6 +1142,7 @@ void USocialToolkit::Debug_OnStopRandomizeUserPresence(bool bClearGeneratedPrese bool USocialToolkit::Debug_HandleRandomizeUserPresenceTick(float DeltaTime, uint8 NumRandomUser) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_USocialToolkit_Debug_HandleRandomizeUserPresenceTick); Debug_ChangeRandomUserPresence(NumRandomUser); return true; } diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/User/SocialUserList.cpp b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/User/SocialUserList.cpp index e33f35d24ab2..0f55a6cae012 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/User/SocialUserList.cpp +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/User/SocialUserList.cpp @@ -4,6 +4,7 @@ #include "User/SocialUser.h" #include "SocialToolkit.h" #include "Party/PartyMember.h" +#include "Stats/Stats.h" #include "Containers/Ticker.h" #include "SocialSettings.h" @@ -496,6 +497,7 @@ struct FUserSortData bool FSocialUserList::HandleAutoUpdateList(float) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FSocialUserList_HandleAutoUpdateList); UpdateListInternal(); return true; } diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyDataReplicator.h b/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyDataReplicator.h index 99e82e4f76f1..13f41b6688ee 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyDataReplicator.h +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyDataReplicator.h @@ -6,6 +6,7 @@ #include "UObject/GCObject.h" #include "Containers/Ticker.h" #include "Interfaces/OnlinePartyInterface.h" +#include "Stats/Stats.h" /** Util exclusively for use by TPartyDataReplicator to circumvent circular include header issues (we can't include SocialParty.h or PartyMember.h here) */ class FPartyDataReplicatorHelper @@ -112,6 +113,8 @@ private: bool DeferredHandleReplicateChanges(float) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_TPartyDataReplicator_DeferredHandleReplicateChanges); + UpdateTickerHandle.Reset(); FOnlinePartyData OnlinePartyData; diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/SocialQuery.h b/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/SocialQuery.h index efe359b762e8..a838ec888d22 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/SocialQuery.h +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/SocialQuery.h @@ -6,6 +6,7 @@ #include "OnlineSubsystem.h" #include "SocialToolkit.h" #include "Misc/ConfigCacheIni.h" +#include "Stats/Stats.h" DECLARE_DELEGATE_TwoParams(FOnQueryCompleted, FName, const TSharedRef&); @@ -89,6 +90,7 @@ public: bool HandleExecuteQueries(float) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FSocialQueryManager_HandleExecuteQueries); // Execute all pending queries TArray>> AllQueries; CurrentQueriesById.GenerateValueArray(AllQueries); diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/SocialToolkit.h b/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/SocialToolkit.h index 5e75c96132e3..2133fd4e9e38 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/SocialToolkit.h +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/SocialToolkit.h @@ -33,7 +33,7 @@ DECLARE_DELEGATE_OneParam(FUserDependentAction, USocialUser&); /** Represents the full suite of social functionality available to a given LocalPlayer */ UCLASS(Within = SocialManager) -class PARTY_API USocialToolkit : public UObject +class PARTY_API USocialToolkit : public UObject, public FExec { GENERATED_BODY() @@ -69,6 +69,9 @@ public: ULocalPlayer& GetOwningLocalPlayer() const; const TArray& GetAllUsers() const { return AllUsers; } + // FExec + virtual bool Exec(class UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Out) override; + /** Finds a SocialUser given a unique net ID from any OSS */ USocialUser* FindUser(const FUniqueNetIdRepl& UserId) const; diff --git a/Engine/Plugins/Online/OnlineFramework/Source/PatchCheck/Private/PatchCheck.cpp b/Engine/Plugins/Online/OnlineFramework/Source/PatchCheck/Private/PatchCheck.cpp index 64eee5e84aff..a5bff4bd718f 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/PatchCheck/Private/PatchCheck.cpp +++ b/Engine/Plugins/Online/OnlineFramework/Source/PatchCheck/Private/PatchCheck.cpp @@ -227,7 +227,7 @@ void FPatchCheck::OnCheckForPatchComplete(const FUniqueNetId& UniqueId, EUserPri } else if (PrivilegeResult & (uint32)IOnlineIdentity::EPrivilegeResults::GenericFailure) { -#if (PLATFORM_XBOXONE || PLATFORM_PS4 || PLATFORM_SWITCH) +#if (PLATFORM_XBOXONE || PLATFORM_PS4 || PLATFORM_SWITCH) // #JANUS-PlatformDefReview: Online // Skip console backend failures Result = EPatchCheckResult::NoPatchRequired; #else diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Qos/Private/QosEvaluator.cpp b/Engine/Plugins/Online/OnlineFramework/Source/Qos/Private/QosEvaluator.cpp index 42fa775685f2..3fe2c7c70628 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Qos/Private/QosEvaluator.cpp +++ b/Engine/Plugins/Online/OnlineFramework/Source/Qos/Private/QosEvaluator.cpp @@ -88,80 +88,6 @@ void UQosEvaluator::FindDatacenters(const FQosParams& InParams, const TArray WeakThisCap(this); - for (FDatacenterQosInstance& Datacenter : Datacenters) - { - if (Datacenter.Definition.IsPingable()) - { - const FString& RegionId = Datacenter.Definition.Id; - const int32 NumServers = Datacenter.Definition.Servers.Num(); - int32 ServerIdx = FMath::RandHelper(NumServers); - // Default to invalid ping tests and set it to something else later - Datacenter.Result = EQosDatacenterResult::Invalid; - if (NumServers > 0) - { - for (int32 PingIdx = 0; PingIdx < NumTestsPerRegion; PingIdx++) - { - const FQosPingServerInfo& Server = Datacenter.Definition.Servers[ServerIdx]; - const FString Address = FString::Printf(TEXT("%s:%d"), *Server.Address, Server.Port); - - auto CompletionDelegate = [WeakThisCap, RegionId, NumTestsPerRegion, InCompletionDelegate](FIcmpEchoResult InResult) - { - if (WeakThisCap.IsValid()) - { - auto StrongThis = WeakThisCap.Get(); - if (!StrongThis->IsPendingKill()) - { - StrongThis->OnPingResultComplete(RegionId, NumTestsPerRegion, InResult); - if (StrongThis->AreAllRegionsComplete()) - { - UE_LOG(LogQos, Verbose, TEXT("Qos complete in %0.2f s"), FPlatformTime::Seconds() - StrongThis->StartTimestamp); - EQosCompletionResult TotalResult = EQosCompletionResult::Success; - StrongThis->CalculatePingAverages(); - StrongThis->EndAnalytics(TotalResult); - InCompletionDelegate.ExecuteIfBound(TotalResult, StrongThis->Datacenters); - } - else if (StrongThis->bCancelOperation) - { - UE_LOG(LogQos, Verbose, TEXT("Qos cancelled after %0.2f s"), FPlatformTime::Seconds() - StrongThis->StartTimestamp); - EQosCompletionResult TotalResult = EQosCompletionResult::Canceled; - StrongThis->EndAnalytics(TotalResult); - InCompletionDelegate.ExecuteIfBound(TotalResult, StrongThis->Datacenters); - } - } - } - }; - - UE_LOG(LogQos, VeryVerbose, TEXT("Pinging %s %s"), *Datacenter.Definition.ToDebugString(), *Address); - FUDPPing::UDPEcho(Address, InParams.Timeout, CompletionDelegate); - ServerIdx = (ServerIdx + 1) % NumServers; - bDidNothing = false; - } - } - else - { - UE_LOG(LogQos, Verbose, TEXT("Nothing to ping %s"), *Datacenter.Definition.ToDebugString()); - } - } - else - { - UE_LOG(LogQos, Verbose, TEXT("Datacenter disabled %s"), *Datacenter.Definition.ToDebugString()); - } - } - - if (bDidNothing) - { - InCompletionDelegate.ExecuteIfBound(EQosCompletionResult::Failure, Datacenters); - } -} - void UQosEvaluator::CalculatePingAverages(int32 TimeToDiscount) { for (FDatacenterQosInstance& Datacenter : Datacenters) @@ -211,32 +137,97 @@ bool UQosEvaluator::AreAllRegionsComplete() return true; } -void UQosEvaluator::OnPingResultComplete(const FString& RegionId, int32 NumTests, const FIcmpEchoResult& Result) +void UQosEvaluator::OnEchoManyCompleted(FIcmpEchoManyCompleteResult FinalResult, int32 NumTestsPerRegion, const FOnQosSearchComplete& InQosSearchCompleteDelegate) { - for (FDatacenterQosInstance& Region : Datacenters) - { - if (Region.Definition.Id == RegionId) - { - UE_LOG(LogQos, VeryVerbose, TEXT("Ping Complete %s %s: %d"), *Region.Definition.ToDebugString(), *Result.ResolvedAddress, (int32)(Result.Time * 1000.0f)); + UE_LOG(LogQos, Log, TEXT("UQosEvaluator OnEchoManyCompleted; result status=%d"), FinalResult.Status); + // Copy our aggregated ping results to the appropriate datacenter result sets + + for (const FIcmpEchoManyResult& EchoManyResult : FinalResult.AllResults) + { + const FIcmpEchoResult& Result = EchoManyResult.EchoResult; + const FIcmpTarget& Target = EchoManyResult.Target; + + // Find datacenter that matches the original request's target address for this result. + FDatacenterQosInstance* const FoundDatacenter = FindDatacenterByAddress( + Datacenters, Target.Address, Target.Port); + + if (FoundDatacenter) + { + FDatacenterQosInstance& Region = *FoundDatacenter; + + UE_LOG(LogQos, VeryVerbose, TEXT("Ping Complete %s %s: %d"), + *Region.Definition.ToDebugString(), *Result.ResolvedAddress, static_cast(Result.Time * 1000.0f)); + + int32 PingInMs = UNREACHABLE_PING; const bool bSuccess = (Result.Status == EIcmpResponseStatus::Success); - int32 PingInMs = bSuccess ? (int32)(Result.Time * 1000.0f) : UNREACHABLE_PING; + if (bSuccess) + { + PingInMs = static_cast(Result.Time * 1000.0f); + ++Region.NumResponses; + } Region.PingResults.Add(PingInMs); - Region.NumResponses += bSuccess ? 1 : 0; if (QosStats.IsValid()) { - QosStats->RecordQosAttempt(RegionId, Result.ResolvedAddress, PingInMs, bSuccess); + const FString& DatacenterId = Region.Definition.Id; + + UE_LOG(LogQos, VeryVerbose, TEXT("Record Qos attempt; ping=%d ms"), PingInMs); + QosStats->RecordQosAttempt(DatacenterId, Result.ResolvedAddress, PingInMs, bSuccess); } - if (Region.PingResults.Num() == NumTests) + if (Region.PingResults.Num() == NumTestsPerRegion) { Region.LastCheckTimestamp = FDateTime::UtcNow(); - Region.Result = (Region.NumResponses == NumTests) ? EQosDatacenterResult::Success : EQosDatacenterResult::Incomplete; + Region.Result = (Region.NumResponses == NumTestsPerRegion) ? EQosDatacenterResult::Success : EQosDatacenterResult::Incomplete; } - break; + + UE_LOG(LogQos, VeryVerbose, TEXT("Got %d/%d ping results (%d reachable) for datacenter (%s, %s); status=%d"), + Region.PingResults.Num(), NumTestsPerRegion, Region.NumResponses, *Region.Definition.Id, *Region.Definition.RegionId, Region.Result); } } + + EQosCompletionResult CompletionResult = EQosCompletionResult::Invalid; + + switch (FinalResult.Status) + { + case EIcmpEchoManyStatus::Success: + { + UE_LOG(LogQos, Verbose, TEXT("Qos complete in %0.2f s (all regions: %s)"), + FPlatformTime::Seconds() - StartTimestamp, AreAllRegionsComplete() ? TEXT("yes") : TEXT("no")); + + CompletionResult = EQosCompletionResult::Success; + CalculatePingAverages(); + EndAnalytics(CompletionResult); + InQosSearchCompleteDelegate.ExecuteIfBound(CompletionResult, Datacenters); + } + break; + + case EIcmpEchoManyStatus::Failure: + { + UE_LOG(LogQos, Verbose, TEXT("Qos failed after in %0.2f s"), FPlatformTime::Seconds() - StartTimestamp); + + CompletionResult = EQosCompletionResult::Failure; + EndAnalytics(CompletionResult); + InQosSearchCompleteDelegate.ExecuteIfBound(CompletionResult, Datacenters); + } + break; + + case EIcmpEchoManyStatus::Canceled: + { + UE_LOG(LogQos, Verbose, TEXT("Qos canceled after %0.2f s"), FPlatformTime::Seconds() - StartTimestamp); + + CompletionResult = EQosCompletionResult::Canceled; + EndAnalytics(CompletionResult); + InQosSearchCompleteDelegate.ExecuteIfBound(CompletionResult, Datacenters); + } + break; + + default: + break; + }; + + UE_LOG(LogQos, Verbose, TEXT("UQosEvaluator OnEchoManyCompleted; Qos result=%d"), CompletionResult); } void UQosEvaluator::StartAnalytics() @@ -281,3 +272,99 @@ FTimerManager& UQosEvaluator::GetWorldTimerManager() const return GetWorld()->GetTimerManager(); } +void UQosEvaluator::ResetDatacenterPingResults() +{ + for (FDatacenterQosInstance& Datacenter : Datacenters) + { + if (Datacenter.Definition.IsPingable()) + { + Datacenter.Result = EQosDatacenterResult::Invalid; + } + } +} + +TArray& UQosEvaluator::PopulatePingRequestList(TArray& OutTargets, + const TArray& Datacenters, int32 NumTestsPerRegion) +{ + for (const FDatacenterQosInstance& Datacenter : Datacenters) + { + if (Datacenter.Definition.IsPingable()) + { + PopulatePingRequestList(OutTargets, Datacenter.Definition, NumTestsPerRegion); + } + else + { + UE_LOG(LogQos, Verbose, TEXT("Datacenter disabled %s"), *Datacenter.Definition.ToDebugString()); + } + } + + return OutTargets; +} + +TArray& UQosEvaluator::PopulatePingRequestList(TArray& OutTargets, + const FQosDatacenterInfo& DatacenterDefinition, int32 NumTestsPerRegion) +{ + const TArray& Servers = DatacenterDefinition.Servers; + const int NumServers = Servers.Num(); + + const int ServerStartIdx = FMath::RandHelper(NumServers); + const int NumPings = NumTestsPerRegion; + + for (int PingIdx = 0; PingIdx < NumPings; ++PingIdx) + { + const int ServerIdx = (ServerStartIdx + PingIdx) % NumServers; + const FQosPingServerInfo& Server = Servers[ServerIdx]; + + FIcmpTarget Target(Server.Address, Server.Port); + OutTargets.Add(Target); + } + + return OutTargets; +} + +FDatacenterQosInstance *const UQosEvaluator::FindDatacenterByAddress(TArray& Datacenters, + const FString& ServerAddress, int32 ServerPort) +{ + // Ugly O(n^2) search to find matching datacenter. Maybe reverse-lookup table/set would be better here. + for (FDatacenterQosInstance& Datacenter : Datacenters) + { + for (const FQosPingServerInfo& Server : Datacenter.Definition.Servers) + { + if ((Server.Address == ServerAddress) && (Server.Port == ServerPort)) + { + return &Datacenter; + } + } + } + + return nullptr; +} + +bool UQosEvaluator::PingRegionServers(const FQosParams& InParams, const FOnQosSearchComplete& InQosSearchCompleteDelegate) +{ + const int32 NumTestsPerRegion = InParams.NumTestsPerRegion; + + TArray Targets; + PopulatePingRequestList(Targets, Datacenters, NumTestsPerRegion); + + if (0 == Targets.Num()) + { + // Nothing to do if no servers provided in the list of datacenters. + InQosSearchCompleteDelegate.ExecuteIfBound(EQosCompletionResult::Failure, Datacenters); + return false; + } + + // Clear the ping results (i.e. set results as invalid) for all datacenters that can be pinged. + ResetDatacenterPingResults(); + + auto PingCompletionCallback = FIcmpEchoManyCompleteDelegate::CreateWeakLambda(this, + [this, NumTestsPerRegion, InQosSearchCompleteDelegate](FIcmpEchoManyCompleteResult FinalResult) + { + // Process total data set from async task + OnEchoManyCompleted(FinalResult, NumTestsPerRegion, InQosSearchCompleteDelegate); + }); + + FUDPPing::UDPEchoMany(Targets, InParams.Timeout, PingCompletionCallback); + + return true; +} diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Qos/Private/QosEvaluator.h b/Engine/Plugins/Online/OnlineFramework/Source/Qos/Private/QosEvaluator.h index 5fb64eca106a..86d793e27463 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Qos/Private/QosEvaluator.h +++ b/Engine/Plugins/Online/OnlineFramework/Source/Qos/Private/QosEvaluator.h @@ -13,6 +13,8 @@ class FQosDatacenterStats; class FTimerManager; class IAnalyticsProvider; struct FIcmpEchoResult; +struct FIcmpTarget; +struct FIcmpEchoManyCompleteResult; enum class EQosResponseType : uint8; /** @@ -41,6 +43,7 @@ DECLARE_DELEGATE_OneParam(FOnQosPingEvalComplete, EQosCompletionResult /** Resul */ DECLARE_DELEGATE_TwoParams(FOnQosSearchComplete, EQosCompletionResult /** Result */, const TArray& /** DatacenterInstances */); + /** * Evaluates QoS metrics to determine the best datacenter under current conditions * Additionally capable of generically pinging an array of servers that have a QosBeaconHost active @@ -81,15 +84,32 @@ public: void SetWorld(UWorld* InWorld); + bool IsCanceled() const { return bCancelOperation; } + protected: /** * Use the udp ping code to ping known servers * * @param InParams parameters defining the request - * @param InCompletionDelegate delegate to fire when all regions have completed their tests + * @param InQosSearchCompleteDelegate delegate to fire when all regions have completed their tests */ - void PingRegionServers(const FQosParams& InParams, const FOnQosSearchComplete& InCompletionDelegate); + bool PingRegionServers(const FQosParams& InParams, const FOnQosSearchComplete& InQosSearchCompleteDelegate); + +private: + + void ResetDatacenterPingResults(); + + static TArray& PopulatePingRequestList(TArray& OutTargets, + const TArray& Datacenters, int32 NumTestsPerRegion); + + static TArray& PopulatePingRequestList(TArray& OutTargets, + const FQosDatacenterInfo& DatacenterDefinition, int32 NumTestsPerRegion); + + static FDatacenterQosInstance *const FindDatacenterByAddress(TArray& Datacenters, + const FString& ServerAddress, int32 ServerPort); + + void OnEchoManyCompleted(FIcmpEchoManyCompleteResult FinalResult, int32 NumTestsPerRegion, const FOnQosSearchComplete& InQosSearchCompleteDelegate); private: @@ -116,8 +136,6 @@ private: */ bool AreAllRegionsComplete(); - void OnPingResultComplete(const FString& RegionId, int32 NumTests, const FIcmpEchoResult& Result); - /** * Take all found ping results and process them before consumption at higher levels * diff --git a/Engine/Plugins/Online/OnlineSubsystem/Source/Private/OnlineSessionInterface.cpp b/Engine/Plugins/Online/OnlineSubsystem/Source/Private/OnlineSessionInterface.cpp new file mode 100644 index 000000000000..677c489ddf29 --- /dev/null +++ b/Engine/Plugins/Online/OnlineSubsystem/Source/Private/OnlineSessionInterface.cpp @@ -0,0 +1,35 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Interfaces/OnlineSessionInterface.h" +#include "OnlineError.h" + +// Workaround for not being able to clear a delegate handle for a lambda while the lambda is being executed +void OnOnlineSessionStartMatchmakingBroadcast(FName DelegateSessionName, bool bWasSuccessful, IOnlineSession* OnlineSession, FName RequestedSessionName, FDelegateHandle* DelegateHandle, const FOnStartMatchmakingComplete CompletionDelegate) +{ + if (DelegateSessionName == RequestedSessionName) + { + OnlineSession->ClearOnMatchmakingCompleteDelegate_Handle(*DelegateHandle); + delete DelegateHandle; + + FOnlineError OnlineError(bWasSuccessful); + CompletionDelegate.ExecuteIfBound(RequestedSessionName, OnlineError, FSessionMatchmakingResults()); + } +} + +bool IOnlineSession::StartMatchmaking(const TArray& LocalPlayers, FName SessionName, const FOnlineSessionSettings& NewSessionSettings, TSharedRef& SearchSettings, const FOnStartMatchmakingComplete& CompletionDelegate) +{ + // TODO: Deprecate other StartMatchmaking function in favor of this when this is implemented on all platforms + TArray> LocalPlayerIds; + LocalPlayerIds.Reserve(LocalPlayers.Num()); + for (const FSessionMatchmakingUser& LocalPlayer : LocalPlayers) + { + LocalPlayerIds.Emplace(LocalPlayer.UserId); + } + bool bStartMatchmakingSuccess = StartMatchmaking(LocalPlayerIds, SessionName, NewSessionSettings, SearchSettings); + if (bStartMatchmakingSuccess) + { + FDelegateHandle* DelegateHandle = new FDelegateHandle; + *DelegateHandle = AddOnMatchmakingCompleteDelegate_Handle(FOnMatchmakingCompleteDelegate::CreateStatic(OnOnlineSessionStartMatchmakingBroadcast, this, SessionName, DelegateHandle, CompletionDelegate)); + } + return bStartMatchmakingSuccess; +} diff --git a/Engine/Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlinePresenceInterface.h b/Engine/Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlinePresenceInterface.h index 62f8ee7959a3..01630b2b051a 100644 --- a/Engine/Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlinePresenceInterface.h +++ b/Engine/Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlinePresenceInterface.h @@ -289,9 +289,18 @@ public: * @param Users The list of unique ids of the users to query for presence information. * @param Delegate The delegate to be executed when the potentially asynchronous query operation completes. */ - //@todo samz - interface should be QueryPresence(const FUniqueNetId& User, const TArray >& UserIds, const FOnPresenceTaskCompleteDelegate& Delegate) virtual void QueryPresence(const FUniqueNetId& User, const FOnPresenceTaskCompleteDelegate& Delegate = FOnPresenceTaskCompleteDelegate()) = 0; + /** + * Starts an async operation that will update the cache with presence data from all users in the Users array. + * On platforms that support multiple keys, this function will query all keys. + * + * @param Users The unique id of the user initiating the query for presence information. + * @param UserIds The list of unique ids of the users to query for presence information. + * @param Delegate The delegate to be executed when the potentially asynchronous query operation completes. + */ + virtual void QueryPresence(const FUniqueNetId& LocalUserId, const TArray>& UserIds, const FOnPresenceTaskCompleteDelegate& Delegate) {}; + /** * Delegate executed when new presence data is available for a user. * diff --git a/Engine/Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlineSessionInterface.h b/Engine/Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlineSessionInterface.h index 0d61b27b33e8..b1f48067f263 100644 --- a/Engine/Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlineSessionInterface.h +++ b/Engine/Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlineSessionInterface.h @@ -6,6 +6,7 @@ #include "UObject/CoreOnline.h" #include "OnlineSubsystemTypes.h" #include "OnlineDelegateMacros.h" +#include "OnlineKeyValuePair.h" class FOnlineSession; class FOnlineSessionSearch; @@ -70,7 +71,7 @@ DECLARE_MULTICAST_DELEGATE_TwoParams(FOnDestroySessionComplete, FName, bool); typedef FOnDestroySessionComplete::FDelegate FOnDestroySessionCompleteDelegate; /** - * Delegate fired when the Matchmaking for an online session has completed + * Broadcast delegate fired when the Matchmaking for an online session has completed * * @param SessionName the name of the session the that can now be joined (on success) * @param bWasSuccessful true if the async action completed without error, false if there was an error @@ -79,9 +80,19 @@ DECLARE_MULTICAST_DELEGATE_TwoParams(FOnMatchmakingComplete, FName, bool); typedef FOnMatchmakingComplete::FDelegate FOnMatchmakingCompleteDelegate; /** - * Delegate fired when the Matchmaking request has been canceled + * Delegate fired when StartMatchmaking completes + * Related to FOnMatchmakingComplete, but this is not a broadcast delegate * * @param SessionName the name of the session that was passed to StartMatchmaking + * @param ErrorDetails extended details of the failure (if failed) + * @param Results results of matchmaking (if succeeded) + */ +DECLARE_DELEGATE_ThreeParams(FOnStartMatchmakingComplete, FName /*SessionName*/, const struct FOnlineError& /*ErrorDetails*/, const struct FSessionMatchmakingResults& /*Results*/); + +/** + * Delegate fired when the Matchmaking request has been canceled + * + * @param SessionName the name of the session that was passed to CancelMatchmaking * @param bWasSuccessful true if the async action completed without error, false if there was an error */ DECLARE_MULTICAST_DELEGATE_TwoParams(FOnCancelMatchmakingComplete, FName, bool); @@ -135,7 +146,7 @@ inline const TCHAR* LexToString(const EOnJoinSessionCompleteResult::Type Value) case EOnJoinSessionCompleteResult::SessionDoesNotExist: return TEXT("SessionDoesNotExist"); case EOnJoinSessionCompleteResult::CouldNotRetrieveAddress: - return TEXT("CouldNotretrieveAddress"); + return TEXT("CouldNotRetrieveAddress"); case EOnJoinSessionCompleteResult::AlreadyInSession: return TEXT("AlreadyInSession"); case EOnJoinSessionCompleteResult::UnknownError: @@ -154,6 +165,31 @@ inline const TCHAR* LexToString(const EOnJoinSessionCompleteResult::Type Value) DECLARE_MULTICAST_DELEGATE_TwoParams(FOnJoinSessionComplete, FName, EOnJoinSessionCompleteResult::Type); typedef FOnJoinSessionComplete::FDelegate FOnJoinSessionCompleteDelegate; +/** + * Delegate fired when a player has joined or left a session + * + * @param SessionName The name of the session that changed + * @param UniqueId The ID of the user whose join state has changed + * @param bJoined If true this is a join event, (if false it is a leave event) + */ +DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnSessionParticipantsChange, FName, const FUniqueNetId&, bool); +typedef FOnSessionParticipantsChange::FDelegate FOnSessionParticipantsChangeDelegate; + +/** + * Delegate fired when session is requesting QOS measurements + * @param The name of the session for which the measurements need to be made + */ +DECLARE_MULTICAST_DELEGATE_OneParam(FOnQosDataRequested, FName); +typedef FOnQosDataRequested::FDelegate FOnQosDataRequestedDelegate; + +/** + * Delegate fired when a sessions custom data has changed + * @param The name of the session that had custom data changed + * @param the updated session data + */ +DECLARE_MULTICAST_DELEGATE_TwoParams(FOnSessionCustomDataChanged, FName, const FOnlineSessionSettings&); +typedef FOnSessionCustomDataChanged::FDelegate FOnSessionCustomDataChangedDelegate; + /** * Delegate fired once a single search result is returned (ie friend invite / join) * Session has not been joined at this point, and requires a call to JoinSession() @@ -270,6 +306,21 @@ inline const TCHAR* LexToString(const ESessionFailure::Type Value) DECLARE_MULTICAST_DELEGATE_TwoParams(FOnSessionFailure, const FUniqueNetId&, ESessionFailure::Type); typedef FOnSessionFailure::FDelegate FOnSessionFailureDelegate; +/** Attributes for a matchmaking user */ +struct FSessionMatchmakingUser +{ + /** Id of the user */ + TSharedRef UserId; + /** Attributes for the user */ + FOnlineKeyValuePairs Attributes; +}; + +/** Matchmaking results */ +struct FSessionMatchmakingResults +{ + // Stub struct that can be easily added to without requiring delegate signature changes +}; + /** * Interface definition for the online services session services * Session services are defined as anything related managing a session @@ -467,6 +518,18 @@ public: * @return true if successful searching for sessions, false otherwise */ virtual bool StartMatchmaking(const TArray< TSharedRef >& LocalPlayers, FName SessionName, const FOnlineSessionSettings& NewSessionSettings, TSharedRef& SearchSettings) = 0; + + /** + * Begins cloud based matchmaking for a session + * + * @param LocalPlayers the ids of all local players that will participate in the match + * @param SessionName the name of the session to use, usually will be GAME_SESSION_NAME + * @param NewSessionSettings the desired settings to match against or create with when forming new sessions + * @param SearchSettings the desired settings that the matched session will have + * + * @return true if successful searching for sessions, false otherwise + */ + virtual bool ONLINESUBSYSTEM_API StartMatchmaking(const TArray& LocalPlayers, FName SessionName, const FOnlineSessionSettings& NewSessionSettings, TSharedRef& SearchSettings, const FOnStartMatchmakingComplete& CompletionDelegate); /** * Delegate fired when the cloud matchmaking has completed @@ -612,13 +675,35 @@ public: /** - * Delegate fired when the joining process for an online session has completed + * Delegate fired when the process for a local user joining an online session has completed * * @param SessionName the name of the session this callback is for * @param Result EOnJoinSessionCompleteResult describing the outcome of the call */ DEFINE_ONLINE_DELEGATE_TWO_PARAM(OnJoinSessionComplete, FName, EOnJoinSessionCompleteResult::Type); + /** + * Delegate fired when a player has joined or left a session + * @param SessionName The name of the session that changed + * @param UniqueId The ID of the user whose join state has changed + * @param bJoined if true this is a join event, (if false it is a leave event) + */ + DEFINE_ONLINE_DELEGATE_THREE_PARAM(OnSessionParticipantsChange, FName, const FUniqueNetId&, bool); + + /** + * Delegate fired when session is requesting QOS measurements + * @param The name of the session for which the measurements need to be made + */ + DEFINE_ONLINE_DELEGATE_ONE_PARAM(OnQosDataRequested, FName); + + /** + * Delegate fired when a sessions custom data has been updated + * + * @param SessionName The session that had custom session data changed + * @param SessionSettings The session settings for the session that changed + */ + DEFINE_ONLINE_DELEGATE_TWO_PARAM(OnSessionCustomDataChanged, FName, const FOnlineSessionSettings&); + /** * Allows the local player to follow a friend into a session * diff --git a/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSessionSettings.h b/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSessionSettings.h index 0b27e2f4a960..0d79619386ba 100644 --- a/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSessionSettings.h +++ b/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSessionSettings.h @@ -307,6 +307,9 @@ public: /** Array of custom session settings */ FSessionSettings Settings; + /** Map of custom settings per session member (Not currently used by every OSS) */ + TUniqueNetIdMap MemberSettings; + public: /** Default constructor, used when serializing a network packet */ FOnlineSessionSettings() @@ -623,6 +626,8 @@ public: #define SEARCH_USER FName(TEXT("SEARCHUSER")) /** Keywords to match in session search */ #define SEARCH_KEYWORDS FName(TEXT("SEARCHKEYWORDS")) +/** The matchmaking queue name to matchmake in, e.g. "TeamDeathmatch" (value is string) */ +#define SEARCH_MATCHMAKING_QUEUE FName(TEXT("MATCHMAKINGQUEUE")) /** If set, use the named Xbox Live hopper to find a session via matchmaking (value is a string) */ #define SEARCH_XBOX_LIVE_HOPPER_NAME FName(TEXT("LIVEHOPPERNAME")) /** Which session template from the service configuration to use */ @@ -630,6 +635,10 @@ public: /** Selection method used to determine which match to join when multiple are returned (valid only on Switch) */ #define SEARCH_SWITCH_SELECTION_METHOD FName(TEXT("SWITCHSELECTIONMETHOD")) +// User attributes for searching (FSessionMatchmakingUser::Attributes) +/** Team a user is searching for */ +#define SEARCH_USER_ATTRIBUTE_TEAM TEXT("TEAM") + /** * Encapsulation of a search for sessions request. * Contains all the search parameters and any search results returned after diff --git a/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystem.h b/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystem.h index 9e7ee2a0ee0a..4ea4abc6a7fb 100644 --- a/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystem.h +++ b/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystem.h @@ -122,14 +122,12 @@ DECLARE_MULTICAST_DELEGATE_TwoParams(FOnOnlineEnvironmentChanged, EOnlineEnviron typedef FOnOnlineEnvironmentChanged::FDelegate FOnOnlineEnvironmentChangedDelegate; /** -* Delegate fired when the "Play Together" event is sent from the PS4 system +* Delegate fired when the "Play Together" event is sent from the PS4 system (Deprecated) * * @param UserIndex - User index of the player the event is for * @param UserIdList - list of other users in the PS4 party to send invites to */ -UE_DEPRECATED(4.26, "PlayTogether will no longer be supported and should be removed.") DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPlayTogetherEventReceived, int32, TArray>); -UE_DEPRECATED(4.26, "PlayTogether will no longer be supported and should be removed.") typedef FOnPlayTogetherEventReceived::FDelegate FOnPlayTogetherEventReceivedDelegate; /** @@ -595,10 +593,13 @@ public: */ DEFINE_ONLINE_DELEGATE_TWO_PARAM(OnOnlineEnvironmentChanged, EOnlineEnvironment::Type /*LastEnvironment*/, EOnlineEnvironment::Type /*Environment*/); - PRAGMA_DISABLE_DEPRECATION_WARNINGS - UE_DEPRECATED(4.26, "PlayTogether will no longer be supported and should be removed." ) - FDelegateHandle AddOnPlayTogetherEventReceivedDelegate_Handle(const FOnPlayTogetherEventReceivedDelegate& Delegate) { return FDelegateHandle(); } - PRAGMA_ENABLE_DEPRECATION_WARNINGS + /** + * Delegate fired when the "Play Together" event is sent from the PS4 system (Deprecated) + * + * @param UserIndex - User index of the player the event is for + * @param UserIdList - list of other users in the PS4 party to send invites to + */ + DEFINE_ONLINE_DELEGATE_TWO_PARAM(OnPlayTogetherEventReceived, int32, TArray>); /** * @return The name of the online service this platform uses diff --git a/Engine/Plugins/Online/OnlineSubsystemAmazon/Source/Private/OnlineSubsystemAmazon.cpp b/Engine/Plugins/Online/OnlineSubsystemAmazon/Source/Private/OnlineSubsystemAmazon.cpp index 0cd4db6cdd75..6b492841e122 100644 --- a/Engine/Plugins/Online/OnlineSubsystemAmazon/Source/Private/OnlineSubsystemAmazon.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemAmazon/Source/Private/OnlineSubsystemAmazon.cpp @@ -5,6 +5,7 @@ #include "Misc/ConfigCacheIni.h" #include "OnlineSubsystemAmazonModule.h" #include "OnlineIdentityAmazon.h" +#include "Stats/Stats.h" // FOnlineSubsystemAmazonModule IMPLEMENT_MODULE(FOnlineSubsystemAmazonModule, OnlineSubsystemAmazon); @@ -72,6 +73,8 @@ IOnlineIdentityPtr FOnlineSubsystemAmazon::GetIdentityInterface() const bool FOnlineSubsystemAmazon::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FOnlineSubsystemAmazon_Tick); + if (!FOnlineSubsystemImpl::Tick(DeltaTime)) { return false; diff --git a/Engine/Plugins/Online/OnlineSubsystemFacebook/Source/Private/OnlineSubsystemFacebookCommon.cpp b/Engine/Plugins/Online/OnlineSubsystemFacebook/Source/Private/OnlineSubsystemFacebookCommon.cpp index 9892329d46a0..a4197da292ba 100644 --- a/Engine/Plugins/Online/OnlineSubsystemFacebook/Source/Private/OnlineSubsystemFacebookCommon.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemFacebook/Source/Private/OnlineSubsystemFacebookCommon.cpp @@ -9,6 +9,7 @@ #include "OnlineExternalUIFacebookCommon.h" #include "Misc/ConfigCacheIni.h" #include "Misc/CommandLine.h" +#include "Stats/Stats.h" /** Fallback to latest tested API version */ #define FACEBOOK_API_VER TEXT("v2.12") @@ -69,6 +70,8 @@ bool FOnlineSubsystemFacebookCommon::Shutdown() bool FOnlineSubsystemFacebookCommon::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FOnlineSubsystemFacebookCommon_Tick); + if (!FOnlineSubsystemImpl::Tick(DeltaTime)) { return false; diff --git a/Engine/Plugins/Online/OnlineSubsystemGoogle/Source/Private/OnlineSubsystemGoogleCommon.cpp b/Engine/Plugins/Online/OnlineSubsystemGoogle/Source/Private/OnlineSubsystemGoogleCommon.cpp index 3e78040134fd..c57e7b344ab7 100644 --- a/Engine/Plugins/Online/OnlineSubsystemGoogle/Source/Private/OnlineSubsystemGoogleCommon.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemGoogle/Source/Private/OnlineSubsystemGoogleCommon.cpp @@ -6,6 +6,7 @@ #include "OnlineExternalUIGoogleCommon.h" #include "Misc/ConfigCacheIni.h" #include "Misc/CommandLine.h" +#include "Stats/Stats.h" #define GOOGLE_CLIENTAUTH_ID TEXT("ClientId") #define GOOGLE_SERVERAUTH_ID TEXT("ServerClientId") @@ -93,6 +94,8 @@ FOnlineSubsystemGoogleCommon::FGoogleConfigurationDelegate& FOnlineSubsystemGoog bool FOnlineSubsystemGoogleCommon::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FOnlineSubsystemGoogleCommon_Tick); + if (!FOnlineSubsystemImpl::Tick(DeltaTime)) { return false; diff --git a/Engine/Plugins/Online/OnlineSubsystemNull/Source/Private/OnlineMessageSanitizerNull.cpp b/Engine/Plugins/Online/OnlineSubsystemNull/Source/Private/OnlineMessageSanitizerNull.cpp index 6185b5e084f5..d86e5eb90b15 100644 --- a/Engine/Plugins/Online/OnlineSubsystemNull/Source/Private/OnlineMessageSanitizerNull.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemNull/Source/Private/OnlineMessageSanitizerNull.cpp @@ -2,6 +2,7 @@ #include "OnlineMessageSanitizerNull.h" #include "Containers/Ticker.h" +#include "Stats/Stats.h" FMessageSanitizerNull::FMessageSanitizerNull() : RequestId(0) @@ -23,6 +24,7 @@ void FMessageSanitizerNull::SanitizeDisplayName(const FString& DisplayName, cons FDelegateHandle TickerHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([this, DisplayName, CompletionDelegate, ThisRequest](float) -> bool { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMessageSanitizerNull_SanitizeDisplayName); FString CleanString; PerformSanitize(DisplayName, CleanString); CompletionDelegate.ExecuteIfBound(true, CleanString); @@ -41,6 +43,7 @@ void FMessageSanitizerNull::SanitizeDisplayNames(const TArray& DisplayN FDelegateHandle TickerHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([this, DisplayNames, CompletionDelegate, ThisRequest](float) -> bool { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMessageSanitizerNull_SanitizeDisplayNames); TArray OutStrings; for (const FString& String : DisplayNames) { diff --git a/Engine/Plugins/Online/OnlineSubsystemNull/Source/Private/OnlineSubsystemNull.cpp b/Engine/Plugins/Online/OnlineSubsystemNull/Source/Private/OnlineSubsystemNull.cpp index db995891e118..25f789440000 100644 --- a/Engine/Plugins/Online/OnlineSubsystemNull/Source/Private/OnlineSubsystemNull.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemNull/Source/Private/OnlineSubsystemNull.cpp @@ -12,6 +12,7 @@ #include "OnlineStoreV2InterfaceNull.h" #include "OnlinePurchaseInterfaceNull.h" #include "OnlineMessageSanitizerNull.h" +#include "Stats/Stats.h" FThreadSafeCounter FOnlineSubsystemNull::TaskCounter; @@ -157,6 +158,8 @@ IMessageSanitizerPtr FOnlineSubsystemNull::GetMessageSanitizer(int32 LocalUserNu bool FOnlineSubsystemNull::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FOnlineSubsystemNull_Tick); + if (!FOnlineSubsystemImpl::Tick(DeltaTime)) { return false; diff --git a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OnlineSubsystemOculus.cpp b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OnlineSubsystemOculus.cpp index 58d213aa511a..a85ab698e76f 100644 --- a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OnlineSubsystemOculus.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OnlineSubsystemOculus.cpp @@ -10,6 +10,7 @@ #include "OnlineSessionInterfaceOculus.h" #include "OnlineUserCloudOculus.h" #include "OnlineVoiceOculus.h" +#include "Stats/Stats.h" #if PLATFORM_ANDROID #include "Android/AndroidApplication.h" @@ -151,6 +152,8 @@ IOnlineTournamentPtr FOnlineSubsystemOculus::GetTournamentInterface() const bool FOnlineSubsystemOculus::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FOnlineSubsystemOculus_Tick); + if (!FOnlineSubsystemImpl::Tick(DeltaTime)) { return false; diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineAuthHandlerSteam.h b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineAuthHandlerSteam.h index 0033746f1468..edea8506e29e 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineAuthHandlerSteam.h +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineAuthHandlerSteam.h @@ -24,9 +24,6 @@ public: virtual void Incoming(FBitReader& Packet) override; virtual void Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits) override; - virtual void IncomingConnectionless(const TSharedPtr& Address, FBitReader& Packet) override {} - virtual void OutgoingConnectionless(const TSharedPtr& Address, FBitWriter& Packet, FOutPacketTraits& Traits) override {} - virtual void Tick(float DeltaTime) override; virtual int32 GetReservedPacketBits() const override; diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemSteam.cpp b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemSteam.cpp index 2424696a619a..0a6d03bda63e 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemSteam.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemSteam.cpp @@ -10,6 +10,7 @@ #include "Serialization/BufferArchive.h" #include "Interfaces/IPluginManager.h" #include "SocketSubsystem.h" +#include "Stats/Stats.h" #include "IPAddress.h" #include "OnlineSubsystemSteamPrivate.h" @@ -385,6 +386,8 @@ void FOnlineSubsystemSteam::QueueAsyncOutgoingItem(FOnlineAsyncItem* AsyncItem) bool FOnlineSubsystemSteam::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FOnlineSubsystemSteam_Tick); + if (!FOnlineSubsystemImpl::Tick(DeltaTime)) { return false; diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpConnection.h b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpConnection.h index b74173ad6b40..95be3e7a53ae 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpConnection.h +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpConnection.h @@ -47,7 +47,7 @@ class ONLINESUBSYSTEMUTILS_API UIpConnection : public UNetConnection virtual void LowLevelSend(void* Data, int32 CountBits, FOutPacketTraits& Traits) override; FString LowLevelGetRemoteAddress(bool bAppendPort=false) override; FString LowLevelDescribe() override; - virtual void Tick() override; + virtual void Tick(float DeltaSeconds) override; virtual void CleanUp() override; virtual void ReceivedRawPacket(void* Data, int32 Count) override; virtual float GetTimeoutValue() override; diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpNetDriver.h b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpNetDriver.h index 36ad9bb7a96a..54c9919d7ca5 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpNetDriver.h +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpNetDriver.h @@ -90,13 +90,13 @@ public: { FReceivedPacketView PacketView; - PacketView.Data = MakeArrayView(Data, CountBytesRef); + PacketView.DataView = {Data, CountBytesRef, ECountUnits::Bytes}; PacketView.Address = Address; PacketView.Error = SE_NO_ERROR; UNetConnection* ReturnVal = ProcessConnectionlessPacket(PacketView, { Data, MAX_PACKET_SIZE } ); - CountBytesRef = PacketView.Data.Num(); + CountBytesRef = PacketView.DataView.NumBytes(); return ReturnVal; } diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/OnlineSessionClient.h b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/OnlineSessionClient.h index cfdebfce078c..490995406284 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/OnlineSessionClient.h +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/OnlineSessionClient.h @@ -35,6 +35,9 @@ protected: FOnJoinSessionCompleteDelegate OnJoinSessionCompleteDelegate; /** Delegate for accepting session invites */ FOnSessionUserInviteAcceptedDelegate OnSessionUserInviteAcceptedDelegate; + /** Delegate for handling the play together system event. Will be removed. */ + UE_DEPRECATED(4.26, "PlayTogether will no longer be supported and should be removed.") + FOnPlayTogetherEventReceivedDelegate OnPlayTogetherEventReceivedDelegate; // Handles to the above delegates FDelegateHandle OnSessionInviteAcceptedDelegateHandle; @@ -42,6 +45,9 @@ protected: FDelegateHandle OnDestroyForJoinSessionCompleteDelegateHandle; FDelegateHandle OnDestroyForMainMenuCompleteDelegateHandle; FDelegateHandle OnJoinSessionCompleteDelegateHandle; + /** Deprecated. Will be removed. */ + UE_DEPRECATED(4.26, "PlayTogether will no longer be supported and should be removed.") + FDelegateHandle OnPlayTogetherEventReceivedDelegateHandle; /** Handle to outstanding start session call */ FDelegateHandle StartSessionCompleteHandle; diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpConnection.cpp b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpConnection.cpp index 195d7d92b413..624209fcb2f2 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpConnection.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpConnection.cpp @@ -157,7 +157,7 @@ void UIpConnection::InitRemoteConnection(UNetDriver* InDriver, class FSocket* In SetExpectedClientLoginMsgType( NMT_Hello ); } -void UIpConnection::Tick() +void UIpConnection::Tick(float DeltaSeconds) { if (CVarNetIpConnectionUseSendTasks.GetValueOnGameThread() != 0) { @@ -245,7 +245,7 @@ void UIpConnection::Tick() Close(); } - Super::Tick(); + Super::Tick(DeltaSeconds); } void UIpConnection::CleanUp() diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpNetDriver.cpp b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpNetDriver.cpp index 46c64944e3c7..a4ef037719b8 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpNetDriver.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpNetDriver.cpp @@ -244,7 +244,7 @@ private: } else { - OutPacket.Data = MakeArrayView(CurrentPacket.Data); + OutPacket.DataView = {CurrentPacket.Data.GetData(), CurrentPacket.Data.Num(), ECountUnits::Bytes}; OutPacket.Error = CurrentPacket.Error; OutPacket.Address = CurrentPacket.Address; bRecvSuccess = CurrentPacket.bRecvSuccess; @@ -1151,6 +1151,7 @@ void UIpNetDriver::TickDispatch(float DeltaTime) for (FPacketIterator It(this); It; ++It) { FReceivedPacketView ReceivedPacket; + FInPacketTraits& ReceivedTraits = ReceivedPacket.Traits; bool bOk = It.GetCurrentPacket(ReceivedPacket); const TSharedRef FromAddr = ReceivedPacket.Address.ToSharedRef(); UNetConnection* Connection = nullptr; @@ -1159,13 +1160,13 @@ void UIpNetDriver::TickDispatch(float DeltaTime) if (bOk) { // Immediately stop processing (continuing to next receive), for empty packets (usually a DDoS) - if (ReceivedPacket.Data.Num() == 0) + if (ReceivedPacket.DataView.NumBits() == 0) { DDoS.IncBadPacketCounter(); continue; } - FPacketAudit::NotifyLowLevelReceive((uint8*)ReceivedPacket.Data.GetData(), ReceivedPacket.Data.Num()); + FPacketAudit::NotifyLowLevelReceive((uint8*)ReceivedPacket.DataView.GetData(), ReceivedPacket.DataView.NumBytes()); } else { @@ -1252,8 +1253,6 @@ void UIpNetDriver::TickDispatch(float DeltaTime) } } - bool bRecentlyDisconnectedClient = false; - if (Connection == nullptr) { UNetConnection** Result = MappedClientConnections.Find(FromAddr); @@ -1268,7 +1267,7 @@ void UIpNetDriver::TickDispatch(float DeltaTime) } else { - bRecentlyDisconnectedClient = true; + ReceivedTraits.bFromRecentlyDisconnected = true; } } check(Connection == nullptr || CastChecked(Connection)->RemoteAddr->CompareEndpoints(*FromAddr)); @@ -1304,7 +1303,7 @@ void UIpNetDriver::TickDispatch(float DeltaTime) } else { - bRecentlyDisconnectedClient ? DDoS.IncDisconnPacketCounter() : DDoS.IncNonConnPacketCounter(); + ReceivedTraits.bFromRecentlyDisconnected ? DDoS.IncDisconnPacketCounter() : DDoS.IncNonConnPacketCounter(); if (LogPortUnreach && !DDoS.CheckLogRestrictions()) { @@ -1330,7 +1329,7 @@ void UIpNetDriver::TickDispatch(float DeltaTime) } - bRecentlyDisconnectedClient ? DDoS.IncDisconnPacketCounter() : DDoS.IncNonConnPacketCounter(); + ReceivedTraits.bFromRecentlyDisconnected ? DDoS.IncDisconnPacketCounter() : DDoS.IncNonConnPacketCounter(); DDoS.CondCheckNonConnQuotasAndLimits(); } @@ -1346,7 +1345,7 @@ void UIpNetDriver::TickDispatch(float DeltaTime) FPacketBufferView WorkingBuffer = It.GetWorkingBuffer(); Connection = ProcessConnectionlessPacket(ReceivedPacket, WorkingBuffer); - bIgnorePacket = ReceivedPacket.Data.Num() == 0; + bIgnorePacket = ReceivedPacket.DataView.NumBytes() == 0; } else { @@ -1368,7 +1367,7 @@ void UIpNetDriver::TickDispatch(float DeltaTime) It.GetCurrentPacketTimestamp(Connection); } - Connection->ReceivedRawPacket((uint8*)ReceivedPacket.Data.GetData(), ReceivedPacket.Data.Num()); + Connection->ReceivedRawPacket((uint8*)ReceivedPacket.DataView.GetData(), ReceivedPacket.DataView.NumBytes()); } } } @@ -1393,7 +1392,7 @@ UNetConnection* UIpNetDriver::ProcessConnectionlessPacket(FReceivedPacketView& P { UNetConnection* ReturnVal = nullptr; TSharedPtr StatelessConnect; - const TSharedPtr& Address = PacketRef.Address; + const TSharedPtr& Address = PacketRef.Address; FString IncomingAddress = Address->ToString(true); bool bPassedChallenge = false; bool bRestartedHandshake = false; @@ -1403,10 +1402,9 @@ UNetConnection* UIpNetDriver::ProcessConnectionlessPacket(FReceivedPacketView& P { StatelessConnect = StatelessConnectComponent.Pin(); - const ProcessedPacket HandlerResult = ConnectionlessHandler->IncomingConnectionless(Address, - (uint8*)PacketRef.Data.GetData(), PacketRef.Data.Num()); + EIncomingResult Result = ConnectionlessHandler->IncomingConnectionless(PacketRef); - if (!HandlerResult.bError) + if (Result == EIncomingResult::Success) { bPassedChallenge = StatelessConnect->HasPassedChallenge(Address, bRestartedHandshake); @@ -1475,16 +1473,22 @@ UNetConnection* UIpNetDriver::ProcessConnectionlessPacket(FReceivedPacketView& P } - int32 NewCountBytes = FMath::DivideAndRoundUp(HandlerResult.CountBits, 8); + int32 NewCountBytes = PacketRef.DataView.NumBytes(); + uint8* WorkingData = WorkingBuffer.Buffer.GetData(); if (NewCountBytes > 0) { - FMemory::Memcpy(WorkingBuffer.Buffer.GetData(), HandlerResult.Data, NewCountBytes); + const uint8* NewData = PacketRef.DataView.GetData(); + + if (NewData != WorkingData) + { + FMemory::Memcpy(WorkingData, NewData, NewCountBytes); + } bIgnorePacket = false; } - PacketRef.Data = MakeArrayView(WorkingBuffer.Buffer.GetData(), NewCountBytes); + PacketRef.DataView = {WorkingData, NewCountBytes, ECountUnits::Bytes}; } } } @@ -1551,7 +1555,7 @@ UNetConnection* UIpNetDriver::ProcessConnectionlessPacket(FReceivedPacketView& P if (bIgnorePacket) { - PacketRef.Data = MakeArrayView(PacketRef.Data.GetData(), 0); + PacketRef.DataView = {PacketRef.DataView.GetData(), 0, ECountUnits::Bits}; } return ReturnVal; @@ -1721,20 +1725,22 @@ void UIpNetDriver::TestSuddenPortChange(uint32 NumConnections) // Reset the connection's port to pretend that we used to be sending traffic on an old connection. This is // done because once the test is complete, we need to be back onto the port we started with. This // fakes what happens in live with clients randomly sending traffic on a new port. - UIpConnection* const TestConnection = (UIpConnection*)ClientConnections[i]; - TSharedRef RemoteAddrRef = TestConnection->RemoteAddr.ToSharedRef(); + if (UIpConnection* const TestConnection = Cast(ClientConnections[i])) + { + TSharedRef RemoteAddrRef = TestConnection->RemoteAddr.ToSharedRef(); - MappedClientConnections.Remove(RemoteAddrRef); + MappedClientConnections.Remove(RemoteAddrRef); - TestConnection->RemoteAddr->SetPort(i + 9876); + TestConnection->RemoteAddr->SetPort(i + 9876); - MappedClientConnections.Add(RemoteAddrRef, TestConnection); + MappedClientConnections.Add(RemoteAddrRef, TestConnection); - // We need to set AllowPlayerPortUnreach to true because the net driver will try sending traffic - // to the IP/Port we just set which is invalid. On Windows, this causes an error to be returned in - // RecvFrom (WSAECONNRESET). When AllowPlayerPortUnreach is true, these errors are ignored. - AllowPlayerPortUnreach = true; - UE_LOG(LogNet, Log, TEXT("TestSuddenPortChange - Changed this connection: %s."), *TestConnection->Describe()); + // We need to set AllowPlayerPortUnreach to true because the net driver will try sending traffic + // to the IP/Port we just set which is invalid. On Windows, this causes an error to be returned in + // RecvFrom (WSAECONNRESET). When AllowPlayerPortUnreach is true, these errors are ignored. + AllowPlayerPortUnreach = true; + UE_LOG(LogNet, Log, TEXT("TestSuddenPortChange - Changed this connection: %s."), *TestConnection->Describe()); + } } } } @@ -1756,7 +1762,7 @@ bool UIpNetDriver::Exec( UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar ) UIpConnection* UIpNetDriver::GetServerConnection() { - return (UIpConnection*)ServerConnection; + return Cast(ServerConnection); } UIpNetDriver::FReceiveThreadRunnable::FReceiveThreadRunnable(UIpNetDriver* InOwningNetDriver) diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/OnlineSessionClient.cpp b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/OnlineSessionClient.cpp index d47ec5a5342d..87ec34f90b92 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/OnlineSessionClient.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/OnlineSessionClient.cpp @@ -46,6 +46,14 @@ void UOnlineSessionClient::RegisterOnlineDelegates() OnDestroyForJoinSessionCompleteDelegate = FOnDestroySessionCompleteDelegate::CreateUObject(this, &ThisClass::OnDestroyForJoinSessionComplete); OnDestroyForMainMenuCompleteDelegate = FOnDestroySessionCompleteDelegate::CreateUObject(this, &ThisClass::OnDestroyForMainMenuComplete); OnSessionUserInviteAcceptedDelegate = FOnSessionUserInviteAcceptedDelegate::CreateUObject(this, &ThisClass::OnSessionUserInviteAccepted); + PRAGMA_DISABLE_DEPRECATION_WARNINGS + OnPlayTogetherEventReceivedDelegate = FOnPlayTogetherEventReceivedDelegate::CreateUObject(this, &ThisClass::OnPlayTogetherEventReceived); + + if (IOnlineSubsystem* const OnlineSubsystem = IOnlineSubsystem::Get()) + { + OnPlayTogetherEventReceivedDelegateHandle = OnlineSubsystem->AddOnPlayTogetherEventReceivedDelegate_Handle(OnPlayTogetherEventReceivedDelegate); + } + PRAGMA_ENABLE_DEPRECATION_WARNINGS IOnlineSessionPtr SessionInt = GetSessionInt(); if (SessionInt.IsValid()) @@ -61,6 +69,13 @@ void UOnlineSessionClient::ClearOnlineDelegates() { SessionInt->ClearOnSessionUserInviteAcceptedDelegate_Handle(OnSessionUserInviteAcceptedDelegateHandle); } + + if (IOnlineSubsystem* const OnlineSubsystem = IOnlineSubsystem::Get()) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + OnlineSubsystem->ClearOnPlayTogetherEventReceivedDelegate_Handle(OnPlayTogetherEventReceivedDelegateHandle); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } } void UOnlineSessionClient::OnSessionUserInviteAccepted(bool bWasSuccessful, int32 ControllerId, TSharedPtr UserId, const FOnlineSessionSearchResult& SearchResult) diff --git a/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Private/VivoxVoiceChat.cpp b/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Private/VivoxVoiceChat.cpp index 8fe4021f8216..4b980c5cd1df 100644 --- a/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Private/VivoxVoiceChat.cpp +++ b/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Private/VivoxVoiceChat.cpp @@ -440,7 +440,7 @@ void FVivoxVoiceChatUser::Login(FPlatformUserId PlatformId, const FString& Playe LoginSession.PlayerName = PlayerName; LoginSession.AccountName = AccountName; LoginSession.UserUri = UserUri; - LoginSession.State = FLoginSession::EState::LoggedOut; + LoginSession.State = FLoginSession::EState::LoggingIn; OnVoiceChatLoginCompleteDelegate = Delegate; @@ -455,8 +455,6 @@ void FVivoxVoiceChatUser::Login(FPlatformUserId PlatformId, const FString& Playe TriggerCompletionDelegate(OnVoiceChatLoginCompleteDelegate, PlayerName, Result); return; } - - LoginSession.State = FLoginSession::EState::LoggingIn; } void FVivoxVoiceChatUser::Logout(const FOnVoiceChatLogoutCompleteDelegate& Delegate) @@ -1921,6 +1919,19 @@ bool FVivoxVoiceChat::Initialize() return IsInitialized(); } +void FVivoxVoiceChat::Initialize(const FOnVoiceChatInitializeCompleteDelegate& Delegate) +{ + const bool bResult = Initialize(); + const FVoiceChatResult Result = bResult ? FVoiceChatResult::CreateSuccess() : FVoiceChatResult(EVoiceChatResult::ImplementationError); + if (Delegate.IsBound()) + { + AsyncTask(ENamedThreads::GameThread, [Delegate, Result]() + { + Delegate.Execute(Result); + }); + } +} + bool FVivoxVoiceChat::Uninitialize() { if (IsInitialized()) @@ -1935,6 +1946,19 @@ bool FVivoxVoiceChat::Uninitialize() return true; } +void FVivoxVoiceChat::Uninitialize(const FOnVoiceChatUninitializeCompleteDelegate& Delegate) +{ + const bool bResult = Uninitialize(); + const FVoiceChatResult Result = bResult ? FVoiceChatResult::CreateSuccess() : FVoiceChatResult(EVoiceChatResult::ImplementationError); + if (Delegate.IsBound()) + { + AsyncTask(ENamedThreads::GameThread, [Delegate, Result]() + { + Delegate.Execute(Result); + }); + } +} + bool FVivoxVoiceChat::IsInitialized() const { return bInitialized; @@ -3157,6 +3181,22 @@ void FVivoxVoiceChat::RegisterVoiceChatUser(FVivoxVoiceChatUser* User) void FVivoxVoiceChat::UnregisterVoiceChatUser(FVivoxVoiceChatUser* User) { - FScopeLock Lock(&VoiceChatUsersCriticalSection); - VoiceChatUsers.Remove(User); + if (User) + { + if (IsInitialized() + && IsConnected() + && User->IsLoggedIn()) + { + User->Logout(FOnVoiceChatLogoutCompleteDelegate::CreateLambda([](const FString& PlayerName, const FVoiceChatResult& Result) + { + if (!Result.IsSuccess()) + { + UE_LOG(LogVivoxVoiceChat, Warning, TEXT("UnregisterVoiceChatUser PlayerName=[%s] Logout %s"), *PlayerName, *LexToString(Result)) + } + })); + } + + FScopeLock Lock(&VoiceChatUsersCriticalSection); + VoiceChatUsers.Remove(User); + } } diff --git a/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Public/VivoxVoiceChat.h b/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Public/VivoxVoiceChat.h index 9008728f5e67..401c3cef7530 100644 --- a/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Public/VivoxVoiceChat.h +++ b/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Public/VivoxVoiceChat.h @@ -273,7 +273,9 @@ public: // IVoiceChat Interface virtual bool Initialize() override; + virtual void Initialize(const FOnVoiceChatInitializeCompleteDelegate& Delegate) override; virtual bool Uninitialize() override; + virtual void Uninitialize(const FOnVoiceChatUninitializeCompleteDelegate& Delegate) override; virtual bool IsInitialized() const override; virtual void Connect(const FOnVoiceChatConnectCompleteDelegate& Delegate) override; virtual void Disconnect(const FOnVoiceChatDisconnectCompleteDelegate& Delegate) override; diff --git a/Engine/Plugins/Online/VoiceChat/VoiceChat/Source/Public/VoiceChat.h b/Engine/Plugins/Online/VoiceChat/VoiceChat/Source/Public/VoiceChat.h index 36d81c01ea34..aff5366fbc3c 100644 --- a/Engine/Plugins/Online/VoiceChat/VoiceChat/Source/Public/VoiceChat.h +++ b/Engine/Plugins/Online/VoiceChat/VoiceChat/Source/Public/VoiceChat.h @@ -84,6 +84,8 @@ inline FString LexToString(const FVoiceChatDeviceInfo& DeviceInfo) return FString::Printf(TEXT("DisplayName=[%s] Id=[%s]"), *DeviceInfo.DisplayName, *DeviceInfo.Id); } +DECLARE_DELEGATE_OneParam(FOnVoiceChatInitializeCompleteDelegate, const FVoiceChatResult& /* Result */); +DECLARE_DELEGATE_OneParam(FOnVoiceChatUninitializeCompleteDelegate, const FVoiceChatResult& /* Result */); DECLARE_DELEGATE_OneParam(FOnVoiceChatConnectCompleteDelegate, const FVoiceChatResult& /* Result */); DECLARE_DELEGATE_OneParam(FOnVoiceChatDisconnectCompleteDelegate, const FVoiceChatResult& /* Result */); DECLARE_DELEGATE_TwoParams(FOnVoiceChatLoginCompleteDelegate, const FString& /* PlayerName */, const FVoiceChatResult& /* Result */); @@ -627,15 +629,25 @@ public: } /** - * Initialize VoiceChat + * Initialize VoiceChat synchronously */ virtual bool Initialize() = 0; /** - * Uninitialize VoiceChat + * Initialize VoiceChat asynchronously + */ + virtual void Initialize(const FOnVoiceChatInitializeCompleteDelegate& Delegate) = 0; + + /** + * Uninitialize VoiceChat synchronously */ virtual bool Uninitialize() = 0; + /** + * Uninitialize VoiceChat asynchronously + */ + virtual void Uninitialize(const FOnVoiceChatUninitializeCompleteDelegate& Delegate) = 0; + /** * Is voice chat initialized? * diff --git a/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Public/SkeletalMeshComponentBudgeted.h b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Public/SkeletalMeshComponentBudgeted.h index 28a89fe1b1cd..64c0b48ed1d3 100644 --- a/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Public/SkeletalMeshComponentBudgeted.h +++ b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Public/SkeletalMeshComponentBudgeted.h @@ -50,11 +50,15 @@ public: bool GetShouldUseActorRenderedFlag() const { return bShouldUseActorRenderedFlag; }; void SetShouldUseActorRenderedFlag(bool value) { bShouldUseActorRenderedFlag = value; }; -private: + +protected: + // UActorComponent interface virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; +private: + // USkeletalMeshComponent interface virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; virtual void CompleteParallelAnimationEvaluation(bool bDoPostAnimEvaluation) override; diff --git a/Engine/Plugins/Runtime/AudioModulation/AudioModulation.uplugin b/Engine/Plugins/Runtime/AudioModulation/AudioModulation.uplugin index 4ae2ffc3fb7b..578a397cfd87 100644 --- a/Engine/Plugins/Runtime/AudioModulation/AudioModulation.uplugin +++ b/Engine/Plugins/Runtime/AudioModulation/AudioModulation.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, "Version": 1, - "VersionName": "1.1", + "VersionName": "2.0", "FriendlyName": "Audio Modulation", "Description": "Enables modulation of control parameters in the Unreal Audio Engine.", "Category": "Audio", @@ -13,9 +13,9 @@ "EnabledByDefault": false, "CanContainContent": true, "IsBetaVersion": true, + "IsExperimentalVersion": false, "Installed": false, - "Modules": - [ + "Modules": [ { "Name": "AudioModulation", "Type": "Runtime", diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulation.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulation.cpp index 5d1822895ccb..f5ed42fdf249 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulation.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulation.cpp @@ -37,6 +37,11 @@ namespace AudioModulation ModSystem->Initialize(InitializationParams); } + void FAudioModulation::OnAuditionEnd() + { + ModSystem->OnAuditionEnd(); + } + #if !UE_BUILD_SHIPPING bool FAudioModulation::OnPostHelp(FCommonViewportClient* ViewportClient, const TCHAR* Stream) { diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationDebugger.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationDebugger.cpp index c89304c6614e..ca6f9dddf04e 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationDebugger.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationDebugger.cpp @@ -35,9 +35,9 @@ namespace AudioModulation const int32 MaxNameLength = 40; const int32 XIndent = 36; - FColor GetUnitRenderColor(const float Value, const FVector2D& Range) + FColor GetUnitRenderColor(const float Value) { - return Value > Range.Y || Range.X < Value + return Value > 1.0f || Value < 0.0f ? FColor::Red : FColor::Green; } @@ -213,10 +213,10 @@ namespace AudioModulation float Target = Bus.DefaultValue; float Value = Bus.DefaultValue; - if (const FControlBusMixChannelDebugInfo* Channel = BusMix.Channels.Find(Bus.Id)) + if (const FControlBusMixStageDebugInfo* Stage = BusMix.Stages.Find(Bus.Id)) { - Target = Channel->TargetValue; - Value = Channel->CurrentValue; + Target = Stage->TargetValue; + Value = Stage->CurrentValue; } if (Target != Value) @@ -246,7 +246,7 @@ namespace AudioModulation } else { - const FColor Color = Debug::GetUnitRenderColor(Value, Bus.Range); + const FColor Color = Debug::GetUnitRenderColor(Value); Canvas.DrawShadowedString(RowX, Y, *FString::Printf(TEXT("%.4f"), Value), &Font, Color); } } @@ -258,7 +258,7 @@ namespace AudioModulation { RowX += Width * CellWidth; // Add before to leave space for row headers const float Value = Bus.LFOValue; - const FColor Color = Debug::GetUnitRenderColor(Value, Bus.Range); + const FColor Color = Debug::GetUnitRenderColor(Value); Canvas.DrawShadowedString(RowX, Y, *FString::Printf(TEXT("%.4f"), Value), &Font, Color); } @@ -268,7 +268,7 @@ namespace AudioModulation for (const FControlBusDebugInfo& Bus : FilteredBuses) { RowX += Width * CellWidth; // Add before to leave space for row headers - const FColor Color = Debug::GetUnitRenderColor(Bus.Value, Bus.Range); + const FColor Color = Debug::GetUnitRenderColor(Bus.Value); Canvas.DrawShadowedString(RowX, Y, *FString::Printf(TEXT("%.4f"), Bus.Value), &Font, Color); } Y += Height; @@ -338,7 +338,6 @@ namespace AudioModulation DebugInfo.LFOValue = Proxy->GetLFOValue(); DebugInfo.MixValue = Proxy->GetMixValue(); DebugInfo.Name = Proxy->GetName(); - DebugInfo.Range = Proxy->GetRange(); DebugInfo.RefCount = Proxy->GetRefCount(); DebugInfo.Value = Proxy->GetValue(); RefreshedFilteredBuses.Add(DebugInfo); @@ -353,12 +352,12 @@ namespace AudioModulation FControlBusMixDebugInfo DebugInfo; DebugInfo.Name = Proxy->GetName(); DebugInfo.RefCount = Proxy->GetRefCount(); - for (const TPair& Channel : Proxy->Channels) + for (const TPair& Stage : Proxy->Stages) { - FControlBusMixChannelDebugInfo ChannelDebugInfo; - ChannelDebugInfo.CurrentValue = Channel.Value.Value.GetCurrentValue(); - ChannelDebugInfo.TargetValue = Channel.Value.Value.TargetValue; - DebugInfo.Channels.Add(Channel.Key, ChannelDebugInfo); + FControlBusMixStageDebugInfo StageDebugInfo; + StageDebugInfo.CurrentValue = Stage.Value.Value.GetCurrentValue(); + StageDebugInfo.TargetValue = Stage.Value.Value.TargetValue; + DebugInfo.Stages.Add(Stage.Key, StageDebugInfo); } RefreshedFilteredMixes.Add(DebugInfo); } diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationDebugger.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationDebugger.h index 96e9350bdc58..d618796ff32e 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationDebugger.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationDebugger.h @@ -8,7 +8,7 @@ #include "SoundControlBus.h" #include "SoundControlBusMix.h" #include "SoundModulationProxy.h" -#include "SoundModulatorLFO.h" +#include "SoundModulationGeneratorLFO.h" namespace AudioModulation @@ -16,7 +16,7 @@ namespace AudioModulation // Forward Declarations struct FReferencedProxies; - struct FControlBusMixChannelDebugInfo + struct FControlBusMixStageDebugInfo { float TargetValue; float CurrentValue; @@ -28,7 +28,7 @@ namespace AudioModulation uint32 Id; uint32 RefCount; - TMap Channels; + TMap Stages; }; struct FControlBusDebugInfo @@ -38,7 +38,6 @@ namespace AudioModulation float LFOValue; float MixValue; float Value; - FVector2D Range; uint32 Id; uint32 RefCount; }; diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationProfileSerializer.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationProfileSerializer.h index 39d0fcfdfe94..1af42d1376c7 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationProfileSerializer.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationProfileSerializer.h @@ -17,7 +17,7 @@ namespace AudioModulation { class FProfileSerializer { - static bool ChannelValueToFloat(const FConfigSection& InSection, const FName& InMember, float& OutValue) + static bool StageValueToFloat(const FConfigSection& InSection, const FName& InMember, float& OutValue) { if (const FConfigValue* Value = InSection.Find(InMember)) { @@ -110,14 +110,14 @@ namespace AudioModulation FConfigFile& ConfigFile = GConfig->FindOrAdd(ConfigPath); ConfigFile.Reset(); - for (const FSoundControlBusMixChannel& Channel : InBusMix.Channels) + for (const FSoundControlBusMixStage& Stage : InBusMix.MixStages) { - if (Channel.Bus) + if (Stage.Bus) { - FConfigSection& ConfigSection = ConfigFile.Add(Channel.Bus->GetPathName()); - ConfigSection.Add(GET_MEMBER_NAME_CHECKED(FSoundModulationValue, AttackTime), FConfigValue(FString::Printf(TEXT("%f"), Channel.Value.AttackTime))); - ConfigSection.Add(GET_MEMBER_NAME_CHECKED(FSoundModulationValue, ReleaseTime), FConfigValue(FString::Printf(TEXT("%f"), Channel.Value.ReleaseTime))); - ConfigSection.Add(GET_MEMBER_NAME_CHECKED(FSoundModulationValue, TargetValue), FConfigValue(FString::Printf(TEXT("%f"), Channel.Value.TargetValue))); + FConfigSection& ConfigSection = ConfigFile.Add(Stage.Bus->GetPathName()); + ConfigSection.Add(GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, AttackTime), FConfigValue(FString::Printf(TEXT("%f"), Stage.Value.AttackTime))); + ConfigSection.Add(GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, ReleaseTime), FConfigValue(FString::Printf(TEXT("%f"), Stage.Value.ReleaseTime))); + ConfigSection.Add(GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, TargetValue), FConfigValue(FString::Printf(TEXT("%f"), Stage.Value.TargetValue))); } } ConfigFile.Dirty = true; @@ -150,29 +150,29 @@ namespace AudioModulation bool bMarkDirty = false; TSet SectionsProcessed; - TArray& Channels = InOutBusMix.Channels; - for (int32 i = Channels.Num() - 1; i >= 0; --i) + TArray& Stages = InOutBusMix.MixStages; + for (int32 i = Stages.Num() - 1; i >= 0; --i) { - FSoundControlBusMixChannel& Channel = Channels[i]; - if (Channel.Bus) + FSoundControlBusMixStage& Stage = Stages[i]; + if (Stage.Bus) { - FString PathName = Channel.Bus->GetPathName(); + FString PathName = Stage.Bus->GetPathName(); if (FConfigSection* ConfigSection = ConfigFile->Find(PathName)) { - ChannelValueToFloat(*ConfigSection, GET_MEMBER_NAME_CHECKED(FSoundModulationValue, AttackTime), Channel.Value.AttackTime); - ChannelValueToFloat(*ConfigSection, GET_MEMBER_NAME_CHECKED(FSoundModulationValue, ReleaseTime), Channel.Value.ReleaseTime); - ChannelValueToFloat(*ConfigSection, GET_MEMBER_NAME_CHECKED(FSoundModulationValue, TargetValue), Channel.Value.TargetValue); + StageValueToFloat(*ConfigSection, GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, AttackTime), Stage.Value.AttackTime); + StageValueToFloat(*ConfigSection, GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, ReleaseTime), Stage.Value.ReleaseTime); + StageValueToFloat(*ConfigSection, GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, TargetValue), Stage.Value.TargetValue); SectionsProcessed.Emplace(MoveTemp(PathName)); bMarkDirty = true; } else { - Channels.RemoveAt(i); + Stages.RemoveAt(i); } } else { - Channels.RemoveAt(i); + Stages.RemoveAt(i); } } @@ -184,23 +184,23 @@ namespace AudioModulation { const FConfigSection& ConfigSection = ConfigFile->FindChecked(SectionName); UObject* BusObj = FSoftObjectPath(SectionName).TryLoad(); - if (USoundControlBusBase* Bus = Cast(BusObj)) + if (USoundControlBus* Bus = Cast(BusObj)) { - FSoundControlBusMixChannel Channel; - Channel.Bus = Bus; + FSoundControlBusMixStage Stage; + Stage.Bus = Bus; - ChannelValueToFloat(ConfigSection, GET_MEMBER_NAME_CHECKED(FSoundModulationValue, AttackTime), Channel.Value.AttackTime); - ChannelValueToFloat(ConfigSection, GET_MEMBER_NAME_CHECKED(FSoundModulationValue, ReleaseTime), Channel.Value.ReleaseTime); - ChannelValueToFloat(ConfigSection, GET_MEMBER_NAME_CHECKED(FSoundModulationValue, TargetValue), Channel.Value.TargetValue); + StageValueToFloat(ConfigSection, GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, AttackTime), Stage.Value.AttackTime); + StageValueToFloat(ConfigSection, GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, ReleaseTime), Stage.Value.ReleaseTime); + StageValueToFloat(ConfigSection, GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, TargetValue), Stage.Value.TargetValue); - Channels.Emplace(MoveTemp(Channel)); + Stages.Emplace(MoveTemp(Stage)); bMarkDirty = true; } else { UE_LOG(LogAudioModulation, Warning, - TEXT("Bus missing or invalid type at '%s'. Profile '%s' channel not added/updated for mix '%s'"), + TEXT("Bus missing or invalid type at '%s'. Profile '%s' stage not added/updated for mix '%s'"), *SectionName, *ConfigPath, *Path); diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationStatics.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationStatics.cpp index bda14f58e6c4..6c72fdb4d5a7 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationStatics.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationStatics.cpp @@ -109,7 +109,7 @@ UAudioModulationStatics::UAudioModulationStatics(const FObjectInitializer& Objec { } -void UAudioModulationStatics::ActivateBus(const UObject* WorldContextObject, USoundControlBusBase* Bus) +void UAudioModulationStatics::ActivateBus(const UObject* WorldContextObject, USoundControlBus* Bus) { if (!Bus) { @@ -135,12 +135,12 @@ void UAudioModulationStatics::ActivateBusMix(const UObject* WorldContextObject, } } -void UAudioModulationStatics::ActivateBusModulator(const UObject* WorldContextObject, USoundBusModulatorBase* Modulator) +void UAudioModulationStatics::ActivateBusModulator(const UObject* WorldContextObject, USoundModulationGenerator* Modulator) { UWorld* World = GetAudioWorld(WorldContextObject); if (AudioModulation::FAudioModulationSystem* ModSystem = GetModulationSystem(World)) { - if (USoundBusModulatorLFO* LFO = Cast(Modulator)) + if (USoundModulationGeneratorLFO* LFO = Cast(Modulator)) { ModSystem->ActivateLFO(*LFO); } @@ -212,7 +212,7 @@ USoundControlBus* UAudioModulationStatics::CreateBus(const UObject* WorldContext return NewBus; } -USoundBusModulatorLFO* UAudioModulationStatics::CreateLFO(const UObject* WorldContextObject, FName Name, float Amplitude, float Frequency, float Offset, bool Activate) +USoundModulationGeneratorLFO* UAudioModulationStatics::CreateLFO(const UObject* WorldContextObject, FName Name, float Amplitude, float Frequency, float Offset, bool Activate) { UWorld* World = GetAudioWorld(WorldContextObject); if (!World) @@ -220,7 +220,7 @@ USoundBusModulatorLFO* UAudioModulationStatics::CreateLFO(const UObject* WorldCo return nullptr; } - USoundBusModulatorLFO* NewLFO = NewObject(GetTransientPackage(), Name); + USoundModulationGeneratorLFO* NewLFO = NewObject(GetTransientPackage(), Name); NewLFO->Amplitude = Amplitude; NewLFO->Frequency = Frequency; NewLFO->Offset = Offset; @@ -236,15 +236,15 @@ USoundBusModulatorLFO* UAudioModulationStatics::CreateLFO(const UObject* WorldCo return NewLFO; } -FSoundControlBusMixChannel UAudioModulationStatics::CreateBusMixChannel(const UObject* WorldContextObject, USoundControlBusBase* Bus, float Value, float AttackTime, float ReleaseTime) +FSoundControlBusMixStage UAudioModulationStatics::CreateBusMixStage(const UObject* WorldContextObject, USoundControlBus* Bus, float Value, float AttackTime, float ReleaseTime) { - FSoundControlBusMixChannel MixChannel; - MixChannel.Bus = Bus; - MixChannel.Value = FSoundModulationValue(Value, AttackTime, ReleaseTime); - return MixChannel; + FSoundControlBusMixStage MixStage; + MixStage.Bus = Bus; + MixStage.Value = FSoundModulationMixValue(Value, AttackTime, ReleaseTime); + return MixStage; } -USoundControlBusMix* UAudioModulationStatics::CreateBusMix(const UObject* WorldContextObject, FName Name, TArray Channels, bool Activate) +USoundControlBusMix* UAudioModulationStatics::CreateBusMix(const UObject* WorldContextObject, FName Name, TArray Stages, bool Activate) { UWorld* World = GetAudioWorld(WorldContextObject); if (!World) @@ -253,16 +253,16 @@ USoundControlBusMix* UAudioModulationStatics::CreateBusMix(const UObject* WorldC } USoundControlBusMix* NewBusMix = NewObject(GetTransientPackage(), Name); - for (FSoundControlBusMixChannel& Channel : Channels) + for (FSoundControlBusMixStage& Stage : Stages) { - if (Channel.Bus) + if (Stage.Bus) { - NewBusMix->Channels.Emplace(Channel); + NewBusMix->MixStages.Emplace(Stage); } else { UE_LOG(LogAudioModulation, Warning, - TEXT("USoundControlBusMix '%s' was created but bus provided is null. Channel not added to mix."), + TEXT("USoundControlBusMix '%s' was created but bus provided is null. Stage not added to mix."), *Name.ToString()); } } @@ -278,7 +278,7 @@ USoundControlBusMix* UAudioModulationStatics::CreateBusMix(const UObject* WorldC return NewBusMix; } -void UAudioModulationStatics::DeactivateBus(const UObject* WorldContextObject, USoundControlBusBase* Bus) +void UAudioModulationStatics::DeactivateBus(const UObject* WorldContextObject, USoundControlBus* Bus) { if (Bus) { @@ -302,12 +302,12 @@ void UAudioModulationStatics::DeactivateBusMix(const UObject* WorldContextObject } } -void UAudioModulationStatics::DeactivateBusModulator(const UObject* WorldContextObject, USoundBusModulatorBase* Modulator) +void UAudioModulationStatics::DeactivateBusModulator(const UObject* WorldContextObject, USoundModulationGenerator* Modulator) { UWorld* World = GetAudioWorld(WorldContextObject); if (AudioModulation::FAudioModulationSystem* ModSystem = GetModulationSystem(World)) { - if (USoundBusModulatorLFO* LFO = Cast(Modulator)) + if (USoundModulationGeneratorLFO* LFO = Cast(Modulator)) { ModSystem->DeactivateLFO(*LFO); } @@ -326,7 +326,7 @@ void UAudioModulationStatics::SaveMixToProfile(const UObject* WorldContextObject } } -TArray UAudioModulationStatics::LoadMixFromProfile(const UObject* WorldContextObject, USoundControlBusMix* BusMix, bool bActivate, int32 ProfileIndex) +TArray UAudioModulationStatics::LoadMixFromProfile(const UObject* WorldContextObject, USoundControlBusMix* BusMix, bool bActivate, int32 ProfileIndex) { if (BusMix) { @@ -341,10 +341,10 @@ TArray UAudioModulationStatics::LoadMixFromProfile(c } } - return TArray(); + return TArray(); } -void UAudioModulationStatics::UpdateMix(const UObject* WorldContextObject, USoundControlBusMix* Mix, TArray Channels) +void UAudioModulationStatics::UpdateMix(const UObject* WorldContextObject, USoundControlBusMix* Mix, TArray Stages, float InFadeTime) { if (Mix) { @@ -353,42 +353,40 @@ void UAudioModulationStatics::UpdateMix(const UObject* WorldContextObject, USoun { // UObject representation is not updated in this form of the call as doing so from // PIE can result in an unstable state where UObject is modified but not properly dirtied. - ModSystem->UpdateMix(Channels, *Mix, false /* bUpdateObject */); + ModSystem->UpdateMix(Stages, *Mix, false /* bUpdateObject */, InFadeTime); } } } void UAudioModulationStatics::UpdateMixByFilter( - const UObject* WorldContextObject, - USoundControlBusMix* Mix, - FString AddressFilter, - TSubclassOf BusClassFilter, - float Value, - float AttackTime, - float ReleaseTime) + const UObject* WorldContextObject, + USoundControlBusMix* Mix, + FString AddressFilter, + TSubclassOf ParamClassFilter, + USoundModulationParameter* ParamFilter, + float Value, + float FadeTime) { if (Mix) { UWorld* World = GetAudioWorld(WorldContextObject); if (AudioModulation::FAudioModulationSystem* ModSystem = GetModulationSystem(World)) { - FSoundModulationValue ModValue(Value, AttackTime, ReleaseTime); - // UObject representation is not updated in this form of the call as doing so from // PIE can result in an unstable state where UObject is modified but not properly dirtied. - ModSystem->UpdateMixByFilter(AddressFilter, BusClassFilter, ModValue, *Mix, false /* bUpdateObject */); + ModSystem->UpdateMixByFilter(AddressFilter, ParamClassFilter, ParamFilter, Value, FadeTime, *Mix, false /* bUpdateObject */); } } } -void UAudioModulationStatics::UpdateMixFromObject(const UObject* WorldContextObject, USoundControlBusMix* Mix) +void UAudioModulationStatics::UpdateMixFromObject(const UObject* WorldContextObject, USoundControlBusMix* Mix, float InFadeTime) { if (Mix) { UWorld* World = GetAudioWorld(WorldContextObject); if (AudioModulation::FAudioModulationSystem* ModSystem = GetModulationSystem(World)) { - ModSystem->UpdateMix(*Mix); + ModSystem->UpdateMix(*Mix, InFadeTime); } } } diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationSystem.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationSystem.cpp index 2aeb109f0fc1..4f0dcee05213 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationSystem.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationSystem.cpp @@ -14,7 +14,7 @@ #include "Misc/CoreDelegates.h" #include "SoundControlBusProxy.h" #include "SoundControlBusMixProxy.h" -#include "SoundModulatorLFOProxy.h" +#include "SoundModulationGeneratorLFOProxy.h" #include "SoundModulationPatchProxy.h" #include "SoundModulationProxy.h" #include "UObject/UObjectIterator.h" @@ -42,12 +42,12 @@ namespace AudioModulation COUNT }; - struct FProfileChannelInfo + struct FProfileStageInfo { - USoundControlBusBase* Bus; - FSoundModulationValue Value; + USoundControlBus* Bus; + FSoundModulationMixValue Value; - FProfileChannelInfo(const FModulatorBusMixChannelProxy& InProxy) + FProfileStageInfo(const FModulatorBusMixStageProxy& InProxy) : Bus(nullptr) , Value(InProxy.Value) { @@ -63,6 +63,11 @@ namespace AudioModulation } #if !UE_BUILD_SHIPPING + void FAudioModulationSystem::OnAuditionEnd() + { + DeactivateAllBusMixes(); + } + bool FAudioModulationSystem::OnPostHelp(FCommonViewportClient* ViewportClient, const TCHAR* Stream) { check(IsInGameThread()); @@ -82,7 +87,7 @@ namespace AudioModulation } #endif // !UE_BUILD_SHIPPING - void FAudioModulationSystem::ActivateBus(const USoundControlBusBase& InBus) + void FAudioModulationSystem::ActivateBus(const USoundControlBus& InBus) { RunCommandOnProcessingThread([this, Settings = FControlBusSettings(InBus)]() { @@ -114,7 +119,7 @@ namespace AudioModulation ActivateBusMix(FModulatorBusMixSettings(InBusMix)); } - void FAudioModulationSystem::ActivateLFO(const USoundBusModulatorLFO& InLFO) + void FAudioModulationSystem::ActivateLFO(const USoundModulationGeneratorLFO& InLFO) { RunCommandOnProcessingThread([this, Settings = FModulatorLFOSettings(InLFO)]() { @@ -137,7 +142,7 @@ namespace AudioModulation return !FMath::IsNearlyEqual(InitValue, OutValue); } - void FAudioModulationSystem::DeactivateBus(const USoundControlBusBase& InBus) + void FAudioModulationSystem::DeactivateBus(const USoundControlBus& InBus) { RunCommandOnProcessingThread([this, BusId = static_cast(InBus.GetUniqueID())]() { @@ -173,7 +178,7 @@ namespace AudioModulation }); } - void FAudioModulationSystem::DeactivateLFO(const USoundBusModulatorLFO& InLFO) + void FAudioModulationSystem::DeactivateLFO(const USoundModulationGeneratorLFO& InLFO) { RunCommandOnProcessingThread([this, LFOId = static_cast(InLFO.GetUniqueID())]() { @@ -403,36 +408,36 @@ namespace AudioModulation UE_LOG(LogAudioModulation, Display, TEXT("Mix '%s' is active, saving current mix proxy state to profile '%i'."), *MixName, InProfileIndex); AudioModulation::FModulatorBusMixProxy& MixProxy = MixHandle.FindProxy(); - TMap PassedChannelInfo; - for (TPair& Pair : MixProxy.Channels) + TMap PassedStageInfo; + for (TPair& Pair : MixProxy.Stages) { - FModulatorBusMixChannelProxy& Channel = Pair.Value; - PassedChannelInfo.Add(Pair.Key, Channel.Value); + FModulatorBusMixStageProxy& Stage = Pair.Value; + PassedStageInfo.Add(Pair.Key, Stage.Value); } - AsyncTask(ENamedThreads::GameThread, [this, PassedChannelInfo, MixToSerialize, InProfileIndex]() + AsyncTask(ENamedThreads::GameThread, [this, PassedStageInfo, MixToSerialize, InProfileIndex]() { if (!MixToSerialize.IsValid()) { return; } - TMap ChannelInfo = PassedChannelInfo; + TMap StageInfo = PassedStageInfo; USoundControlBusMix* TempMix = NewObject(GetTransientPackage(), *FGuid().ToString(EGuidFormats::Short)); // Buses on proxy may differ than those on uobject definition, so iterate and find by cached ids // and add to temp mix to be serialized. - for (TObjectIterator Itr; Itr; ++Itr) + for (TObjectIterator Itr; Itr; ++Itr) { - if (USoundControlBusBase* Bus = *Itr) + if (USoundControlBus* Bus = *Itr) { FBusId ItrBusId = static_cast(Bus->GetUniqueID()); - if (FSoundModulationValue* Value = ChannelInfo.Find(ItrBusId)) + if (FSoundModulationMixValue* Value = StageInfo.Find(ItrBusId)) { - FSoundControlBusMixChannel BusMixChannel; - BusMixChannel.Bus = Bus; - BusMixChannel.Value = *Value; - TempMix->Channels.Add(MoveTemp(BusMixChannel)); + FSoundControlBusMixStage BusMixStage; + BusMixStage.Bus = Bus; + BusMixStage.Value = *Value; + TempMix->MixStages.Add(MoveTemp(BusMixStage)); } } } @@ -443,18 +448,18 @@ namespace AudioModulation }); } - TArray FAudioModulationSystem::LoadMixFromProfile(const int32 InProfileIndex, USoundControlBusMix& OutBusMix) + TArray FAudioModulationSystem::LoadMixFromProfile(const int32 InProfileIndex, USoundControlBusMix& OutBusMix) { const FString TempName = FGuid::NewGuid().ToString(EGuidFormats::Short); if (USoundControlBusMix* TempMix = NewObject(GetTransientPackage(), *TempName)) { const FString MixPath = OutBusMix.GetPathName(); AudioModulation::FProfileSerializer::Deserialize(InProfileIndex, *TempMix, &MixPath); - UpdateMix(TempMix->Channels, OutBusMix); - return TempMix->Channels; + UpdateMix(TempMix->MixStages, OutBusMix); + return TempMix->MixStages; } - return TArray(); + return TArray(); } void FAudioModulationSystem::RunCommandOnProcessingThread(TUniqueFunction Cmd) @@ -483,12 +488,12 @@ namespace AudioModulation return static_cast(EModulatorType::Patch); } - if (RegisterModulator(InHandleId, InModulatorBase, RefProxies.Buses, RefModulators.BusMap, OutParameter)) + if (RegisterModulator(InHandleId, InModulatorBase, RefProxies.Buses, RefModulators.BusMap, OutParameter)) { return static_cast(EModulatorType::Bus); } - if (RegisterModulator(InHandleId, InModulatorBase, RefProxies.LFOs, RefModulators.LFOMap, OutParameter)) + if (RegisterModulator(InHandleId, InModulatorBase, RefProxies.LFOs, RefModulators.LFOMap, OutParameter)) { return static_cast(EModulatorType::LFO); } @@ -578,30 +583,30 @@ namespace AudioModulation }); } - void FAudioModulationSystem::UpdateMix(const TArray& InChannels, USoundControlBusMix& InOutMix, bool bInUpdateObject) + void FAudioModulationSystem::UpdateMix(const TArray& InStages, USoundControlBusMix& InOutMix, bool bInUpdateObject, float InFadeTime) { if (bInUpdateObject) { - TMap UpdatedChannelBusses; - for (const FSoundControlBusMixChannel& Channel : InChannels) + TMap UpdatedStageBusses; + for (const FSoundControlBusMixStage& Stage : InStages) { - if (Channel.Bus) + if (Stage.Bus) { - UpdatedChannelBusses.Add(Channel.Bus->GetUniqueID(), &Channel); + UpdatedStageBusses.Add(Stage.Bus->GetUniqueID(), &Stage); } } bool bMarkDirty = false; - for (FSoundControlBusMixChannel& Channel : InOutMix.Channels) + for (FSoundControlBusMixStage& Stage : InOutMix.MixStages) { - if (!Channel.Bus) + if (!Stage.Bus) { continue; } - if (const FSoundControlBusMixChannel* BusChannel = UpdatedChannelBusses.FindRef(Channel.Bus->GetUniqueID())) + if (const FSoundControlBusMixStage* BusStage = UpdatedStageBusses.FindRef(Stage.Bus->GetUniqueID())) { - Channel = *BusChannel; + Stage = *BusStage; bMarkDirty = true; } } @@ -610,62 +615,66 @@ namespace AudioModulation const FBusMixId MixId = static_cast(InOutMix.GetUniqueID()); - TArray ChannelSettings; - for (const FSoundControlBusMixChannel& Channel : InChannels) + TArray StageSettings; + for (const FSoundControlBusMixStage& Stage : InStages) { - ChannelSettings.Emplace(Channel); + StageSettings.Emplace(Stage); } - RunCommandOnProcessingThread([this, MixId, ChannelSettings]() + RunCommandOnProcessingThread([this, MixId, StageSettings, InFadeTime]() { if (FModulatorBusMixProxy* BusMixes = RefProxies.BusMixes.Find(MixId)) { - BusMixes->SetMix(ChannelSettings); + BusMixes->SetMix(StageSettings, InFadeTime); } }); } void FAudioModulationSystem::UpdateMixByFilter( - const FString& InAddressFilter, - const TSubclassOf& InBusClass, - const FSoundModulationValue& InValue, - USoundControlBusMix& InOutMix, - bool bInUpdateObject) + const FString& InAddressFilter, + const TSubclassOf& InParamClassFilter, + USoundModulationParameter* InParamFilter, + float InValue, + float InFadeTime, + USoundControlBusMix& InOutMix, + bool bInUpdateObject) { - const uint32 ClassId = InBusClass ? InBusClass->GetUniqueID() : USoundControlBusBase::StaticClass()->GetUniqueID(); + const uint32 ParamClassId = InParamClassFilter ? InParamClassFilter->GetUniqueID() : INDEX_NONE; + const uint32 ParamId = InParamFilter ? InParamFilter->GetUniqueID() : INDEX_NONE; if (bInUpdateObject) { bool bMarkDirty = false; - static const uint32 BaseBusClassId = USoundControlBusBase::StaticClass()->GetUniqueID(); - for (FSoundControlBusMixChannel& Channel : InOutMix.Channels) + for (FSoundControlBusMixStage& Stage : InOutMix.MixStages) { - if (!Channel.Bus) + if (!Stage.Bus) { continue; } - if (ClassId != BaseBusClassId && Channel.Bus->GetClass()->GetUniqueID() != ClassId) + if (USoundModulationParameter* Parameter = Stage.Bus->Parameter) + { + if (ParamId != INDEX_NONE && ParamId != Parameter->GetUniqueID()) + { + continue; + } + + if (UClass* Class = Parameter->GetClass()) + { + if (ParamClassId != INDEX_NONE && ParamClassId != Class->GetUniqueID()) + { + continue; + } + } + } + + if (!FAudioAddressPattern::PartsMatch(InAddressFilter, Stage.Bus->Address)) { continue; } - if (!FAudioAddressPattern::PartsMatch(InAddressFilter, Channel.Bus->Address)) - { - continue; - } - - Channel.Value.TargetValue = InValue.TargetValue; - - if (InValue.AttackTime >= 0.0f) - { - Channel.Value.AttackTime = InValue.AttackTime; - } - - if (InValue.ReleaseTime >= 0.0f) - { - Channel.Value.ReleaseTime = InValue.ReleaseTime; - } + Stage.Value.TargetValue = InValue; + Stage.Value.SetActiveFade(FSoundModulationMixValue::EActiveFade::Override, InFadeTime); bMarkDirty = true; } @@ -677,23 +686,31 @@ namespace AudioModulation const FString AddressFilter = InAddressFilter; const FBusMixId MixId = static_cast(InOutMix.GetUniqueID()); - RunCommandOnProcessingThread([this, ClassId, MixId, AddressFilter, InValue]() + RunCommandOnProcessingThread([this, ParamClassId, ParamId, MixId, AddressFilter, InValue, InFadeTime]() { if (FModulatorBusMixProxy* MixProxy = RefProxies.BusMixes.Find(MixId)) { - MixProxy->SetMixByFilter(AddressFilter, ClassId, InValue); + MixProxy->SetMixByFilter(AddressFilter, ParamClassId, ParamId, InValue, InFadeTime); } }); } - void FAudioModulationSystem::UpdateMix(const USoundControlBusMix& InMix) + void FAudioModulationSystem::UpdateMix(const USoundControlBusMix& InMix, float InFadeTime) { - RunCommandOnProcessingThread([this, MixSettings = FModulatorBusMixSettings(InMix)]() + RunCommandOnProcessingThread([this, MixSettings = FModulatorBusMixSettings(InMix), InFadeTime]() { FBusMixHandle BusMixHandle = FBusMixHandle::Get(MixSettings.GetId(), RefProxies.BusMixes); if (BusMixHandle.IsValid()) { - BusMixHandle.FindProxy() = MixSettings; + FModulatorBusMixProxy& MixProxy = BusMixHandle.FindProxy(); + if (MixProxy.GetStatus() == FModulatorBusMixProxy::EStatus::Enabled) + { + MixProxy = MixSettings; + for (TPair& Stage : MixProxy.Stages) + { + Stage.Value.Value.SetActiveFade(FSoundModulationMixValue::EActiveFade::Override, InFadeTime); + } + } } #if !UE_BUILD_SHIPPING else @@ -706,7 +723,7 @@ namespace AudioModulation void FAudioModulationSystem::UpdateModulator(const USoundModulatorBase& InModulator) { - if (const USoundBusModulatorLFO* InLFO = Cast(&InModulator)) + if (const USoundModulationGeneratorLFO* InLFO = Cast(&InModulator)) { RunCommandOnProcessingThread([this, LFOSettings = FModulatorLFOSettings(*InLFO)]() { @@ -724,7 +741,7 @@ namespace AudioModulation }); } - if (const USoundControlBusBase* InBus = Cast(&InModulator)) + if (const USoundControlBus* InBus = Cast(&InModulator)) { RunCommandOnProcessingThread([this, BusSettings = FControlBusSettings(*InBus)]() { diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationSystem.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationSystem.h index 616fa406679d..02287e6b2bc2 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationSystem.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/AudioModulationSystem.h @@ -15,8 +15,8 @@ #include "SoundModulationPatchProxy.h" #include "SoundModulationProxy.h" #include "SoundModulationValue.h" -#include "SoundModulatorLFO.h" -#include "SoundModulatorLFOProxy.h" +#include "SoundModulationGeneratorLFO.h" +#include "SoundModulationGeneratorLFOProxy.h" #include "Templates/Function.h" #if WITH_AUDIOMODULATION @@ -32,7 +32,7 @@ namespace AudioModulation class FModulationInputProxy; class FModulationPatchProxy; class FModulationPatchRefProxy; - class FModulatorBusMixChannelProxy; + class FModulatorBusMixStageProxy; struct FReferencedProxies { @@ -56,19 +56,19 @@ namespace AudioModulation void Initialize(const FAudioPluginInitializationParams& InitializationParams); - void ActivateBus(const USoundControlBusBase& InBus); + void ActivateBus(const USoundControlBus& InBus); void ActivateBusMix(const FModulatorBusMixSettings& InSettings); void ActivateBusMix(const USoundControlBusMix& InBusMix); - void ActivateLFO(const USoundBusModulatorLFO& InLFO); + void ActivateLFO(const USoundModulationGeneratorLFO& InLFO); /** * Deactivates respectively typed (i.e. BusMix, Bus, etc.) object proxy if no longer referenced. * If still referenced, will wait until references are finished before destroying. */ - void DeactivateBus(const USoundControlBusBase& InBus); + void DeactivateBus(const USoundControlBus& InBus); void DeactivateBusMix(const USoundControlBusMix& InBusMix); void DeactivateAllBusMixes(); - void DeactivateLFO(const USoundBusModulatorLFO& InLFO); + void DeactivateLFO(const USoundModulationGeneratorLFO& InLFO); Audio::FModulationParameter GetParameter(FName InParamName) const; @@ -84,20 +84,20 @@ namespace AudioModulation void SaveMixToProfile(const USoundControlBusMix& InBusMix, const int32 InProfileIndex); /* Loads mix from .ini profile for iterative development that does not require re-cooking a mix. Returns copy - * of mix channel values saved in profile. */ - TArray LoadMixFromProfile(const int32 InProfileIndex, USoundControlBusMix& OutBusMix); + * of mix stage values saved in profile. */ + TArray LoadMixFromProfile(const int32 InProfileIndex, USoundControlBusMix& OutBusMix); /* * Updates mix/mix by filter, modifying the mix instance if it is active. If bInUpdateObject is true, * updates UObject definition in addition to proxy. */ - void UpdateMix(const TArray& InChannels, USoundControlBusMix& InOutMix, bool bInUpdateObject = false); - void UpdateMixByFilter(const FString& InAddressFilter, const TSubclassOf& InClassFilter, const FSoundModulationValue& InValue, USoundControlBusMix& InOutMix, bool bInUpdateObject = false); + void UpdateMix(const TArray& InStages, USoundControlBusMix& InOutMix, bool bInUpdateObject = false, float InFadeTime = -1.0f); + void UpdateMixByFilter(const FString& InAddressFilter, const TSubclassOf& InParamClassFilter, USoundModulationParameter* InParamFilter, float Value, float FadeTime, USoundControlBusMix& InOutMix, bool bInUpdateObject = false); /* * Commits any changes from a mix applied to a UObject definition to mix instance if active. */ - void UpdateMix(const USoundControlBusMix& InMix); + void UpdateMix(const USoundControlBusMix& InMix, float InFadeTime = -1.0f); /* * Commits any changes from a modulator type applied to a UObject definition @@ -105,6 +105,8 @@ namespace AudioModulation */ void UpdateModulator(const USoundModulatorBase& InModulator); + void OnAuditionEnd(); + private: /* Calculates modulation value, storing it in the provided float reference and returns if value changed */ bool CalculateModulationValue(FModulationPatchProxy& OutProxy, float& OutValue) const; @@ -194,7 +196,7 @@ namespace AudioModulation friend FModulationInputProxy; friend FModulationPatchProxy; friend FModulationPatchRefProxy; - friend FModulatorBusMixChannelProxy; + friend FModulatorBusMixStageProxy; }; static void IterateModSystems(TUniqueFunction InFunction) @@ -229,21 +231,23 @@ namespace AudioModulation void SoloBusMix(const USoundControlBusMix& InBusMix) { } #endif // WITH_EDITOR + void OnAuditionEnd() { } + #if !UE_BUILD_SHIPPING bool OnPostHelp(FCommonViewportClient* ViewportClient, const TCHAR* Stream) { return false; } int OnRenderStat(FViewport* Viewport, FCanvas* Canvas, int32 X, int32 Y, const UFont& Font, const FVector* ViewLocation, const FRotator* ViewRotation) { return Y; } bool OnToggleStat(FCommonViewportClient* ViewportClient, const TCHAR* Stream) { return false; } #endif // !UE_BUILD_SHIPPING - void ActivateBus(const USoundControlBusBase& InBus) { } + void ActivateBus(const USoundControlBus& InBus) { } void ActivateBusMix(const FModulatorBusMixSettings& InSettings) { } void ActivateBusMix(const USoundControlBusMix& InBusMix) { } - void ActivateLFO(const USoundBusModulatorLFO& InLFO) { } + void ActivateLFO(const USoundModulationGeneratorLFO& InLFO) { } void DeactivateAllBusMixes() { } - void DeactivateBus(const USoundControlBusBase& InBus) { } - void DeactivateBusMix(const USoundControlBusBase& InBusMix) { } - void DeactivateLFO(const USoundBusModulatorLFO& InLFO) { } + void DeactivateBus(const USoundControlBus& InBus) { } + void DeactivateBusMix(const USoundControlBus& InBusMix) { } + void DeactivateLFO(const USoundModulationGeneratorLFO& InLFO) { } void SaveMixToProfile(const USoundControlBusMix& InBusMix, const int32 InProfileIndex) { } void LoadMixFromProfile(const int32 InProfileIndex, USoundControlBusMix& OutBusMix) { } @@ -255,9 +259,9 @@ namespace AudioModulation bool GetModulatorValue(const Audio::FModulatorHandle& ModulatorHandle, float& OutValue) const { return false; } void UnregisterModulator(const Audio::FModulatorHandle& InHandle) { } - void UpdateMix(const USoundControlBusMix& InMix) { } - void UpdateMix(const TArray& InChannels, USoundControlBusMix& InOutMix, bool bUpdateObject = false) { } - void UpdateMixByFilter(const FString& InAddressFilter, const TSubclassOf& InClassFilter, const FSoundModulationValue& InValue, USoundControlBusMix& InOutMix, bool bUpdateObject = false) { } + void UpdateMix(const USoundControlBusMix& InMix, float InFadeTime) { } + void UpdateMix(const TArray& InStages, USoundControlBusMix& InOutMix, bool bUpdateObject = false) { } + void UpdateMixByFilter(const FString& InAddressFilter, const TSubclassOf& InParamClassFilter, USoundModulationParameter* InParamFilter, float InValue, float InFadeTime, USoundControlBusMix& InOutMix, bool bUpdateObject = false) { } void UpdateModulator(const USoundModulatorBase& InModulator) { } }; } // namespace AudioModulation diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBus.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBus.cpp index 331d373ea0ab..22f27f1fb713 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBus.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBus.cpp @@ -9,19 +9,20 @@ #include "DSP/BufferVectorOperations.h" #include "Engine/World.h" #include "SoundControlBusProxy.h" -#include "SoundModulatorLFO.h" +#include "SoundModulationGeneratorLFO.h" -USoundControlBusBase::USoundControlBusBase(const FObjectInitializer& ObjectInitializer) +USoundControlBus::USoundControlBus(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , bBypass(false) #if WITH_EDITORONLY_DATA , bOverrideAddress(false) -#endif +#endif // WITH_EDITORONLY_DATA + , Parameter(nullptr) { } #if WITH_EDITOR -void USoundControlBusBase::PostDuplicate(EDuplicateMode::Type DuplicateMode) +void USoundControlBus::PostDuplicate(EDuplicateMode::Type DuplicateMode) { if (!bOverrideAddress) { @@ -31,11 +32,11 @@ void USoundControlBusBase::PostDuplicate(EDuplicateMode::Type DuplicateMode) Super::PostDuplicate(DuplicateMode); } -void USoundControlBusBase::PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent) +void USoundControlBus::PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent) { if (FProperty* Property = InPropertyChangedEvent.Property) { - if (Property->GetFName() == GET_MEMBER_NAME_CHECKED(USoundControlBusBase, bOverrideAddress) && !bOverrideAddress) + if (Property->GetFName() == GET_MEMBER_NAME_CHECKED(USoundControlBus, bOverrideAddress) && !bOverrideAddress) { Address = GetName(); } @@ -49,7 +50,7 @@ void USoundControlBusBase::PostEditChangeProperty(FPropertyChangedEvent& InPrope Super::PostEditChangeProperty(InPropertyChangedEvent); } -void USoundControlBusBase::PostInitProperties() +void USoundControlBus::PostInitProperties() { if (!bOverrideAddress) { @@ -59,7 +60,7 @@ void USoundControlBusBase::PostInitProperties() Super::PostInitProperties(); } -void USoundControlBusBase::PostRename(UObject* OldOuter, const FName OldName) +void USoundControlBus::PostRename(UObject* OldOuter, const FName OldName) { if (!bOverrideAddress) { @@ -68,7 +69,7 @@ void USoundControlBusBase::PostRename(UObject* OldOuter, const FName OldName) } #endif // WITH_EDITOR -void USoundControlBusBase::BeginDestroy() +void USoundControlBus::BeginDestroy() { Super::BeginDestroy(); @@ -87,20 +88,7 @@ void USoundControlBusBase::BeginDestroy() } } -const Audio::FModulationMixFunction& USoundControlBusBase::GetMixFunction() const +const Audio::FModulationMixFunction& USoundControlBus::GetMixFunction() const { return Audio::FModulationParameter::GetDefaultMixFunction(); } - -USoundControlBus::USoundControlBus(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , Parameter(nullptr) -{ -} - -#if WITH_EDITOR -void USoundControlBus::PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent) -{ - Super::PostEditChangeProperty(InPropertyChangedEvent); -} -#endif // WITH_EDITOR \ No newline at end of file diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusMix.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusMix.cpp index a500df26031b..8af2a019f16c 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusMix.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusMix.cpp @@ -16,33 +16,23 @@ #include "Widgets/Notifications/SNotificationList.h" #endif // WITH_EDITOR - #define LOCTEXT_NAMESPACE "AudioModulation" -FSoundControlBusMixChannel::FSoundControlBusMixChannel() +FSoundControlBusMixStage::FSoundControlBusMixStage() : Bus(nullptr) { } -FSoundControlBusMixChannel::FSoundControlBusMixChannel(USoundControlBusBase* InBus, const float TargetValue) +FSoundControlBusMixStage::FSoundControlBusMixStage(USoundControlBus* InBus, const float TargetValue) : Bus(InBus) { - if (Bus) - { - Value.TargetValue = FMath::Clamp(TargetValue, Bus->GetMin(), Bus->GetMax()); - } - else - { - Value.TargetValue = FMath::Clamp(TargetValue, 0.0f, 1.0f); - } + Value.TargetValue = FMath::Clamp(TargetValue, 0.0f, 1.0f); } USoundControlBusMix::USoundControlBusMix(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) -#if WITH_EDITORONLY_DATA , ProfileIndex(0) -#endif // WITH_EDITORONLY_DATA { } @@ -67,8 +57,6 @@ void USoundControlBusMix::BeginDestroy() } } -#if WITH_EDITOR - void USoundControlBusMix::ActivateMix() { AudioModulation::IterateModSystems([this](AudioModulation::FAudioModulationSystem& OutModSystem) @@ -95,7 +83,9 @@ void USoundControlBusMix::DeactivateAllMixes() void USoundControlBusMix::LoadMixFromProfile() { - if (AudioModulation::FProfileSerializer::Deserialize(ProfileIndex, *this)) + const bool bSucceeded = AudioModulation::FProfileSerializer::Deserialize(ProfileIndex, *this); +#if WITH_EDITOR + if (bSucceeded) { FNotificationInfo Info(FText::Format( LOCTEXT("SoundControlBusMix_LoadSucceeded", "'Control Bus Mix '{0}' profile {1} loaded successfully."), @@ -107,8 +97,10 @@ void USoundControlBusMix::LoadMixFromProfile() Info.bUseThrobber = true; FSlateNotificationManager::Get().AddNotification(Info); } +#endif // WITH_EDITOR } +#if WITH_EDITOR void USoundControlBusMix::PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent) { OnPropertyChanged(InPropertyChangedEvent.Property, InPropertyChangedEvent.ChangeType); @@ -127,13 +119,13 @@ void USoundControlBusMix::OnPropertyChanged(FProperty* Property, EPropertyChange { if (InChangeType == EPropertyChangeType::Interactive || InChangeType == EPropertyChangeType::ValueSet) { - if (Property->GetFName() == GET_MEMBER_NAME_CHECKED(FSoundModulationValue, TargetValue)) + if (Property->GetFName() == GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, TargetValue)) { - for (FSoundControlBusMixChannel& Channel : Channels) + for (FSoundControlBusMixStage& Stage : MixStages) { - if (Channel.Bus) + if (Stage.Bus) { - Channel.Value.TargetValue = FMath::Clamp(Channel.Value.TargetValue, Channel.Bus->GetMin(), Channel.Bus->GetMax()); + Stage.Value.TargetValue = FMath::Clamp(Stage.Value.TargetValue, 0.0f, 1.0f); } } } @@ -145,10 +137,13 @@ void USoundControlBusMix::OnPropertyChanged(FProperty* Property, EPropertyChange OutModSystem.UpdateMix(*this); }); } +#endif // WITH_EDITOR void USoundControlBusMix::SaveMixToProfile() { - if (AudioModulation::FProfileSerializer::Serialize(*this, ProfileIndex)) + const bool bSucceeded = AudioModulation::FProfileSerializer::Serialize(*this, ProfileIndex); +#if WITH_EDITOR + if (bSucceeded) { FNotificationInfo Info(FText::Format( LOCTEXT("SoundControlBusMix_SaveSucceeded", "'Control Bus Mix '{0}' profile {1} saved successfully."), @@ -160,6 +155,7 @@ void USoundControlBusMix::SaveMixToProfile() Info.bUseThrobber = true; FSlateNotificationManager::Get().AddNotification(Info); } +#endif // WITH_EDITOR } void USoundControlBusMix::SoloMix() @@ -169,6 +165,5 @@ void USoundControlBusMix::SoloMix() OutModSystem.SoloBusMix(*this); }); } -#endif // WITH_EDITOR #undef LOCTEXT_NAMESPACE // AudioModulation diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusMixProxy.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusMixProxy.cpp index 4d6c1749445c..14dd73e660c9 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusMixProxy.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusMixProxy.cpp @@ -18,19 +18,21 @@ namespace AudioModulation { const FBusMixId InvalidBusMixId = INDEX_NONE; - FModulatorBusMixChannelSettings::FModulatorBusMixChannelSettings(const FSoundControlBusMixChannel& InChannel) - : TModulatorBase(InChannel.Bus->GetName(), InChannel.Bus->GetUniqueID()) - , Address(InChannel.Bus->Address) - , ClassId(InChannel.Bus->GetClass()->GetUniqueID()) - , Value(InChannel.Value) - , BusSettings(FControlBusSettings(*InChannel.Bus)) + FModulatorBusMixStageSettings::FModulatorBusMixStageSettings(const FSoundControlBusMixStage& InStage) + : TModulatorBase(InStage.Bus->GetName(), InStage.Bus->GetUniqueID()) + , Address(InStage.Bus->Address) + , ParamClassId(InStage.Bus->Parameter->GetClass()->GetUniqueID()) + , ParamId(InStage.Bus->Parameter->GetUniqueID()) + , Value(InStage.Value) + , BusSettings(FControlBusSettings(*InStage.Bus)) { } - FModulatorBusMixChannelProxy::FModulatorBusMixChannelProxy(const FModulatorBusMixChannelSettings& InSettings, FAudioModulationSystem& OutModSystem) + FModulatorBusMixStageProxy::FModulatorBusMixStageProxy(const FModulatorBusMixStageSettings& InSettings, FAudioModulationSystem& OutModSystem) : TModulatorBase(InSettings.BusSettings.GetName(), InSettings.BusSettings.GetId()) , Address(InSettings.Address) - , ClassId(InSettings.ClassId) + , ParamClassId(InSettings.ParamClassId) + , ParamId(InSettings.ParamId) , Value(InSettings.Value) , BusHandle(FBusHandle::Create(InSettings.BusSettings, OutModSystem.RefProxies.Buses, OutModSystem)) { @@ -39,17 +41,17 @@ namespace AudioModulation FModulatorBusMixSettings::FModulatorBusMixSettings(const USoundControlBusMix& InBusMix) : TModulatorBase(InBusMix.GetName(), InBusMix.GetUniqueID()) { - for (const FSoundControlBusMixChannel& Channel : InBusMix.Channels) + for (const FSoundControlBusMixStage& Stage : InBusMix.MixStages) { - if (Channel.Bus) + if (Stage.Bus) { - Channels.Add(FModulatorBusMixChannelSettings(Channel)); + Stages.Add(FModulatorBusMixStageSettings(Stage)); } else { UE_LOG(LogAudioModulation, Warning, - TEXT("USoundControlBusMix '%s' has channel with no bus specified. " - "Mix instance initialized with channel ignored."), + TEXT("USoundControlBusMix '%s' has stage with no bus specified. " + "Mix instance initialized with stage ignored."), *InBusMix.GetFullName()); } } @@ -75,74 +77,71 @@ namespace AudioModulation void FModulatorBusMixProxy::Reset() { - Channels.Reset(); + Stages.Reset(); } void FModulatorBusMixProxy::SetEnabled(const FModulatorBusMixSettings& InSettings) { check(ModSystem); - // Cache channels to avoid releasing channel state (and potentially referenced bus state) when re-enabling - FChannelMap CachedChannels = Channels; + // Cache stages to avoid releasing stage state (and potentially referenced bus state) when re-enabling + FStageMap CachedStages = Stages; Status = EStatus::Enabled; - Channels.Reset(); - for (const FModulatorBusMixChannelSettings& ChannelSettings : InSettings.Channels) + Stages.Reset(); + for (const FModulatorBusMixStageSettings& StageSettings : InSettings.Stages) { - FModulatorBusMixChannelProxy ChannelProxy(ChannelSettings, *ModSystem); + FModulatorBusMixStageProxy StageProxy(StageSettings, *ModSystem); - const FBusId BusId = ChannelSettings.GetId(); - if (const FModulatorBusMixChannelProxy* CachedChannel = CachedChannels.Find(BusId)) + const FBusId BusId = StageSettings.GetId(); + if (const FModulatorBusMixStageProxy* CachedStage = CachedStages.Find(BusId)) { - ChannelProxy.Value.SetCurrentValue(CachedChannel->Value.GetCurrentValue()); + StageProxy.Value.SetCurrentValue(CachedStage->Value.GetCurrentValue()); } - Channels.Emplace(BusId, ChannelProxy); + Stages.Emplace(BusId, StageProxy); } } - void FModulatorBusMixProxy::SetMix(const TArray& InChannels) + void FModulatorBusMixProxy::SetMix(const TArray& InStages, float InFadeTime) { - for (const FModulatorBusMixChannelSettings& NewChannel : InChannels) + for (const FModulatorBusMixStageSettings& NewStage : InStages) { - const FBusId BusId = NewChannel.GetId(); - if (FModulatorBusMixChannelProxy* ChannelProxy = Channels.Find(BusId)) + const FBusId BusId = NewStage.GetId(); + if (FModulatorBusMixStageProxy* StageProxy = Stages.Find(BusId)) { - ChannelProxy->Value.TargetValue = NewChannel.Value.TargetValue; - ChannelProxy->Value.AttackTime = NewChannel.Value.AttackTime; - ChannelProxy->Value.ReleaseTime = NewChannel.Value.ReleaseTime; + StageProxy->Value.TargetValue = NewStage.Value.TargetValue; + StageProxy->Value.AttackTime = NewStage.Value.AttackTime; + StageProxy->Value.ReleaseTime = NewStage.Value.ReleaseTime; + + // Setting entire mix wipes pre-existing user fade requests + StageProxy->Value.SetActiveFade(FSoundModulationMixValue::EActiveFade::Override, InFadeTime); } } } - void FModulatorBusMixProxy::SetMixByFilter(const FString& InAddressFilter, uint32 InFilterClassId, const FSoundModulationValue& InValue) + void FModulatorBusMixProxy::SetMixByFilter(const FString& InAddressFilter, uint32 InParamClassId, uint32 InParamId, float InValue, float InFadeTime) { - static const uint32 BaseClassId = USoundControlBusBase::StaticClass()->GetUniqueID(); - - for (TPair& IdProxyPair : Channels) + for (TPair& IdProxyPair : Stages) { - FModulatorBusMixChannelProxy& ChannelProxy = IdProxyPair.Value; - if (InFilterClassId != BaseClassId && ChannelProxy.ClassId != InFilterClassId) + FModulatorBusMixStageProxy& StageProxy = IdProxyPair.Value; + if (InParamId != INDEX_NONE && StageProxy.ParamId != InParamId) { continue; } - if (!FAudioAddressPattern::PartsMatch(InAddressFilter, ChannelProxy.Address)) + if (InParamClassId != INDEX_NONE && StageProxy.ParamClassId != InParamClassId) { continue; } - ChannelProxy.Value.TargetValue = InValue.TargetValue; - - if (InValue.AttackTime >= 0.0f) + if (!FAudioAddressPattern::PartsMatch(InAddressFilter, StageProxy.Address)) { - ChannelProxy.Value.AttackTime = InValue.AttackTime; + continue; } - if (InValue.ReleaseTime >= 0.0f) - { - ChannelProxy.Value.ReleaseTime = InValue.ReleaseTime; - } + StageProxy.Value.TargetValue = InValue; + StageProxy.Value.SetActiveFade(FSoundModulationMixValue::EActiveFade::Override, InFadeTime); } } @@ -157,20 +156,21 @@ namespace AudioModulation void FModulatorBusMixProxy::Update(const double InElapsed, FBusProxyMap& OutProxyMap) { bool bRequestStop = true; - for (TPair& Channel : Channels) + for (TPair& Stage : Stages) { - FModulatorBusMixChannelProxy& ChannelProxy = Channel.Value; - FSoundModulationValue& MixChannelValue = ChannelProxy.Value; + FModulatorBusMixStageProxy& StageProxy = Stage.Value; + FSoundModulationMixValue& MixStageValue = StageProxy.Value; - if (FControlBusProxy* BusProxy = OutProxyMap.Find(ChannelProxy.GetId())) + if (FControlBusProxy* BusProxy = OutProxyMap.Find(StageProxy.GetId())) { - MixChannelValue.Update(InElapsed); + MixStageValue.Update(InElapsed); - const float CurrentValue = MixChannelValue.GetCurrentValue(); + const float CurrentValue = MixStageValue.GetCurrentValue(); if (Status == EStatus::Stopping) { - MixChannelValue.TargetValue = BusProxy->GetDefaultValue(); - if (!FMath::IsNearlyEqual(MixChannelValue.TargetValue, CurrentValue)) + MixStageValue.TargetValue = BusProxy->GetDefaultValue(); + MixStageValue.SetActiveFade(FSoundModulationMixValue::EActiveFade::Release); + if (!FMath::IsNearlyEqual(MixStageValue.TargetValue, CurrentValue)) { bRequestStop = false; } diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusMixProxy.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusMixProxy.h index 6b97a552d4fc..5389e0389280 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusMixProxy.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusMixProxy.h @@ -5,11 +5,11 @@ #include "SoundControlBusProxy.h" #include "SoundModulationProxy.h" #include "SoundModulationValue.h" -#include "SoundModulatorLFOProxy.h" +#include "SoundModulationGeneratorLFOProxy.h" // Forward Declarations -struct FSoundControlBusMixChannel; +struct FSoundControlBusMixStage; class USoundControlBusMix; @@ -21,14 +21,15 @@ namespace AudioModulation using FBusMixId = uint32; extern const FBusMixId InvalidBusMixId; - class FModulatorBusMixChannelSettings : public TModulatorBase + class FModulatorBusMixStageSettings : public TModulatorBase { public: - FModulatorBusMixChannelSettings(const FSoundControlBusMixChannel& InChannel); + FModulatorBusMixStageSettings(const FSoundControlBusMixStage& InStage); FString Address; - uint32 ClassId; - FSoundModulationValue Value; + uint32 ParamClassId = INDEX_NONE; + uint32 ParamId = INDEX_NONE; + FSoundModulationMixValue Value; FControlBusSettings BusSettings; }; @@ -37,18 +38,19 @@ namespace AudioModulation public: FModulatorBusMixSettings(const USoundControlBusMix& InBusMix); - TArray Channels; + TArray Stages; }; - class FModulatorBusMixChannelProxy : public TModulatorBase + class FModulatorBusMixStageProxy : public TModulatorBase { public: - FModulatorBusMixChannelProxy(const FModulatorBusMixChannelSettings& InSettings, FAudioModulationSystem& OutModSystem); + FModulatorBusMixStageProxy(const FModulatorBusMixStageSettings& InSettings, FAudioModulationSystem& OutModSystem); FString Address; - uint32 ClassId; - FSoundModulationValue Value; + uint32 ParamClassId = INDEX_NONE; + uint32 ParamId = INDEX_NONE; + FSoundModulationMixValue Value; FBusHandle BusHandle; }; @@ -68,18 +70,18 @@ namespace AudioModulation EStatus GetStatus() const; - // Resets channel map + // Resets stage map void Reset(); void SetEnabled(const FModulatorBusMixSettings& InSettings); - void SetMix(const TArray& InChannels); - void SetMixByFilter(const FString& InAddressFilter, uint32 InFilterClassId, const FSoundModulationValue& InValue); + void SetMix(const TArray& InStages, float InFadeTime); + void SetMixByFilter(const FString& InAddressFilter, uint32 InParamClassId, uint32 InParamId, float InValue, float InFadeTime); void SetStopping(); void Update(const double Elapsed, FBusProxyMap& ProxyMap); - using FChannelMap = TMap; - FChannelMap Channels; + using FStageMap = TMap; + FStageMap Stages; private: EStatus Status; diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusProxy.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusProxy.cpp index 43e3e1399dc2..0ec646fa35e4 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusProxy.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusProxy.cpp @@ -7,7 +7,7 @@ #include "AudioModulationLogging.h" #include "AudioModulationSystem.h" #include "Engine/World.h" -#include "SoundModulatorLFO.h" +#include "SoundModulationGeneratorLFO.h" namespace AudioModulation @@ -19,7 +19,6 @@ namespace AudioModulation , LFOValue(1.0f) , MixValue(NAN) , bBypass(false) - , Range(0.0f, 1.0f) { } @@ -50,11 +49,6 @@ namespace AudioModulation return LFOValue; } - FVector2D FControlBusProxy::GetRange() const - { - return Range; - } - float FControlBusProxy::GetMixValue() const { return MixValue; @@ -63,7 +57,7 @@ namespace AudioModulation float FControlBusProxy::GetValue() const { const float DefaultMixed = Mix(DefaultValue); - return FMath::Clamp(DefaultMixed * LFOValue, Range.X, Range.Y); + return FMath::Clamp(DefaultMixed * LFOValue, 0.0f, 1.0f); } void FControlBusProxy::Init(const FControlBusSettings& InSettings) @@ -73,14 +67,8 @@ namespace AudioModulation LFOValue = 1.0f; MixValue = NAN; MixFunction = InSettings.MixFunction; - Range = FVector2D(InSettings.Min, InSettings.Max); - if (InSettings.Min > InSettings.Max) - { - Range.X = InSettings.Max; - Range.Y = InSettings.Min; - } - DefaultValue = FMath::Clamp(InSettings.DefaultValue, Range.X, Range.Y); + DefaultValue = FMath::Clamp(InSettings.DefaultValue, 0.0f, 1.0f); bBypass = InSettings.bBypass; TArray NewHandles; diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusProxy.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusProxy.h index 85a5cfff2a5c..4f42e9791460 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusProxy.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundControlBusProxy.h @@ -5,7 +5,7 @@ #include "SoundControlBus.h" #include "SoundModulationParameter.h" #include "SoundModulationProxy.h" -#include "SoundModulatorLFOProxy.h" +#include "SoundModulationGeneratorLFOProxy.h" namespace AudioModulation @@ -20,23 +20,19 @@ namespace AudioModulation { bool bBypass; float DefaultValue; - float Min; - float Max; TArray LFOSettings; Audio::FModulationMixFunction MixFunction; - FControlBusSettings(const USoundControlBusBase& InBus) + FControlBusSettings(const USoundControlBus& InBus) : TModulatorBase(InBus.GetName(), InBus.GetUniqueID()) , bBypass() - , DefaultValue(InBus.GetDefaultValue()) - , Min(InBus.GetMin()) - , Max(InBus.GetMax()) + , DefaultValue(InBus.GetDefaultLinearValue()) , MixFunction(InBus.GetMixFunction()) { - for (const USoundBusModulatorBase* Modulator : InBus.Modulators) + for (const USoundModulationGenerator* Modulator : InBus.Modulators) { - if (const USoundBusModulatorLFO* LFO = Cast(Modulator)) + if (const USoundModulationGeneratorLFO* LFO = Cast(Modulator)) { LFOSettings.Add(*LFO); } @@ -56,7 +52,6 @@ namespace AudioModulation const TArray& GetLFOHandles() const; float GetLFOValue() const; float GetMixValue() const; - FVector2D GetRange() const; float GetValue() const; bool IsBypassed() const; void MixIn(const float InValue); @@ -77,7 +72,6 @@ namespace AudioModulation Audio::FModulationMixFunction MixFunction; TArray LFOHandles; - FVector2D Range; }; using FBusProxyMap = TMap; diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGenerator.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGenerator.cpp new file mode 100644 index 000000000000..2e83b48e8e65 --- /dev/null +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGenerator.cpp @@ -0,0 +1,26 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#include "SoundModulationGenerator.h" + +#include "AudioDevice.h" +#include "AudioModulation.h" +#include "AudioModulationLogging.h" +#include "AudioModulationSystem.h" +#include "Engine/World.h" + + +USoundModulationGenerator::USoundModulationGenerator(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +#if WITH_EDITOR +void USoundModulationGenerator::PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent) +{ + AudioModulation::IterateModSystems([this](AudioModulation::FAudioModulationSystem& OutModSystem) + { + OutModSystem.UpdateModulator(*this); + }); + + Super::PostEditChangeProperty(InPropertyChangedEvent); +} +#endif // WITH_EDITOR diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulatorLFO.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGeneratorLFO.cpp similarity index 52% rename from Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulatorLFO.cpp rename to Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGeneratorLFO.cpp index 856a9d5ee936..f06dab6cc092 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulatorLFO.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGeneratorLFO.cpp @@ -1,34 +1,17 @@ // Copyright Epic Games, Inc. All Rights Reserved. -#include "SoundModulatorLFO.h" +#include "SoundModulationGeneratorLFO.h" #include "AudioDevice.h" #include "AudioModulation.h" #include "AudioModulationLogging.h" #include "AudioModulationSystem.h" #include "Engine/World.h" -#include "SoundModulatorLFOProxy.h" +#include "SoundModulationGeneratorLFOProxy.h" -USoundBusModulatorBase::USoundBusModulatorBase(const FObjectInitializer& ObjectInitializer) +USoundModulationGeneratorLFO::USoundModulationGeneratorLFO(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) -{ -} - -#if WITH_EDITOR -void USoundBusModulatorBase::PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent) -{ - AudioModulation::IterateModSystems([this](AudioModulation::FAudioModulationSystem& OutModSystem) - { - OutModSystem.UpdateModulator(*this); - }); - - Super::PostEditChangeProperty(InPropertyChangedEvent); -} -#endif // WITH_EDITOR - -USoundBusModulatorLFO::USoundBusModulatorLFO(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , Shape(ESoundModulatorLFOShape::Sine) + , Shape(ESoundModulationGeneratorLFOShape::Sine) , Amplitude(0.5f) , Frequency(1.0f) , Offset(0.5f) @@ -37,7 +20,7 @@ USoundBusModulatorLFO::USoundBusModulatorLFO(const FObjectInitializer& ObjectIni { } -void USoundBusModulatorLFO::BeginDestroy() +void USoundModulationGeneratorLFO::BeginDestroy() { Super::BeginDestroy(); diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulatorLFOProxy.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGeneratorLFOProxy.cpp similarity index 88% rename from Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulatorLFOProxy.cpp rename to Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGeneratorLFOProxy.cpp index 4a90516136f6..b4c7415adeb5 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulatorLFOProxy.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGeneratorLFOProxy.cpp @@ -1,5 +1,5 @@ // Copyright Epic Games, Inc. All Rights Reserved. -#include "SoundModulatorLFOProxy.h" +#include "SoundModulationGeneratorLFOProxy.h" #include "AudioModulation.h" #include "AudioModulationSystem.h" @@ -52,7 +52,7 @@ namespace AudioModulation LFO.SetFrequency(InLFO.Frequency); LFO.SetMode(InLFO.bLooping ? Audio::ELFOMode::Type::Sync : Audio::ELFOMode::OneShot); - static_assert(static_cast(ESoundModulatorLFOShape::COUNT) == static_cast(Audio::ELFO::Type::NumLFOTypes), "LFOShape/ELFO Type mismatch"); + static_assert(static_cast(ESoundModulationGeneratorLFOShape::COUNT) == static_cast(Audio::ELFO::Type::NumLFOTypes), "LFOShape/ELFO Type mismatch"); LFO.SetType(static_cast(InLFO.Shape)); LFO.Start(); } diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulatorLFOProxy.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGeneratorLFOProxy.h similarity index 85% rename from Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulatorLFOProxy.h rename to Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGeneratorLFOProxy.h index 37e0f9048438..eef378bcb7b6 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulatorLFOProxy.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationGeneratorLFOProxy.h @@ -1,8 +1,8 @@ // Copyright Epic Games, Inc. All Rights Reserved. #pragma once +#include "SoundModulationGeneratorLFO.h" #include "SoundModulationProxy.h" -#include "SoundModulatorLFO.h" namespace AudioModulation @@ -30,9 +30,9 @@ namespace AudioModulation uint8 bBypass : 1; uint8 bLooping : 1; - ESoundModulatorLFOShape Shape; + ESoundModulationGeneratorLFOShape Shape; - FModulatorLFOSettings(const USoundBusModulatorLFO& InLFO) + FModulatorLFOSettings(const USoundModulationGeneratorLFO& InLFO) : TModulatorBase(InLFO.GetName(), InLFO.GetUniqueID()) , Amplitude(InLFO.Amplitude) , Frequency(InLFO.Frequency) @@ -42,12 +42,6 @@ namespace AudioModulation , Shape(InLFO.Shape) { } - - // TODO: Remove once moved all UObjects off AudioModSystem render command queue - FLFOId GetUniqueID() const - { - return GetId(); - } }; class FModulatorLFOProxy : public TModulatorProxyRefType diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationParameter.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationParameter.cpp index a672971e46e9..eb728b0722d2 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationParameter.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationParameter.cpp @@ -100,6 +100,7 @@ Audio::FModulationUnitConvertFunction USoundModulationParameterScaled::GetUnitCo } }; } + Audio::FModulationLinearConversionFunction USoundModulationParameterScaled::GetLinearConversionFunction() const { return [InUnitMin = UnitMin, InUnitMax = UnitMax](float* RESTRICT OutValueBuffer, int32 InNumSamples) diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationPatch.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationPatch.cpp index d0d0f7a2537b..bbfac380870d 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationPatch.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationPatch.cpp @@ -52,37 +52,22 @@ void USoundModulationPatch::PostEditChangeChainProperty(FPropertyChangedChainEve Super::PostEditChangeChainProperty(InPropertyChangedEvent); } - -void FSoundModulationPatchBase::Clamp() -{ - if (FSoundModulationOutputBase* Output = GetOutput()) - { - if (Output->Transform.InputMin > Output->Transform.InputMax) - { - Output->Transform.InputMin = Output->Transform.InputMax; - } - - if (Output->Transform.OutputMin > Output->Transform.OutputMax) - { - Output->Transform.OutputMin = Output->Transform.OutputMax; - } - } -} #endif // WITH_EDITOR -FSoundModulationInputBase::FSoundModulationInputBase() +FSoundControlModulationInput::FSoundControlModulationInput() : bSampleAndHold(0) { } -const USoundControlBusBase* FSoundControlModulationInput::GetBus() const +const USoundControlBus* FSoundControlModulationInput::GetBus() const { - return Cast(Bus); + return Bus; } -FSoundModulationPatchBase::FSoundModulationPatchBase() - : bBypass(1) +const USoundControlBus& FSoundControlModulationInput::GetBusChecked() const { + check(Bus); + return *Bus; } #undef LOCTEXT_NAMESPACE // SoundModulationPatch diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationPatchProxy.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationPatchProxy.cpp index ede2eae939e4..aaaf938fd942 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationPatchProxy.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationPatchProxy.cpp @@ -13,7 +13,6 @@ #include "SoundModulationTransform.h" - namespace AudioModulation { const FPatchId InvalidPatchId = INDEX_NONE; @@ -25,7 +24,7 @@ namespace AudioModulation { } - FModulationOutputProxy::FModulationOutputProxy(FSoundModulationOutputTransform InTransform, float InDefaultValue, const Audio::FModulationMixFunction& InMixFunction) + FModulationOutputProxy::FModulationOutputProxy(float InDefaultValue, const Audio::FModulationMixFunction& InMixFunction) : MixFunction(InMixFunction) , DefaultValue(InDefaultValue) { @@ -39,7 +38,7 @@ namespace AudioModulation void FModulationPatchProxy::Init(const FModulationPatchSettings& InSettings, FAudioModulationSystem& OutModSystem) { bBypass = InSettings.bBypass; - DefaultInputValue = InSettings.DefaultInputValue; + DefaultValue = InSettings.DefaultOutputValue; // Cache proxies to avoid releasing bus state (and potentially referenced bus state) when reinitializing TArray CachedProxies = InputProxies; @@ -50,7 +49,7 @@ namespace AudioModulation InputProxies.Emplace(Input, OutModSystem); } - OutputProxy = FModulationOutputProxy(InSettings.Transform, InSettings.DefaultOutputValue, InSettings.MixFunction); + OutputProxy = FModulationOutputProxy(InSettings.DefaultOutputValue, InSettings.MixFunction); } bool FModulationPatchProxy::IsBypassed() const @@ -70,12 +69,13 @@ namespace AudioModulation void FModulationPatchProxy::Update() { - Value = DefaultInputValue; + Value = DefaultValue; float& OutSampleHold = OutputProxy.SampleAndHoldValue; if (!OutputProxy.bInitialized) { - OutSampleHold = DefaultInputValue; + OutSampleHold = DefaultValue; + OutputProxy.bInitialized = true; } for (const FModulationInputProxy& Input : InputProxies) @@ -108,15 +108,6 @@ namespace AudioModulation } } - if (!OutputProxy.bInitialized) - { - const float OutputMin = OutputProxy.Transform.OutputMin; - const float OutputMax = OutputProxy.Transform.OutputMax; - OutSampleHold = FMath::Clamp(OutSampleHold, OutputMin, OutputMax); - OutputProxy.bInitialized = true; - } - - OutputProxy.Transform.Apply(Value); OutputProxy.MixFunction(&Value, &OutSampleHold, 1); } diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationPatchProxy.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationPatchProxy.h index 58bd920798d5..0894afb84422 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationPatchProxy.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationPatchProxy.h @@ -5,7 +5,7 @@ #include "SoundControlBusProxy.h" #include "SoundModulationParameter.h" #include "SoundModulationProxy.h" -#include "SoundModulatorLFOProxy.h" +#include "SoundModulationGeneratorLFOProxy.h" #include "Templates/Function.h" @@ -20,10 +20,10 @@ namespace AudioModulation struct FModulationInputSettings { const FControlBusSettings BusSettings; - const FSoundModulationInputTransform Transform; + const FSoundModulationTransform Transform; const uint8 bSampleAndHold : 1; - FModulationInputSettings(const FSoundModulationInputBase& InInput) + FModulationInputSettings(const FSoundControlModulationInput& InInput) : BusSettings(FControlBusSettings(InInput.GetBusChecked())) , Transform(InInput.Transform) , bSampleAndHold(InInput.bSampleAndHold) @@ -40,7 +40,7 @@ namespace AudioModulation FBusHandle BusHandle; - FSoundModulationInputTransform Transform; + FSoundModulationTransform Transform; bool bSampleAndHold = false; }; @@ -48,7 +48,7 @@ namespace AudioModulation struct FModulationOutputProxy { FModulationOutputProxy() = default; - FModulationOutputProxy(FSoundModulationOutputTransform InTransform, float InDefaultValue, const Audio::FModulationMixFunction& InMixFunction); + FModulationOutputProxy(float InDefaultValue, const Audio::FModulationMixFunction& InMixFunction); /** Whether patch has been initialized or not */ bool bInitialized = false; @@ -61,22 +61,15 @@ namespace AudioModulation /** Default value if no inputs are provided */ float DefaultValue = 1.0f; - - /** Final transform before passing to output */ - FSoundModulationOutputTransform Transform; }; struct FModulationPatchSettings : public TModulatorBase { - float DefaultInputValue = 1.0f; float DefaultOutputValue = 1.0f; TArray InputSettings; bool bBypass = true; - /** Final transform before passing to output */ - FSoundModulationOutputTransform Transform; - /** Function used to mix patch inputs together */ Audio::FModulationMixFunction MixFunction; @@ -84,17 +77,11 @@ namespace AudioModulation FModulationPatchSettings(const FSoundControlModulationPatch& InPatch) : bBypass(InPatch.bBypass) - , Transform(InPatch.Transform) { - if (InPatch.InputParameter) - { - DefaultInputValue = InPatch.InputParameter->Settings.ValueLinear; - MixFunction = InPatch.InputParameter->GetMixFunction(); - } - if (InPatch.OutputParameter) { DefaultOutputValue = InPatch.OutputParameter->Settings.ValueLinear; + MixFunction = InPatch.OutputParameter->GetMixFunction(); } for (const FSoundControlModulationInput& Input : InPatch.Inputs) @@ -106,38 +93,15 @@ namespace AudioModulation } } - FModulationPatchSettings(const FSoundModulationPatchBase& InPatch) - : DefaultInputValue(InPatch.GetDefaultInputValue()) - , bBypass(InPatch.bBypass) - , Transform(InPatch.GetOutputChecked().Transform) - , MixFunction(InPatch.GetMixFunction()) - { - TArray Inputs = InPatch.GetInputs(); - for (const FSoundModulationInputBase* Input : Inputs) - { - if (Input && Input->GetBus()) - { - InputSettings.Emplace(*Input); - } - } - } - FModulationPatchSettings(const USoundModulationPatch& InPatch) : TModulatorBase(InPatch.GetName(), InPatch.GetUniqueID()) , bBypass(InPatch.PatchSettings.bBypass) - , Transform(InPatch.PatchSettings.Transform) { - if (USoundModulationParameter* Parameter = InPatch.PatchSettings.InputParameter) + if (USoundModulationParameter* Parameter = InPatch.PatchSettings.OutputParameter) { MixFunction = Parameter->GetMixFunction(); } - DefaultInputValue = 1.0f; - if (USoundModulationParameter* Parameter = InPatch.PatchSettings.InputParameter) - { - DefaultInputValue = Parameter->Settings.ValueLinear; - } - for (const FSoundControlModulationInput& Input : InPatch.PatchSettings.Inputs) { if (Input.GetBus()) @@ -167,8 +131,8 @@ namespace AudioModulation void Init(const FModulationPatchSettings& InSettings, FAudioModulationSystem& InModSystem); private: - /** Default value of patch (Value mixed when inputs are provided or not, regardless of active state)*/ - float DefaultInputValue = 1.0f; + /** Default value of patch */ + float DefaultValue = 1.0f; /** Current value of the patch */ float Value = 1.0f; diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationTransform.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationTransform.cpp index fb263c0bf947..d10c1a870390 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationTransform.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationTransform.cpp @@ -4,104 +4,75 @@ #include "Audio.h" #include "DSP/Dsp.h" -FSoundModulationInputTransform::FSoundModulationInputTransform() - : InputMin(0.0f) - , InputMax(1.0f) - , OutputMin(0.0f) - , OutputMax(1.0f) -{ -} -void FSoundModulationInputTransform::Apply(float& Value) const -{ - const float Denom = FMath::Max(FMath::Abs(InputMax - InputMin), SMALL_NUMBER); - const float Alpha = FMath::Clamp(FMath::Abs(Value - InputMin) / Denom, 0.0f, 1.0f); - Value = FMath::Lerp(OutputMin, OutputMax, Alpha); - Value = FMath::Clamp(Value, OutputMin, OutputMax); -} - -void FSoundModulationOutputTransform::Apply(float& Value) const +void FSoundModulationTransform::Apply(float& OutValue) const { // Clamp the input - Value = FMath::Clamp(Value, InputMin, InputMax); - - EvaluateCurve(Value); - - // Clamp the output - Value = FMath::Clamp(Value, OutputMin, OutputMax); -} - -void FSoundModulationOutputTransform::EvaluateCurve(float& Value) const -{ - // If custom curve, evaluate curve and return before calculating alpha - if (Curve == ESoundModulatorOutputCurve::Custom) - { - Value = CurveCustom.Eval(Value); - return; - } - - // If shared curve, evaluate curve and return before calculating alpha - if (Curve == ESoundModulatorOutputCurve::Shared) - { - if (CurveShared) - { - Value = CurveShared->FloatCurve.Eval(Value); - } - return; - } - - // Avoid divide-by-zero - const float Denom = FMath::Max(FMath::Abs(InputMax - InputMin), SMALL_NUMBER); - const float Alpha = FMath::Clamp(FMath::Abs(Value - InputMin) / Denom, 0.0f, 1.0f); + OutValue = FMath::Clamp(OutValue, 0.0f, 1.0f); switch (Curve) { - case ESoundModulatorOutputCurve::Linear: + case ESoundModulatorCurve::Custom: { - Value = Alpha; + OutValue = CurveCustom.Eval(OutValue); + break; + } + + case ESoundModulatorCurve::Shared: + { + if (CurveShared) + { + OutValue = CurveShared->FloatCurve.Eval(OutValue); + } + break; + } + + case ESoundModulatorCurve::Linear: + { + // Do nothing, just linearly map output to incoming value } break; - case ESoundModulatorOutputCurve::Exp: + case ESoundModulatorCurve::Exp: { // Alpha is limited to between 0.0f and 1.0f and ExponentialScalar - // between 0 and 10 to keep values "sane" and avoid float boundary. - Value = Alpha * (FMath::Pow(10.0f, Scalar * (Alpha - 1.0f))); + // between 0 and 10 to keep OutValues "sane" and avoid float boundary. + OutValue *= (FMath::Pow(10.0f, Scalar * (OutValue - 1.0f))); } break; - case ESoundModulatorOutputCurve::Exp_Inverse: + case ESoundModulatorCurve::Exp_Inverse: { // Alpha is limited to between 0.0f and 1.0f and ExponentialScalar - // between 0 and 10 to keep values "sane" and avoid float boundary. - Value = ((Alpha - 1.0f) * FMath::Pow(10.0f, -1.0f * Scalar * Alpha)) + 1.0f; + // between 0 and 10 to keep OutValues "sane" and avoid float boundary. + OutValue = ((OutValue - 1.0f) * FMath::Pow(10.0f, -1.0f * Scalar * OutValue)) + 1.0f; } break; - case ESoundModulatorOutputCurve::Log: + case ESoundModulatorCurve::Log: { - Value = (Scalar * FMath::LogX(10.0f, Alpha)) + 1.0f; + OutValue = (Scalar * FMath::LogX(10.0f, OutValue)) + 1.0f; } break; - case ESoundModulatorOutputCurve::Sin: + case ESoundModulatorCurve::Sin: { - Value = Audio::FastSin(HALF_PI * Alpha); + OutValue = Audio::FastSin(HALF_PI * OutValue); } break; - case ESoundModulatorOutputCurve::SCurve: + case ESoundModulatorCurve::SCurve: { - Value = 0.5f * Audio::FastSin(((PI * Alpha) - HALF_PI)) + 0.5f; + OutValue = 0.5f * Audio::FastSin(((PI * OutValue) - HALF_PI)) + 0.5f; } break; default: { - static_assert(static_cast(ESoundModulatorOutputCurve::Count) == 8, "Possible missing case coverage for output curve."); + static_assert(static_cast(ESoundModulatorCurve::Count) == 8, "Possible missing case coverage for output curve."); } break; } - Value = FMath::Lerp(OutputMin, OutputMax, Value); + OutValue = FMath::Clamp(OutValue, 0.0f, 1.0f); } diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationValue.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationValue.cpp index 54981c1616e7..38c1d1d798c8 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationValue.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Private/SoundModulationValue.cpp @@ -4,62 +4,121 @@ #include "SoundModulationProxy.h" -FSoundModulationValue::FSoundModulationValue(float InValue, float InAttackTime, float InReleaseTime) +FSoundModulationMixValue::FSoundModulationMixValue(float InValue, float InAttackTime, float InReleaseTime) : TargetValue(InValue) , AttackTime(InAttackTime) , ReleaseTime(InReleaseTime) + , LerpTime(InAttackTime) , Value(InValue) + , ActiveFade(EActiveFade::Attack) { } -void FSoundModulationValue::SetCurrentValue(float InValue) +void FSoundModulationMixValue::SetActiveFade(EActiveFade InActiveFade, float InFadeTime) +{ + if (ActiveFade == EActiveFade::Release || InActiveFade == ActiveFade) + { + return; + } + + ActiveFade = InActiveFade; + switch (ActiveFade) + { + case EActiveFade::Attack: + { + if (InFadeTime > 0.0f) + { + AttackTime = InFadeTime; + } + LerpTime = AttackTime; + } + break; + + case EActiveFade::Release: + { + if (InFadeTime > 0.0f) + { + ReleaseTime = InFadeTime; + } + LerpTime = ReleaseTime; + } + break; + + case EActiveFade::Override: + default: + { + if (InFadeTime > 0.0f) + { + LerpTime = InFadeTime; + } + // If fade was not set prior, use attack time as default. + else if (LerpTime < 0.0f) + { + LerpTime = AttackTime; + } + break; + } + } + + UpdateDelta(); +} + +void FSoundModulationMixValue::SetCurrentValue(float InValue) { Value = InValue; } -float FSoundModulationValue::GetCurrentValue() const +float FSoundModulationMixValue::GetCurrentValue() const { - // Current does not require update to be called if - // value is attacking or releasing and time is 0 - if (AttackTime <= 0.0f && Value < TargetValue) + if (LerpTime > 0.0f) { - return TargetValue; + return Value; } - if (ReleaseTime <= 0.0f && Value > TargetValue) - { - return TargetValue; - } - - return Value; + // Returning target when lerp is set to non-positive value + // ensures that an update call isn't required to get the + // current value in the same frame. + return TargetValue; } -void FSoundModulationValue::Update(double InElapsed) +void FSoundModulationMixValue::Update(double InElapsed) { - // Attacking + if (!FMath::IsNearlyEqual(LastTarget, TargetValue)) + { + UpdateDelta(); + } + if (Value < TargetValue) { - if (AttackTime > 0.0f) - { - Value = Value + static_cast(InElapsed / AttackTime); - Value = FMath::Min(Value, TargetValue); - } - else - { - Value = TargetValue; - } + Value = static_cast(Value + (Delta * InElapsed)); + Value = FMath::Min(Value, TargetValue); } - // Releasing else if (Value > TargetValue) { - if (ReleaseTime > 0.0f) - { - Value = Value - static_cast(InElapsed / ReleaseTime); - Value = FMath::Max(Value, TargetValue); - } - else - { - Value = TargetValue; - } + Value = static_cast(Value - (Delta * InElapsed)); + Value = FMath::Max(Value, TargetValue); } + +} + +void FSoundModulationMixValue::UpdateDelta() +{ + // Initialize to attack time if unset + if (LerpTime < 0.0f) + { + check(ActiveFade == EActiveFade::Attack); + LerpTime = AttackTime; + } + + if (LerpTime > 0.0f) + { + Delta = FMath::Abs(Value - TargetValue) / LerpTime; + } + else + { + Delta = 0.0f; + Value = TargetValue; + } + + LastTarget = TargetValue; } diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/AudioModulation.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/AudioModulation.h index f0c5b3f6aa6e..3816dca131dc 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/AudioModulation.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/AudioModulation.h @@ -31,6 +31,8 @@ namespace AudioModulation virtual Audio::FModulationParameter GetParameter(FName InParamName); virtual void Initialize(const FAudioPluginInitializationParams& InitializationParams) override; + virtual void OnAuditionEnd() override; + #if !UE_BUILD_SHIPPING virtual bool OnPostHelp(FCommonViewportClient* ViewportClient, const TCHAR* Stream) override; virtual int32 OnRenderStat(FViewport* Viewport, FCanvas* Canvas, int32 X, int32 Y, const UFont& Font, const FVector* ViewLocation, const FRotator* ViewRotation) override; diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/AudioModulationStatics.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/AudioModulationStatics.h index 832b76d2c90f..13037a7e6cff 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/AudioModulationStatics.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/AudioModulationStatics.h @@ -41,7 +41,7 @@ public: WorldContext = "WorldContextObject", Keywords = "activate modulation modulator control bus") ) - static void ActivateBus(const UObject* WorldContextObject, USoundControlBusBase* Bus); + static void ActivateBus(const UObject* WorldContextObject, USoundControlBus* Bus); /** Activates a bus modulator mix. Does nothing if an instance of the provided bus mix is already active * @param BusMix - Mix to activate @@ -59,7 +59,7 @@ public: WorldContext = "WorldContextObject", Keywords = "activate modulation modulator lfo") ) - static void ActivateBusModulator(const UObject* WorldContextObject, USoundBusModulatorBase* Modulator); + static void ActivateBusModulator(const UObject* WorldContextObject, USoundModulationGenerator* Modulator); /** Creates a modulation bus with the provided default value. * @param Name - Name of bus @@ -85,7 +85,7 @@ public: WorldContext = "WorldContextObject", Keywords = "make create lfo modulation modulator") ) - static USoundBusModulatorLFO* CreateLFO( + static USoundModulationGeneratorLFO* CreateLFO( const UObject* WorldContextObject, FName Name, float Amplitude, @@ -94,26 +94,26 @@ public: bool Activate = true ); - /** Creates a channel used to mix a control bus. - * @param Bus - Bus channel is in charge of applying mix value to. - * @param Channels - Value for added bus channel to target when mix is active. - * @param Attack/ReleaseTime - Time in seconds for channel to mix in/out. + /** Creates a stage used to mix a control bus. + * @param Bus - Bus stage is in charge of applying mix value to. + * @param Stages - Value for added bus stage to target when mix is active. + * @param Attack/ReleaseTime - Time in seconds for stage to mix in/out. */ - UFUNCTION(BlueprintCallable, Category = "Audio", DisplayName = "Create Control Bus Mix Channel", meta = ( + UFUNCTION(BlueprintCallable, Category = "Audio", DisplayName = "Create Control Bus Mix Stage", meta = ( AdvancedDisplay = "3", WorldContext = "WorldContextObject", - Keywords = "make create control bus mix modulation modulator channel") + Keywords = "make create control bus mix modulation modulator stage") ) - static FSoundControlBusMixChannel CreateBusMixChannel( + static FSoundControlBusMixStage CreateBusMixStage( const UObject* WorldContextObject, - USoundControlBusBase* Bus, + USoundControlBus* Bus, float Value, float AttackTime = 0.1f, float ReleaseTime = 0.1f); - /** Creates a modulation bus mix and adds a bus channel set to the provided target value + /** Creates a modulation bus mix and adds a bus stage set to the provided target value * @param Name - Name of mix. - * @param Channels - Channels mix is responsible for. + * @param Stages - Stages mix is responsible for. * @param Activate - Whether or not to activate mix on creation. */ UFUNCTION(BlueprintCallable, Category = "Audio", DisplayName = "Create Control Bus Mix", meta = ( @@ -123,7 +123,7 @@ public: static USoundControlBusMix* CreateBusMix( const UObject* WorldContextObject, FName Name, - TArray Channels, + TArray Stages, bool Activate); /** Deactivates a bus. Does nothing if an instance of the provided bus is already inactive @@ -133,7 +133,7 @@ public: WorldContext = "WorldContextObject", Keywords = "deactivate modulation modulator bus") ) - static void DeactivateBus(const UObject* WorldContextObject, USoundControlBusBase* Bus); + static void DeactivateBus(const UObject* WorldContextObject, USoundControlBus* Bus); /** Deactivates a modulation bus mix. Does nothing if an instance of the provided bus mix is already inactive * @param BusMix - Mix to deactivate @@ -152,7 +152,7 @@ public: WorldContext = "WorldContextObject", Keywords = "deactivate bus modulation modulator") ) - static void DeactivateBusModulator(const UObject* WorldContextObject, USoundBusModulatorBase* Modulator); + static void DeactivateBusModulator(const UObject* WorldContextObject, USoundModulationGenerator* Modulator); /** Saves control bus mix to a profile, serialized to an ini file. If mix is loaded, uses current proxy's state. * If not, uses default UObject representation. @@ -170,60 +170,65 @@ public: * @param BusMix - Mix object to deserialize profile .ini to. * @param bActivate - If true, activate mix upon loading from profile. * @param ProfileIndex - Index of profile, allowing multiple profiles to be loaded to single mix object. If <= 0, loads from default profile (no suffix). - * @return Channels - Channel values loaded from profile (empty if profile did not exist or had no values serialized). + * @return Stages - Stage values loaded from profile (empty if profile did not exist or had no values serialized). */ UFUNCTION(BlueprintCallable, Category = "Audio", DisplayName = "Load Control Bus Mix From Profile", meta = ( WorldContext = "WorldContextObject", AdvancedDisplay = "2", Keywords = "load deserialize control bus modulation mix modulator ini") ) - static UPARAM(DisplayName = "Channels") TArray LoadMixFromProfile(const UObject* WorldContextObject, USoundControlBusMix* BusMix, bool bActivate = true, int32 ProfileIndex = 0); + static UPARAM(DisplayName = "Stages") TArray LoadMixFromProfile(const UObject* WorldContextObject, USoundControlBusMix* BusMix, bool bActivate = true, int32 ProfileIndex = 0); - /** Sets a mix with the provided channel data if channels provided in active instance proxy of mix. Does not update UObject definition of mix. + /** Sets a mix with the provided stage data if stages provided in active instance proxy of mix. Does not update UObject definition of mix. * @param Mix - Mix to update - * @param Channels - Channels to set. If channel's bus is not referenced by mix, channel's update request is ignored. - * @param bUpdateObject - If true, will dirty mix object and update channels on the SoundControlBusMix object in addition - * to updating the audio thread proxy. + * @param Stages - Stages to set. If stage's bus is not referenced by mix, stage's update request is ignored. + * @param FadeTime - Fade time to user when interpolating between current value and new values. + * If negative, falls back to last fade time set on stage. If fade time never set on stage, + * uses attack time set on stage in mix asset. */ UFUNCTION(BlueprintCallable, Category = "Audio", DisplayName = "Set Control Bus Mix", meta = ( WorldContext = "WorldContextObject", - Keywords = "set bus control modulation modulator mix channel") + Keywords = "set bus control modulation modulator mix stage") ) - static void UpdateMix(const UObject* WorldContextObject, USoundControlBusMix* Mix, TArray Channels); + static void UpdateMix(const UObject* WorldContextObject, USoundControlBusMix* Mix, TArray Stages, float InFadeTime = -1.0f); - /** Sets filtered channels of a given class to a provided target value for active instance of mix. Does not update UObject definition of mix. + /** Sets filtered stages of a given class to a provided target value for active instance of mix. Does not update UObject definition of mix. * @param Mix - Mix to modify - * @param AddressFilter - Address filter to apply to provided mix's channels. + * @param AddressFilter - Address filter to apply to provided mix's stages. * @param BusClass - Filters buses by subclass. - * @param Value - Target value to mix filtered channels to. - * @param AttackTime - If non-negative, updates the attack time for the resulting bus channels found matching the provided filter. - * @param ReleaseTime - If non-negative, updates the release time for the resulting bus channels found matching the provided filter. - * @param bUpdateObject - If true, will dirty mix object and update channels on the SoundControlBusMix object in addition to updating + * @param Value - Target value to mix filtered stages to. + * @param FadeTime - If non-negative, updates the fade time for the resulting bus stages found matching the provided filter. + * @param AttackTime - If non-negative, updates the attack time for the resulting bus stages found matching the provided filter. + * @param ReleaseTime - If non-negative, updates the release time for the resulting bus stages found matching the provided filter. + * @param bUpdateObject - If true, will dirty mix object and update stages on the SoundControlBusMix object in addition to updating * the audio thread proxy. */ UFUNCTION(BlueprintCallable, Category = "Audio", DisplayName = "Set Control Bus Mix By Filter", meta = ( - AdvancedDisplay = "5", + AdvancedDisplay = "6", WorldContext = "WorldContextObject", - Keywords = "set bus control class modulation modulator mix channel value filter") + Keywords = "set bus control class modulation modulator mix stage value filter") ) static void UpdateMixByFilter( - const UObject* WorldContextObject, - USoundControlBusMix* Mix, - FString AddressFilter, - TSubclassOf BusClassFilter, - float Value, - float AttackTime = -1.0f, - float ReleaseTime = -1.0f); + const UObject* WorldContextObject, + USoundControlBusMix* Mix, + FString AddressFilter, + TSubclassOf ParamClassFilter, + USoundModulationParameter* ParamFilter, + float Value = 1.0f, + float FadeTime = -1.0f); /** Commits updates from a UObject definition of a bus mix to active instance in audio thread * (ignored if mix has not been activated). * @param Mix - Mix to update + * @param FadeTime - Fade time to user when interpolating between current value and new values. + * If negative, falls back to last fade time set on stage. If fade time never set on stage, + * uses attack time set on stage in mix asset. */ UFUNCTION(BlueprintCallable, Category = "Audio", DisplayName = "Update Control Bus Mix", meta = ( WorldContext = "WorldContextObject", Keywords = "update set control bus mix modulation modulator") ) - static void UpdateMixFromObject(const UObject* WorldContextObject, USoundControlBusMix* Mix); + static void UpdateMixFromObject(const UObject* WorldContextObject, USoundControlBusMix* Mix, float FadeTime = -1.0f); /** Commits updates from a UObject definition of a modulator (e.g. Bus, Bus Mix, LFO) to active instance in audio thread * (ignored if modulator type has not been activated). diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/AudioModulationStyle.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/AudioModulationStyle.h index 9a3fc76c6888..b84fccd63dc8 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/AudioModulationStyle.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/AudioModulationStyle.h @@ -14,7 +14,7 @@ public: GENERATED_BODY() UFUNCTION(BlueprintCallable, Category = "Audio|Modulation|Style") - static const FColor GetBusModulatorColor() { return FColor(204, 51, 153); } + static const FColor GetModulationGeneratorColor() { return FColor(204, 51, 153); } UFUNCTION(BlueprintCallable, Category = "Audio|Modulation|Style") static const FColor GetControlBusColor() { return FColor(255, 51, 153); } diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundControlBus.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundControlBus.h index 8dce63dfabb6..0eedd9fb91f7 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundControlBus.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundControlBus.h @@ -7,7 +7,7 @@ #include "CoreMinimal.h" #include "IAudioModulation.h" #include "SoundModulationParameter.h" -#include "SoundModulatorLFO.h" +#include "SoundModulationGeneratorLFO.h" #include "UObject/Object.h" #include "UObject/ObjectMacros.h" @@ -20,8 +20,8 @@ class USoundModulatorBase; struct FPropertyChangedEvent; -UCLASS(BlueprintType, hidecategories = Object, abstract, MinimalAPI) -class USoundControlBusBase : public USoundModulatorBase +UCLASS(BlueprintType, hidecategories = Object, editinlinenew, MinimalAPI) +class USoundControlBus : public USoundModulatorBase { GENERATED_UCLASS_BODY() @@ -40,8 +40,11 @@ public: UPROPERTY(EditAnywhere, Category = Mix, BlueprintReadWrite, meta = (EditCondition = "bOverrideAddress")) FString Address; - UPROPERTY(EditAnywhere, Category = Modulation, BlueprintReadWrite) - TArray Modulators; + UPROPERTY(EditAnywhere, Category = Generators, BlueprintReadWrite, meta = (DisplayName = "Generators")) + TArray Modulators; + + UPROPERTY(EditAnywhere, Category = General, BlueprintReadOnly) + USoundModulationParameter* Parameter; #if WITH_EDITOR virtual void PostDuplicate(EDuplicateMode::Type DuplicateMode) override; @@ -52,23 +55,8 @@ public: virtual void BeginDestroy() override; virtual const Audio::FModulationMixFunction& GetMixFunction() const; - virtual float GetDefaultValue() const { return 1.0f; } - virtual float GetMin() const { return 0.0f; } - virtual float GetMax() const { return 1.0f; } -}; -UCLASS(BlueprintType, hidecategories = Object, editinlinenew, MinimalAPI) -class USoundControlBus : public USoundControlBusBase -{ - GENERATED_UCLASS_BODY() - -public: - UPROPERTY(EditAnywhere, Category = General, BlueprintReadOnly) - USoundModulationParameter* Parameter; - - virtual float GetDefaultValue() const { return Parameter ? Parameter->ConvertLinearToUnit(Parameter->Settings.ValueLinear) : 1.0f; } - virtual float GetMin() const override { return 0.0f; } - virtual float GetMax() const override { return 1.0f; } + virtual float GetDefaultLinearValue() const { return Parameter ? Parameter->Settings.ValueLinear : 1.0f; } virtual FName GetOutputParameterName() const override { @@ -78,8 +66,4 @@ public: } return Super::GetOutputParameterName(); } - -#if WITH_EDITOR - virtual void PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent) override; -#endif // WITH_EDITOR }; diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundControlBusMix.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundControlBusMix.h index 8f06ee241525..16e5891ab6c2 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundControlBusMix.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundControlBusMix.h @@ -10,35 +10,33 @@ #include "SoundControlBusMix.generated.h" - // Forward Declarations -class USoundControlBusBase; +class USoundControlBus; USTRUCT(BlueprintType) -struct FSoundControlBusMixChannel +struct FSoundControlBusMixStage { GENERATED_USTRUCT_BODY() - FSoundControlBusMixChannel(); - FSoundControlBusMixChannel(USoundControlBusBase* InBus, const float TargetValue); + FSoundControlBusMixStage(); + FSoundControlBusMixStage(USoundControlBus* InBus, const float TargetValue); - /* Bus controlled by channel. */ - UPROPERTY(EditAnywhere, Category = Channel, BlueprintReadWrite) - USoundControlBusBase* Bus; + /* Bus controlled by stage. */ + UPROPERTY(EditAnywhere, Category = Stage, BlueprintReadWrite) + USoundControlBus* Bus; /* Value mix is set to. */ - UPROPERTY(EditAnywhere, Category = Channel, BlueprintReadWrite) - FSoundModulationValue Value; + UPROPERTY(EditAnywhere, Category = Stage, BlueprintReadWrite) + FSoundModulationMixValue Value; }; -UCLASS(config = Engine, autoexpandcategories = (Channel, Mix), editinlinenew, BlueprintType, MinimalAPI) +UCLASS(config = Engine, autoexpandcategories = (Stage, Mix), editinlinenew, BlueprintType, MinimalAPI) class USoundControlBusMix : public UObject { GENERATED_UCLASS_BODY() protected: -#if WITH_EDITOR // Loads the mix from the provided profile index UFUNCTION(Category = Mix, meta = (CallInEditor = "true")) void LoadMixFromProfile(); @@ -47,30 +45,27 @@ protected: UFUNCTION(Category = Mix, meta = (CallInEditor = "true")) void SaveMixToProfile(); - // Solos this mix, deactivating all others and activating this + // Solos this mix, deactivating all others and activating this // (if its not already active) while testing in-editor in all // active worlds UFUNCTION(Category = Mix, meta = (CallInEditor = "true")) void SoloMix(); - // Deactivates this mix while testing in-editor in all active worlds + // Deactivates this mix in all active worlds UFUNCTION(Category = Mix, meta = (CallInEditor = "true")) void ActivateMix(); - // Deactivates this mix while testing in-editor in all active worlds + // Deactivates this mix in all active worlds UFUNCTION(Category = Mix, meta = (CallInEditor = "true")) void DeactivateMix(); - // Deactivates all mixes while testing in-editor in all active worlds + // Deactivates all mixes in all active worlds UFUNCTION(Category = Mix, meta = (CallInEditor = "true")) void DeactivateAllMixes(); -#endif // WITH_EDITOR public: -#if WITH_EDITORONLY_DATA UPROPERTY(EditAnywhere, Transient, Category = Mix) uint32 ProfileIndex; -#endif // WITH_EDITORONLY_DATA virtual void BeginDestroy() override; @@ -78,10 +73,9 @@ public: virtual void PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent) override; virtual void PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedEvent) override; virtual void OnPropertyChanged(FProperty* Property, EPropertyChangeType::Type ChangeType); - #endif // WITH_EDITOR - /* Array of channels controlled by mix. */ + /* Array of stages controlled by mix. */ UPROPERTY(EditAnywhere, Category = Mix, BlueprintReadOnly) - TArray Channels; + TArray MixStages; }; diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationGenerator.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationGenerator.h new file mode 100644 index 000000000000..aff35845eb9f --- /dev/null +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationGenerator.h @@ -0,0 +1,26 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "DSP/LFO.h" +#include "IAudioModulation.h" +#include "SoundModulationValue.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" + +#include "SoundModulationGenerator.generated.h" + + +/** + * Base class for modulators that algoithmically generate values that can effect + * various endpoints (ex. Control Buses & Parameter Destinations) + */ +UCLASS(hideCategories = Object, abstract, MinimalAPI) +class USoundModulationGenerator : public USoundModulatorBase +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent) override; +#endif // WITH_EDITOR +}; diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulatorLFO.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationGeneratorLFO.h similarity index 75% rename from Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulatorLFO.h rename to Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationGeneratorLFO.h index 98ce57334e3b..66ffab5f38ce 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulatorLFO.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationGeneratorLFO.h @@ -4,15 +4,15 @@ #include "CoreMinimal.h" #include "DSP/LFO.h" #include "IAudioModulation.h" -#include "SoundModulationValue.h" +#include "SoundModulationGenerator.h" #include "UObject/ObjectMacros.h" #include "UObject/Object.h" -#include "SoundModulatorLFO.generated.h" +#include "SoundModulationGeneratorLFO.generated.h" UENUM(BlueprintType) -enum class ESoundModulatorLFOShape : uint8 +enum class ESoundModulationGeneratorLFOShape : uint8 { Sine UMETA(DisplayName = "Sine"), UpSaw UMETA(DisplayName = "Saw (Up)"), @@ -25,21 +25,8 @@ enum class ESoundModulatorLFOShape : uint8 COUNT UMETA(Hidden) }; -/** - * Base class for modulators that manipulate control bus values - */ -UCLASS(hideCategories = Object, abstract, MinimalAPI) -class USoundBusModulatorBase : public USoundModulatorBase -{ - GENERATED_UCLASS_BODY() - -#if WITH_EDITOR - virtual void PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent) override; -#endif // WITH_EDITOR -}; - UCLASS(BlueprintType, hidecategories = Object, editinlinenew, MinimalAPI) -class USoundBusModulatorLFO : public USoundBusModulatorBase +class USoundModulationGeneratorLFO : public USoundModulationGenerator { GENERATED_UCLASS_BODY() @@ -48,7 +35,7 @@ public: /** Shape of oscillating waveform */ UPROPERTY(EditAnywhere, Category = Modulation, BlueprintReadWrite) - ESoundModulatorLFOShape Shape; + ESoundModulationGeneratorLFOShape Shape; /** Amplitude of oscillator */ UPROPERTY(EditAnywhere, Category = Modulation, BlueprintReadWrite, meta = (UIMin = "0", UIMax = "1", ClampMin = "0", ClampMax = "1")) diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationPatch.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationPatch.h index 8fc286176b3d..cd0d76909221 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationPatch.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationPatch.h @@ -10,104 +10,31 @@ #include "SoundModulationPatch.generated.h" -class USoundControlBusBase; +// Forward Declarations class USoundControlBus; USTRUCT(BlueprintType) -struct AUDIOMODULATION_API FSoundModulationOutputBase +struct AUDIOMODULATION_API FSoundControlModulationInput { GENERATED_USTRUCT_BODY() - /** Final transform before passing to output */ - UPROPERTY(EditAnywhere, Category = Modulation, BlueprintReadWrite, meta = (ShowOnlyInnerProperties)) - FSoundModulationOutputTransform Transform; - - virtual ~FSoundModulationOutputBase() = default; -}; - -USTRUCT(BlueprintType) -struct AUDIOMODULATION_API FSoundModulationOutputFixedOperator : public FSoundModulationOutputBase -{ - GENERATED_USTRUCT_BODY() -}; - -USTRUCT(BlueprintType) -struct AUDIOMODULATION_API FSoundModulationInputBase -{ - GENERATED_USTRUCT_BODY() - - FSoundModulationInputBase(); - virtual ~FSoundModulationInputBase() = default; + FSoundControlModulationInput(); /** Get the modulated input value on parent patch initialization and hold that value for its lifetime */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Input, meta = (DisplayName = "Sample-And-Hold")) uint8 bSampleAndHold : 1; /** Transform to apply to the input prior to mix phase */ - UPROPERTY() - FSoundModulationInputTransform Transform; - - virtual const USoundControlBusBase* GetBus() const PURE_VIRTUAL(FSoundModulationInputBase::GetBus, return nullptr; ); - - const USoundControlBusBase& GetBusChecked() const - { - const USoundControlBusBase* Bus = GetBus(); - check(Bus); - return *GetBus(); - } -}; - -USTRUCT(BlueprintType) -struct AUDIOMODULATION_API FSoundControlModulationInput : public FSoundModulationInputBase -{ - GENERATED_USTRUCT_BODY() + UPROPERTY(EditAnywhere, Category = Input) + FSoundModulationTransform Transform; /** The input bus */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Input) USoundControlBus* Bus = nullptr; - virtual const USoundControlBusBase* GetBus() const override; -}; - -USTRUCT(BlueprintType) -struct AUDIOMODULATION_API FSoundModulationPatchBase -{ - GENERATED_USTRUCT_BODY() - - FSoundModulationPatchBase(); - - virtual ~FSoundModulationPatchBase() = default; - - /** Whether or not patch is bypassed */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Inputs) - uint8 bBypass : 1; - - virtual float GetDefaultInputValue() const { return 1.0f; } - virtual const Audio::FModulationMixFunction& GetMixFunction() const { return Audio::FModulationParameter::GetDefaultMixFunction(); } - virtual TArray GetInputs() const PURE_VIRTUAL(FSoundModulationPatchBase::GetInputs(), return TArray();); - virtual FSoundModulationOutputBase* GetOutput() PURE_VIRTUAL(FSoundModulationPatchBase::GetOutput(), return nullptr;); - virtual const FSoundModulationOutputBase* GetOutput() const PURE_VIRTUAL(FSoundModulationPatchBase::GetOutput(), return nullptr;); - - FSoundModulationOutputBase& GetOutputChecked() - { - FSoundModulationOutputBase* Output = GetOutput(); - check(Output); - - return *Output; - } - - const FSoundModulationOutputBase& GetOutputChecked() const - { - const FSoundModulationOutputBase* Output = GetOutput(); - check(Output); - - return *Output; - } - -#if WITH_EDITOR - virtual void Clamp(); -#endif // WITH_EDITOR + const USoundControlBus* GetBus() const; + const USoundControlBus& GetBusChecked() const; }; USTRUCT(BlueprintType) @@ -119,20 +46,13 @@ struct AUDIOMODULATION_API FSoundControlModulationPatch UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Inputs) bool bBypass = true; - /** Input parameter of patch */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Inputs, meta = (DisplayName = "Input Parameter")) - USoundModulationParameter* InputParameter = nullptr; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Output, meta = (DisplayName = "Parameter")) + USoundModulationParameter* OutputParameter = nullptr; /** Modulation inputs */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Inputs) TArray Inputs; - UPROPERTY(EditAnywhere, Category = Output) - USoundModulationParameter* OutputParameter = nullptr; - - /** Final transform before passing to output */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Output) - FSoundModulationOutputTransform Transform; }; UCLASS(config = Engine, editinlinenew, BlueprintType) diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationTransform.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationTransform.h index 406fb357e36b..6f3c0608c723 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationTransform.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationTransform.h @@ -9,35 +9,8 @@ #include "SoundModulationTransform.generated.h" -USTRUCT(BlueprintType) -struct FSoundModulationInputTransform -{ - GENERATED_USTRUCT_BODY() - - FSoundModulationInputTransform(); - - /** Minimum value to clamp the input to prior to transforming via linear interpolation. */ - UPROPERTY(EditAnywhere, Category = Modulation, BlueprintReadWrite, meta = (DisplayName = "Input Min", UIMin = "0", UIMax = "1")) - float InputMin; - - /** Maximum value to clamp the input to prior to transforming via linear interpolation. */ - UPROPERTY(EditAnywhere, Category = Modulation, BlueprintReadWrite, meta = (DisplayName = "Input Max", UIMin = "0", UIMax = "1")) - float InputMax; - - /** Minimum value to scale the output to. */ - UPROPERTY(EditAnywhere, Category = Modulation, BlueprintReadWrite, meta = (DisplayName = "Output Min", UIMin = "0", UIMax = "1")) - float OutputMin; - - /** Maximum value to scale the output to. */ - UPROPERTY(EditAnywhere, Category = Modulation, BlueprintReadWrite, meta = (DisplayName = "Output Max", UIMin = "0", UIMax = "1")) - float OutputMax; - - /** Applies transform to provided value */ - void Apply(float& OutValue) const; -}; - UENUM() -enum class ESoundModulatorOutputCurve : uint8 +enum class ESoundModulatorCurve : uint8 { // Expressions Linear UMETA(DisplayName = "Linear"), @@ -57,21 +30,13 @@ enum class ESoundModulatorOutputCurve : uint8 }; USTRUCT(BlueprintType) -struct AUDIOMODULATION_API FSoundModulationOutputTransform +struct AUDIOMODULATION_API FSoundModulationTransform { GENERATED_USTRUCT_BODY() - /** Minimum value to clamp the input to. */ - UPROPERTY() - float InputMin = 0.0f; - - /** Maximum value to clamp the input to. */ - UPROPERTY() - float InputMax = 1.0f; - /** The curve to apply when transforming the output. */ - UPROPERTY(EditAnywhere, Category = Input, BlueprintReadWrite, meta = (DisplayName = "Transform Curve")) - ESoundModulatorOutputCurve Curve = ESoundModulatorOutputCurve::Linear; + UPROPERTY(EditAnywhere, Category = Input, BlueprintReadWrite, meta = (DisplayName = "Curve Type")) + ESoundModulatorCurve Curve = ESoundModulatorCurve::Linear; /** When curve set to log, exponential or exponential inverse, value is factor 'b' in following equations with output 'y' and input 'x': * Exponential: y = x * 10^-b(1-x) @@ -89,17 +54,6 @@ struct AUDIOMODULATION_API FSoundModulationOutputTransform UPROPERTY(EditAnywhere, meta = (DisplayName = "Asset"), Category = Curve, BlueprintReadWrite) UCurveFloat* CurveShared = nullptr; - /** Minimum value to clamp output to. */ - UPROPERTY() - float OutputMin = 0.0f; - - /** Maximum value to clamp output to. */ - UPROPERTY() - float OutputMax = 1.0f; - /** Applies transform to provided value */ void Apply(float& OutValue) const; - -private: - void EvaluateCurve(float& Value) const; }; diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationValue.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationValue.h index 8a4412b3aaef..98ce0c265df9 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationValue.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulation/Public/SoundModulationValue.h @@ -8,12 +8,24 @@ #include "SoundModulationValue.generated.h" USTRUCT(BlueprintType) -struct AUDIOMODULATION_API FSoundModulationValue +struct AUDIOMODULATION_API FSoundModulationMixValue { GENERATED_USTRUCT_BODY() - FSoundModulationValue() = default; - FSoundModulationValue(float InValue, float InAttackTime, float InReleaseTime); + FSoundModulationMixValue() = default; + FSoundModulationMixValue(float InValue, float InAttackTime, float InReleaseTime); + + enum class EActiveFade : uint8 + { + /** Value interpolating from the parameter's default value to the mix value. */ + Attack, + + /** Value interpolating from the mix value to the parameter's default value. */ + Release, + + /** User-requested fade time to an active mix by filter (ex. from Blueprint) or editor property adjustment */ + Override + }; /** Target value of the modulator. */ UPROPERTY(EditAnywhere, Category = Modulation, BlueprintReadWrite, meta = (DisplayName = "Value")) @@ -25,14 +37,16 @@ struct AUDIOMODULATION_API FSoundModulationValue float TargetUnitValue = 1.0f; #endif // WITH_EDITORONLY_DATA - /** Time it takes (in sec) to unitarily increase the bus value (from 0 to 1). */ + /** Time it takes (in sec) to interpolate from the parameter's default value to the mix value. */ UPROPERTY(EditAnywhere, Category = Modulation, BlueprintReadWrite, meta = (DisplayName = "Attack Time (sec)", ClampMin = "0.0", UIMin = "0.0")) float AttackTime = 0.1f; - /** Time it takes (in sec) to unitarily decrease the bus value (from 1 to 0). */ + /** Time it takes (in sec) to interpolate from the current mix value to the parameter's default value. */ UPROPERTY(EditAnywhere, Category = Modulation, BlueprintReadWrite, meta = (DisplayName = "Release Time (sec)", ClampMin = "0.0", UIMin = "0.0")) float ReleaseTime = 0.1f; + void SetActiveFade(EActiveFade InActiveFade, float InFadeTime = -1.0f); + /** Set current value (for resetting value state only as circumvents lerp, and may result in discontinuity). */ void SetCurrentValue(float InValue); @@ -42,5 +56,11 @@ struct AUDIOMODULATION_API FSoundModulationValue void Update(double Elapsed); private: + void UpdateDelta(); + + float LerpTime = -1.0f; float Value = 1.0f; + float LastTarget = -1.0f; + float Delta = 0.0f; + EActiveFade ActiveFade = EActiveFade::Attack; }; diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulationGeneratorLFO.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulationGeneratorLFO.cpp new file mode 100644 index 000000000000..9fecaf0b2aeb --- /dev/null +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulationGeneratorLFO.cpp @@ -0,0 +1,23 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#include "AssetTypeActions_SoundModulationGeneratorLFO.h" + +#include "SoundModulationGeneratorLFO.h" + + +#define LOCTEXT_NAMESPACE "AssetTypeActions" + +UClass* FAssetTypeActions_SoundModulationGeneratorLFO::GetSupportedClass() const +{ + return USoundModulationGeneratorLFO::StaticClass(); +} + +const TArray& FAssetTypeActions_SoundModulationGeneratorLFO::GetSubMenus() const +{ + static const TArray SubMenus + { + LOCTEXT("AssetSoundModulationSubMenu", "Modulation") + }; + + return SubMenus; +} +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulatorLFO.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulationGeneratorLFO.h similarity index 70% rename from Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulatorLFO.h rename to Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulationGeneratorLFO.h index 01297bd5452b..647a67145955 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulatorLFO.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulationGeneratorLFO.h @@ -6,12 +6,12 @@ #include "AssetTypeActions_Base.h" #include "AudioModulationStyle.h" -class FAssetTypeActions_SoundModulatorLFO : public FAssetTypeActions_Base +class FAssetTypeActions_SoundModulationGeneratorLFO : public FAssetTypeActions_Base { public: // IAssetTypeActions Implementation - virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_SoundModulatorLFO", "Control Modulator (LFO)"); } - virtual FColor GetTypeColor() const override { return UAudioModulationStyle::GetBusModulatorColor(); } + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_SoundModulationGeneratorLFO", "Modulation Generator (LFO)"); } + virtual FColor GetTypeColor() const override { return UAudioModulationStyle::GetModulationGeneratorColor(); } virtual const TArray& GetSubMenus() const override; virtual UClass* GetSupportedClass() const override; virtual uint32 GetCategories() override { return EAssetTypeCategories::Sounds; } diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulatorLFO.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulatorLFO.cpp deleted file mode 100644 index 1234572313a0..000000000000 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AssetTypeActions/AssetTypeActions_SoundModulatorLFO.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. -#include "AssetTypeActions_SoundModulatorLFO.h" - -#include "SoundModulatorLFO.h" - - -#define LOCTEXT_NAMESPACE "AssetTypeActions" - -UClass* FAssetTypeActions_SoundModulatorLFO::GetSupportedClass() const -{ - return USoundBusModulatorLFO::StaticClass(); -} - -const TArray& FAssetTypeActions_SoundModulatorLFO::GetSubMenus() const -{ - static const TArray SubMenus - { - LOCTEXT("AssetSoundModulationSubMenu", "Modulation") - }; - - return SubMenus; -} -#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AudioModulationEditor.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AudioModulationEditor.cpp index a914f12b16f1..2499d831eaf2 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AudioModulationEditor.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/AudioModulationEditor.cpp @@ -1,30 +1,30 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "AudioModulationEditor.h" +#include "AssetRegistryModule.h" #include "AssetTypeActions/AssetTypeActions_SoundControlBus.h" #include "AssetTypeActions/AssetTypeActions_SoundControlBusMix.h" +#include "AssetTypeActions/AssetTypeActions_SoundModulationGeneratorLFO.h" #include "AssetTypeActions/AssetTypeActions_SoundModulationParameter.h" #include "AssetTypeActions/AssetTypeActions_SoundModulationPatch.h" -#include "AssetTypeActions/AssetTypeActions_SoundModulatorLFO.h" #include "Editors/ModulationPatchCurveEditorViewStacked.h" #include "Framework/Commands/UIAction.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/MultiBox/MultiBoxExtender.h" +#include "IAssetRegistry.h" #include "ICurveEditorModule.h" #include "Internationalization/Internationalization.h" -#include "Layouts/SoundControlBusMixChannelLayout.h" +#include "Layouts/SoundControlBusMixStageLayout.h" #include "Layouts/SoundControlModulationPatchLayout.h" #include "Layouts/SoundModulationParameterSettingsLayout.h" #include "Layouts/SoundModulationTransformLayout.h" #include "LevelEditor.h" +#include "Sound/SoundBase.h" #include "SoundModulationParameter.h" #include "SoundModulationTransform.h" #include "Templates/SharedPointer.h" #include "Textures/SlateIcon.h" #include "UObject/UObjectIterator.h" -#include "AssetRegistryModule.h" -#include "IAssetRegistry.h" -#include "Sound/SoundBase.h" DEFINE_LOG_CATEGORY(LogAudioModulationEditor); @@ -84,13 +84,13 @@ void FAudioModulationEditorModule::StartupModule() AudioModulationEditor::AddAssetAction(AssetTools, AssetActions); AudioModulationEditor::AddAssetAction(AssetTools, AssetActions); - AudioModulationEditor::AddAssetAction(AssetTools, AssetActions); + AudioModulationEditor::AddAssetAction(AssetTools, AssetActions); AudioModulationEditor::AddAssetAction(AssetTools, AssetActions); AudioModulationEditor::AddAssetAction(AssetTools, AssetActions); - SetIcon(TEXT("SoundBusModulatorLFO")); SetIcon(TEXT("SoundControlBus")); SetIcon(TEXT("SoundControlBusMix")); + SetIcon(TEXT("SoundModulationGeneratorLFO")); SetIcon(TEXT("SoundModulationPatch")); SetIcon(TEXT("SoundModulationParameter")); @@ -132,18 +132,18 @@ void FAudioModulationEditorModule::StartupModule() void FAudioModulationEditorModule::RegisterCustomPropertyLayouts() { FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked(TEXT("PropertyEditor")); - PropertyModule.RegisterCustomPropertyTypeLayout("SoundModulationOutputTransform", + PropertyModule.RegisterCustomPropertyTypeLayout("SoundModulationTransform", FOnGetPropertyTypeCustomizationInstance::CreateStatic( - &FSoundModulationOutputTransformLayoutCustomization::MakeInstance)); + &FSoundModulationTransformLayoutCustomization::MakeInstance)); PropertyModule.RegisterCustomPropertyTypeLayout("SoundModulationParameterSettings", FOnGetPropertyTypeCustomizationInstance::CreateStatic( &FSoundModulationParameterSettingsLayoutCustomization::MakeInstance)); PropertyModule.RegisterCustomPropertyTypeLayout("SoundControlModulationPatch", FOnGetPropertyTypeCustomizationInstance::CreateStatic( &FSoundControlModulationPatchLayoutCustomization::MakeInstance)); - PropertyModule.RegisterCustomPropertyTypeLayout("SoundControlBusMixChannel", + PropertyModule.RegisterCustomPropertyTypeLayout("SoundControlBusMixStage", FOnGetPropertyTypeCustomizationInstance::CreateStatic( - &FSoundControlBusMixChannelLayoutCustomization::MakeInstance)); + &FSoundControlBusMixStageLayoutCustomization::MakeInstance)); } void FAudioModulationEditorModule::ShutdownModule() diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchCurveEditorViewStacked.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchCurveEditorViewStacked.cpp index 6a600a3b8b52..6899260de2ca 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchCurveEditorViewStacked.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchCurveEditorViewStacked.cpp @@ -8,10 +8,12 @@ #include "CurveModel.h" #include "EditorStyleSet.h" #include "Fonts/FontMeasure.h" +#include "Framework/Application/SlateApplication.h" #include "IAudioModulation.h" #include "SCurveEditorPanel.h" #include "Sound/SoundModulationDestination.h" #include "SoundControlBus.h" +#include "SoundModulationParameter.h" #include "SoundModulationPatch.h" #include "Widgets/Text/STextBlock.h" @@ -30,83 +32,112 @@ namespace PatchCurveViewUtils } // namespace PatchCurveViewUtils - -FModPatchCurveEditorModel::FModPatchCurveEditorModel(FRichCurve& InRichCurve, UObject* InOwner, EModPatchOutputEditorCurveSource InSource, UCurveFloat* InSharedCurve) +FModPatchCurveEditorModel::FModPatchCurveEditorModel(FRichCurve& InRichCurve, UObject* InOwner, EModPatchOutputEditorCurveSource InSource, int32 InInputIndex) : FRichCurveEditorModelRaw(&InRichCurve, InOwner) , Patch(CastChecked(InOwner)) + , InputIndex(InInputIndex) , Source(InSource) { check(InOwner); + Refresh(InSource, InInputIndex); +} - FText InputAxisName = LOCTEXT("ModulationCurveDisplayName_Linear", "Linear"); +void FModPatchCurveEditorModel::Refresh(EModPatchOutputEditorCurveSource InSource, int32 InInputIndex) +{ + InputAxisName = LOCTEXT("ModulationCurveDisplayTitle_Linear", "Linear"); FText OutputAxisName = InputAxisName; - if (Patch.IsValid()) + + FSoundControlModulationInput* Input = nullptr; + Source = EModPatchOutputEditorCurveSource::Unset; + InputIndex = -1; + + if (Patch.IsValid() && InInputIndex >= 0 && InInputIndex < Patch->PatchSettings.Inputs.Num()) { - if (Patch->PatchSettings.InputParameter) + InputIndex = InInputIndex; + Source = InSource; + + static const FText AxisNameFormat = LOCTEXT("ModulationCurveDisplayTitle_AxisNameFormat", "{0} ({1})"); + + Input = &Patch->PatchSettings.Inputs[InInputIndex]; + if (const USoundControlBus* Bus = Input->GetBus()) { - InputAxisName = Patch->PatchSettings.InputParameter->Settings.UnitDisplayName; + if (USoundModulationParameter* Parameter = Bus->Parameter) + { + InputAxisName = FText::Format(AxisNameFormat, FText::FromString(Parameter->GetName()), Parameter->Settings.UnitDisplayName); + } } - if (Patch->PatchSettings.OutputParameter) + if (USoundModulationParameter* Parameter = Patch->PatchSettings.OutputParameter) { - OutputAxisName = Patch->PatchSettings.OutputParameter->Settings.UnitDisplayName; + OutputAxisName = FText::Format(AxisNameFormat, FText::FromString(Parameter->GetName()), Parameter->Settings.UnitDisplayName); } } - const FText ShortNameBase = FText::Format(LOCTEXT("ModulationCurveDisplayName_Axis", "X = {0}, Y = {1}"), InputAxisName, OutputAxisName); + AxesDescriptor = FText::Format(LOCTEXT("ModulationCurveDisplayTitle_AxesFormat", "X = {0}, Y = {1}"), InputAxisName, OutputAxisName); bKeyDrawEnabled = true; - Color = UAudioModulationStyle::GetControlBusColor(); - IntentionName = TEXT("AudioControlValue"); - SupportedViews = ViewId; + Color = UAudioModulationStyle::GetControlBusColor(); + IntentionName = TEXT("AudioControlValue"); + SupportedViews = ViewId; + + ShortDisplayName = LOCTEXT("ModulationCurveDisplayTitle_BusUnset", "Bus (Unset)"); + if (ensure(Input)) + { + if (const USoundControlBus* Bus = Input->GetBus()) + { + ShortDisplayName = FText::FromString(Input->GetBus()->GetName()); + } + } + + bKeyDrawEnabled = false; const bool bIsBypassed = GetIsBypassed(); if (bIsBypassed) { - SetShortDisplayName(FText::Format(LOCTEXT("ModulationCurveDisabledDisplayName", "{0} (Bypassed)"), ShortNameBase)); - bKeyDrawEnabled = false; + LongDisplayName = FText::Format(LOCTEXT("ModulationCurveDisplay_BypassedNameFormat", "{0} (Bypassed)"), ShortDisplayName); } else { - switch (Source) + UEnum* Enum = StaticEnum(); + check(Enum); + + FString EnumValueName = Enum->GetValueAsString(Source); + int32 DelimIndex = -1; + EnumValueName.FindLastChar(':', DelimIndex); + if (DelimIndex > 0 && DelimIndex < EnumValueName.Len() - 1) { - case EModPatchOutputEditorCurveSource::Shared: - { - check(InSharedCurve); - FText CurveNameText = FText::FromString(InSharedCurve->GetName()); - SetShortDisplayName(FText::Format(LOCTEXT("ModulationSharedDisplayName", "{0} [Shared ({1})]"), ShortNameBase, CurveNameText)); - } - break; - - case EModPatchOutputEditorCurveSource::Custom: - { - ShortDisplayName = FText::Format(LOCTEXT("ModulationOutputCurveDisplayName", "{0} [Custom]"), ShortNameBase); - } - break; - - case EModPatchOutputEditorCurveSource::Expression: - { - bKeyDrawEnabled = false; - ShortDisplayName = FText::Format(LOCTEXT("ModulationOutputCurveExpressionDisplayName", "{0} [Expression]"), ShortNameBase); - } - break; - - case EModPatchOutputEditorCurveSource::Unset: - default: - { - bKeyDrawEnabled = false; - ShortDisplayName = FText::Format(LOCTEXT("ModulationOutputCurveUnsetDisplayName", "{0} [Shared (Unset)]"), ShortNameBase); - } - break; + EnumValueName.RightChopInline(DelimIndex + 1); } + + FText CurveSourceText = FText::FromString(EnumValueName); + + if (Source == EModPatchOutputEditorCurveSource::Custom) + { + bKeyDrawEnabled = true; + } + else if (Source == EModPatchOutputEditorCurveSource::Shared) + { + check(Input); + UCurveFloat* SharedCurve = Input->Transform.CurveShared; + check(SharedCurve); + CurveSourceText = FText::Format(LOCTEXT("ModulationCurveDisplayTitle_SharedNameFormat", "Shared, {0}"), FText::FromString(SharedCurve->GetName())); + bKeyDrawEnabled = true; + } + + LongDisplayName = FText::Format(LOCTEXT("ModulationCurveDisplayTitle_NameFormat", "{0}: {1} ({2})"), FText::AsNumber(InInputIndex), ShortDisplayName, CurveSourceText); } } FLinearColor FModPatchCurveEditorModel::GetColor() const { - return !GetIsBypassed() && (Source == EModPatchOutputEditorCurveSource::Custom || Source == EModPatchOutputEditorCurveSource::Expression) + return !GetIsBypassed() && (Source == EModPatchOutputEditorCurveSource::Custom) ? Color - : Color.Desaturate(0.45f); + : Color.Desaturate(0.35f); +} + +const FText& FModPatchCurveEditorModel::GetAxesDescriptor() const +{ + return AxesDescriptor; } bool FModPatchCurveEditorModel::GetIsBypassed() const @@ -124,6 +155,19 @@ EModPatchOutputEditorCurveSource FModPatchCurveEditorModel::GetSource() const return Source; } +USoundModulationParameter* FModPatchCurveEditorModel::GetPatchInputParameter() const +{ + if (Patch.IsValid() && InputIndex >= 0 && InputIndex < Patch->PatchSettings.Inputs.Num()) + { + if (const USoundControlBus* Bus = Patch->PatchSettings.Inputs[InputIndex].GetBus()) + { + return Bus->Parameter; + } + } + + return nullptr; +} + const USoundModulationPatch* FModPatchCurveEditorModel::GetPatch() const { return Patch.Get(); @@ -140,8 +184,8 @@ void SModulationPatchEditorViewStacked::Construct(const FArguments& InArgs, TWea { SCurveEditorViewStacked::Construct(InArgs, InCurveEditor); - StackedHeight = 250.0; - StackedPadding = 28.0f; + StackedHeight = 300.0f; + StackedPadding = 60.0f; } void SModulationPatchEditorViewStacked::PaintView(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 BaseLayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const @@ -173,7 +217,7 @@ void SModulationPatchEditorViewStacked::DrawLabels(const FGeometry& AllottedGeom const FSlateFontInfo FontInfo = FCoreStyle::Get().GetFontStyle("FontAwesome.11"); const FVector2D LocalSize = AllottedGeometry.GetLocalSize(); - const FCurveEditorScreenSpaceV ViewSpace = GetViewSpace(); + const FCurveEditorScreenSpace ViewSpace = GetViewSpace(); // Draw the curve labels for each view for (auto It = CurveInfoByID.CreateConstIterator(); It; ++It) @@ -185,7 +229,7 @@ void SModulationPatchEditorViewStacked::DrawLabels(const FGeometry& AllottedGeom } const int32 CurveIndexFromBottom = CurveInfoByID.Num() - It->Value.CurveIndex - 1; - const double PaddingToBottomOfView = (CurveIndexFromBottom + 1)*ValueSpacePadding; + const double PaddingToBottomOfView = (CurveIndexFromBottom + 1) * ValueSpacePadding; const float PixelBottom = ViewSpace.ValueToScreen(CurveIndexFromBottom + PaddingToBottomOfView); const float PixelTop = ViewSpace.ValueToScreen(CurveIndexFromBottom + PaddingToBottomOfView + 1.0); @@ -195,16 +239,32 @@ void SModulationPatchEditorViewStacked::DrawLabels(const FGeometry& AllottedGeom continue; } - const FText Label = Curve->GetLongDisplayName(); + const TSharedRef FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); - const FVector2D Position(CurveViewConstants::CurveLabelOffsetX * 1.5f, PixelTop + CurveViewConstants::CurveLabelOffsetY * 3.0f); + // Render label + FText Label = Curve->GetLongDisplayName(); - const FPaintGeometry LabelGeometry = AllottedGeometry.ToPaintGeometry(FSlateLayoutTransform(Position)); - const FPaintGeometry LabelDropshadowGeometry = AllottedGeometry.ToPaintGeometry(FSlateLayoutTransform(Position + FVector2D(2, 2))); + static const float LabelOffsetX = 15.0f; + static const float LabelOffsetY = 35.0f; + FVector2D LabelPosition(LabelOffsetX, PixelTop - LabelOffsetY); + FPaintGeometry LabelGeometry = AllottedGeometry.ToPaintGeometry(FSlateLayoutTransform(LabelPosition)); + const FVector2D LabelSize = FontMeasure->Measure(Label, FontInfo); - // Drop shadow - FSlateDrawElement::MakeText(OutDrawElements, LabelLayerId, LabelDropshadowGeometry, Label, FontInfo, DrawEffects, FLinearColor::Black.CopyWithNewOpacity(0.80f)); FSlateDrawElement::MakeText(OutDrawElements, LabelLayerId + 1, LabelGeometry, Label, FontInfo, DrawEffects, Curve->GetColor()); + + // Render axes descriptor + FText Descriptor = static_cast(Curve)->GetAxesDescriptor(); + + const FVector2D DescriptorSize = FontMeasure->Measure(Descriptor, FontInfo); + + static const float LabelBufferX = 20.0f; // Keeps label and axes descriptor visually separated + static const float GutterBufferX = 20.0f; // Accounts for potential scroll bar + const float ViewWidth = ViewSpace.GetPhysicalWidth(); + const float FloatingDescriptorX = ViewWidth - DescriptorSize.X; + LabelPosition = FVector2D(FMath::Max(LabelSize.X + LabelBufferX, FloatingDescriptorX - GutterBufferX), PixelTop - LabelOffsetY); + LabelGeometry = AllottedGeometry.ToPaintGeometry(FSlateLayoutTransform(LabelPosition)); + + FSlateDrawElement::MakeText(OutDrawElements, LabelLayerId + 1, LabelGeometry, Descriptor, FontInfo, DrawEffects, Curve->GetColor()); } } @@ -244,20 +304,17 @@ void SModulationPatchEditorViewStacked::DrawViewGrids(const FGeometry& AllottedG if (const FModPatchCurveEditorModel* EditorModel = static_cast(DrawInfo.GetCurveModel())) { - if (const USoundModulationPatch* Patch = EditorModel->GetPatch()) + if (USoundModulationParameter* InputParameter = EditorModel->GetPatchInputParameter()) { for (int32 i = 0; i < CurveModelGridLabelsX.Num(); ++i) { FText& Label = CurveModelGridLabelsX[i]; - if (USoundModulationParameter* InputParameter = Patch->PatchSettings.InputParameter) - { - PatchCurveViewUtils::FormatLabel(*InputParameter, DrawInfo.LabelFormat, Label); - } + PatchCurveViewUtils::FormatLabel(*InputParameter, DrawInfo.LabelFormat, Label); } } } - const int32 Index = CurveInfoByID.Num() - It->Value.CurveIndex - 1; + const int32 Index = CurveInfoByID.Num() - It->Value.CurveIndex - 1; double Padding = (Index + 1) * ValueSpacePadding; DrawInfo.SetLowerValue(Index + Padding); diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchCurveEditorViewStacked.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchCurveEditorViewStacked.h index 2649cf424a39..a136e39becb6 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchCurveEditorViewStacked.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchCurveEditorViewStacked.h @@ -10,12 +10,16 @@ #include "Views/SCurveEditorViewStacked.h" #include "Views/SInteractiveCurveEditorView.h" +#include "ModulationPatchCurveEditorViewStacked.generated.h" // Forward Declarations class UCurveFloat; +class USoundModulationParameter; class USoundModulationPatch; +struct FSoundControlModulationInput; +UENUM() enum class EModPatchOutputEditorCurveSource : uint8 { Custom, @@ -133,24 +137,28 @@ public: class FModPatchCurveEditorModel : public FRichCurveEditorModelRaw { public: - static ECurveEditorViewID ViewId; - FModPatchCurveEditorModel(FRichCurve& InRichCurve, UObject* InOwner, EModPatchOutputEditorCurveSource InSource, UCurveFloat* SharedCurve); + FModPatchCurveEditorModel(FRichCurve& InRichCurve, UObject* InOwner, EModPatchOutputEditorCurveSource InSource, int32 InInputIndex); + const FText& GetAxesDescriptor() const; bool GetIsBypassed() const; + USoundModulationParameter* GetPatchInputParameter() const; + const USoundModulationPatch* GetPatch() const; + EModPatchOutputEditorCurveSource GetSource() const; + void Refresh(EModPatchOutputEditorCurveSource InSource, int32 InInputIndex); virtual bool IsReadOnly() const override; - virtual FLinearColor GetColor() const override; - EModPatchOutputEditorCurveSource GetSource() const; - - const USoundModulationPatch* GetPatch() const; private: TWeakObjectPtr Patch; - EModPatchOutputEditorCurveSource Source; + int32 InputIndex = -1; + EModPatchOutputEditorCurveSource Source = EModPatchOutputEditorCurveSource::Unset; + + FText InputAxisName; + FText AxesDescriptor; }; class SModulationPatchEditorViewStacked : public SCurveEditorViewStacked diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchEditor.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchEditor.cpp index 917a6e8404c8..514f7036155e 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchEditor.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchEditor.cpp @@ -20,6 +20,7 @@ #include "RichCurveEditorModel.h" #include "SCurveEditorPanel.h" #include "SoundModulationPatch.h" +#include "SoundModulationTransform.h" #include "Tree/SCurveEditorTree.h" #include "Tree/ICurveEditorTreeItem.h" #include "Tree/SCurveEditorTreePin.h" @@ -28,8 +29,6 @@ #include "Widgets/Input/SNumericDropDown.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/SFrameRatePicker.h" -#include "ModulationPatchCurveEditorViewStacked.h" -#include "SoundModulationPatch.h" #define LOCTEXT_NAMESPACE "ModulationPatchEditor" @@ -41,7 +40,6 @@ const FName FModulationPatchEditor::PropertiesTabId(TEXT("ModulationPatchEditor_ FModulationPatchEditor::FModulationPatchEditor() - : CurveModel(FCurveModelID::Unique()) { } @@ -160,37 +158,57 @@ void FModulationPatchEditor::Init(const EToolkitMode::Type Mode, const TSharedPt } } -void FModulationPatchEditor::GenerateExpressionCurve(const FSoundModulationOutputTransform& InTransform, bool bIsUnset) +void FModulationPatchEditor::ClearExpressionCurve(int32 InInputIndex) +{ + if (CurveData.IsValidIndex(InInputIndex)) + { + CurveData[InInputIndex].ExpressionCurve.Reset(); + } +} + +void FModulationPatchEditor::GenerateExpressionCurve(int32 InInputIndex, EModPatchOutputEditorCurveSource InSource, bool bInIsUnset) { if (!CurveEditor.IsValid()) { return; } - TSharedPtr NewCurve = MakeShared(); - ExpressionCurve = NewCurve; + USoundModulationPatch* Patch = CastChecked(GetEditingObject()); + if (!ensure(Patch) || !CurveData.IsValidIndex(InInputIndex)) + { + return; + } + + TSharedPtr Curve = CurveData[InInputIndex].ExpressionCurve; + if (!Curve.IsValid()) + { + Curve = MakeShared(); + CurveData[InInputIndex].ExpressionCurve = Curve; + } if (!GetIsBypassed()) { + const FSoundControlModulationInput& Input = Patch->PatchSettings.Inputs[InInputIndex]; + int32 CurveResolution; - switch (InTransform.Curve) + switch (Input.Transform.Curve) { - case ESoundModulatorOutputCurve::Linear: + case ESoundModulatorCurve::Linear: { CurveResolution = 2; } break; - case ESoundModulatorOutputCurve::Sin: - case ESoundModulatorOutputCurve::SCurve: + case ESoundModulatorCurve::Sin: + case ESoundModulatorCurve::SCurve: { CurveResolution = 64; } break; - case ESoundModulatorOutputCurve::Log: - case ESoundModulatorOutputCurve::Exp: - case ESoundModulatorOutputCurve::Exp_Inverse: + case ESoundModulatorCurve::Log: + case ESoundModulatorCurve::Exp: + case ESoundModulatorCurve::Exp_Inverse: { CurveResolution = 256; } @@ -203,29 +221,68 @@ void FModulationPatchEditor::GenerateExpressionCurve(const FSoundModulationOutpu break; } + Curve->Reset(); + + check(CurveResolution > 1); const float CurveResolutionRatio = 1.0f / (CurveResolution - 1); for (int32 i = 0; i < CurveResolution; ++i) { - const float X = FMath::Lerp(InTransform.InputMin, InTransform.InputMax, CurveResolutionRatio * i); + const float X = FMath::Lerp(0.0f, 1.0f, CurveResolutionRatio * i); float Y = X; - InTransform.Apply(Y); - - NewCurve->AddKey(X, Y); + Input.Transform.Apply(Y); + Curve->AddKey(X, Y); } } - const EModPatchOutputEditorCurveSource Source = bIsUnset ? EModPatchOutputEditorCurveSource::Unset : EModPatchOutputEditorCurveSource::Expression; - SetCurve(*NewCurve.Get(), Source, nullptr); + const EModPatchOutputEditorCurveSource Source = bInIsUnset ? EModPatchOutputEditorCurveSource::Unset : EModPatchOutputEditorCurveSource::Expression; + SetCurve(InInputIndex, *Curve.Get(), Source); } -void FModulationPatchEditor::SetCurve(FRichCurve& InRichCurve, EModPatchOutputEditorCurveSource InSource, UCurveFloat* InSharedCurve) +bool FModulationPatchEditor::RequiresNewCurve(int32 InInputIndex, const FRichCurve& InRichCurve) const +{ + const FCurveModelID CurveModelID = CurveData[InInputIndex].ModelID; + const TUniquePtr* CurveModel = CurveEditor->GetCurves().Find(CurveModelID); + if (!CurveModel || !CurveModel->IsValid()) + { + return true; + } + + FModPatchCurveEditorModel* PatchCurveModel = static_cast(CurveModel->Get()); + check(PatchCurveModel); + if (&PatchCurveModel->GetRichCurve() != &InRichCurve) + { + return true; + } + + return false; +} + + +void FModulationPatchEditor::SetCurve(int32 InInputIndex, FRichCurve& InRichCurve, EModPatchOutputEditorCurveSource InSource) { check(CurveEditor.IsValid()); - CurveEditor->RemoveAllCurves(); - TUniquePtr NewCurve = MakeUnique(InRichCurve, GetEditingObject(), InSource, InSharedCurve); - CurveModel = CurveEditor->AddCurve(MoveTemp(NewCurve)); - CurveEditor->PinCurve(CurveModel); + if (!ensure(CurveData.IsValidIndex(InInputIndex))) + { + return; + } + + FCurveData& CurveDataEntry = CurveData[InInputIndex]; + + const bool bRequiresNewCurve = RequiresNewCurve(InInputIndex, InRichCurve); + if (bRequiresNewCurve) + { + TUniquePtr NewCurve = MakeUnique(InRichCurve, GetEditingObject(), InSource, InInputIndex); + CurveDataEntry.ModelID = CurveEditor->AddCurve(MoveTemp(NewCurve)); + } + else + { + const TUniquePtr& CurveModel = CurveEditor->GetCurves().FindChecked(CurveDataEntry.ModelID); + check(CurveModel.Get()); + static_cast(CurveModel.Get())->Refresh(InSource, InInputIndex); + } + + CurveEditor->PinCurve(CurveDataEntry.ModelID); } bool FModulationPatchEditor::GetIsBypassed() const @@ -269,7 +326,7 @@ void FModulationPatchEditor::NotifyPostChange(const FPropertyChangedEvent& Prope { if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) { - UpdateCurve(); + RefreshCurves(); } } @@ -287,7 +344,7 @@ TSharedRef FModulationPatchEditor::SpawnTab_Properties(const FSpawnTab TSharedRef FModulationPatchEditor::SpawnTab_OutputCurve(const FSpawnTabArgs& Args) { - UpdateCurve(); + RefreshCurves(); CurveEditor->ZoomToFit(); TSharedRef NewDockTab = SNew(SDockTab) @@ -310,65 +367,172 @@ void FModulationPatchEditor::PostUndo(bool bSuccess) { if (bSuccess) { - UpdateCurve(); + RefreshCurves(); } } -void FModulationPatchEditor::UpdateCurve() +void FModulationPatchEditor::ResetCurves() { - check(CurveEditor.IsValid()); + const USoundModulationPatch* Patch = CastChecked(GetEditingObject()); + check(Patch); - CurveEditor->UnpinCurve(CurveModel); + CurveEditor->RemoveAllCurves(); + CurveData.Reset(); + CurveData.AddDefaulted(Patch->PatchSettings.Inputs.Num()); +} - USoundModulationPatch* Patch = CastChecked(GetEditingObject()); - - if (Patch->PatchSettings.bBypass) +void FModulationPatchEditor::InitCurves() +{ + const USoundModulationPatch * Patch = CastChecked(GetEditingObject()); + const FSoundControlModulationPatch& PatchSettings = Patch->PatchSettings; + const int32 NumInputs = PatchSettings.Inputs.Num(); + if (NumInputs < CurveData.Num() || NumInputs > CurveData.Num()) { - CurveEditor->RemoveAllCurves(); + ResetCurves(); return; } - FSoundModulationOutputTransform Transform = Patch->PatchSettings.Transform; - switch (Transform.Curve) + for (int32 i = 0; i < PatchSettings.Inputs.Num(); ++i) { - case ESoundModulatorOutputCurve::Exp: - case ESoundModulatorOutputCurve::Exp_Inverse: - case ESoundModulatorOutputCurve::Linear: - case ESoundModulatorOutputCurve::Log: - case ESoundModulatorOutputCurve::SCurve: - case ESoundModulatorOutputCurve::Sin: + const FSoundControlModulationInput& Input = PatchSettings.Inputs[i]; + switch (Input.Transform.Curve) { - GenerateExpressionCurve(Transform); - } - break; - - case ESoundModulatorOutputCurve::Shared: - { - if (UCurveFloat* SharedCurve = Transform.CurveShared) + case ESoundModulatorCurve::Exp: + case ESoundModulatorCurve::Exp_Inverse: + case ESoundModulatorCurve::Linear: + case ESoundModulatorCurve::Log: + case ESoundModulatorCurve::SCurve: + case ESoundModulatorCurve::Sin: { - SetCurve(SharedCurve->FloatCurve, EModPatchOutputEditorCurveSource::Shared, SharedCurve); + if (RequiresNewCurve(i, *CurveData[i].ExpressionCurve.Get())) + { + ResetCurves(); + } } - else + break; + + case ESoundModulatorCurve::Shared: { - // Builds a dummy expression that just maps input to output in case - // where asset isn't selected and leave source as unset - GenerateExpressionCurve(Transform, true /* bIsUnset */); + if (UCurveFloat* SharedCurve = Input.Transform.CurveShared) + { + if (RequiresNewCurve(i, SharedCurve->FloatCurve)) + { + ResetCurves(); + } + } + else if (RequiresNewCurve(i, *CurveData[i].ExpressionCurve.Get())) + { + ResetCurves(); + } } - } - break; + break; - case ESoundModulatorOutputCurve::Custom: - { - TrimKeys(Transform); - SetCurve(Transform.CurveCustom, EModPatchOutputEditorCurveSource::Custom); - } - break; + case ESoundModulatorCurve::Custom: + { + if (RequiresNewCurve(i, Input.Transform.CurveCustom)) + { + ResetCurves(); + } + } + break; - default: - { - static_assert(static_cast(ESoundModulatorOutputCurve::Count) == 8, "Possible missing case coverage for output curve."); + default: + { + static_assert(static_cast(ESoundModulatorCurve::Count) == 8, "Possible missing case coverage for output curve."); + } + break; } - break; + } +} + +void FModulationPatchEditor::RefreshCurves() +{ + check(CurveEditor.IsValid()); + + for (const FCurveData& CurveDataEntry : CurveData) + { + CurveEditor->UnpinCurve(CurveDataEntry.ModelID); + } + + USoundModulationPatch* Patch = CastChecked(GetEditingObject()); + check(Patch); + + if (Patch->PatchSettings.bBypass) + { + ResetCurves(); + return; + } + + InitCurves(); + + for (int32 i = 0; i < Patch->PatchSettings.Inputs.Num(); ++i) + { + FSoundControlModulationInput& Input = Patch->PatchSettings.Inputs[i]; + switch (Input.Transform.Curve) + { + case ESoundModulatorCurve::Exp: + case ESoundModulatorCurve::Exp_Inverse: + case ESoundModulatorCurve::Linear: + case ESoundModulatorCurve::Log: + case ESoundModulatorCurve::SCurve: + case ESoundModulatorCurve::Sin: + { + GenerateExpressionCurve(i, EModPatchOutputEditorCurveSource::Expression); + } + break; + + case ESoundModulatorCurve::Shared: + { + if (UCurveFloat* SharedCurve = Input.Transform.CurveShared) + { + ClearExpressionCurve(i); + SetCurve(i, SharedCurve->FloatCurve, EModPatchOutputEditorCurveSource::Shared); + } + else + { + // Builds a dummy expression that just maps input to output in case + // where asset isn't selected and leave source as unset + GenerateExpressionCurve(i, EModPatchOutputEditorCurveSource::Expression, true /* bIsUnset */); + } + } + break; + + case ESoundModulatorCurve::Custom: + { + TrimKeys(Input.Transform); + ClearExpressionCurve(i); + SetCurve(i, Input.Transform.CurveCustom, EModPatchOutputEditorCurveSource::Custom); + } + break; + + default: + { + static_assert(static_cast(ESoundModulatorCurve::Count) == 8, "Possible missing case coverage for output curve."); + } + break; + } + } + + // Collect and remove stale curves from editor + TArray ToRemove; + TSet ActiveModelIDs; + for (const FCurveData& CurveDataEntry : CurveData) + { + ActiveModelIDs.Add(CurveDataEntry.ModelID); + } + + const TMap>& Curves = CurveEditor->GetCurves(); + for (const TPair>& Pair : Curves) + { + if (!ActiveModelIDs.Contains(Pair.Key)) + { + ToRemove.Add(Pair.Key); + } + } + + for (FCurveModelID ModelID : ToRemove) + { + CurveEditor->RemoveCurve(ModelID); } } @@ -376,19 +540,19 @@ void FModulationPatchEditor::PostRedo(bool bSuccess) { if (bSuccess) { - UpdateCurve(); + RefreshCurves(); } } -void FModulationPatchEditor::TrimKeys(FSoundModulationOutputTransform& OutTransform) const +void FModulationPatchEditor::TrimKeys(FSoundModulationTransform& OutTransform) { FRichCurve& Curve = OutTransform.CurveCustom; - while (Curve.GetNumKeys() > 0 && OutTransform.InputMin > Curve.GetFirstKey().Time) + while (Curve.GetNumKeys() > 0 && 0.0f > Curve.GetFirstKey().Time) { Curve.DeleteKey(Curve.GetFirstKeyHandle()); } - while (Curve.GetNumKeys() > 0 && OutTransform.InputMax < Curve.GetLastKey().Time) + while (Curve.GetNumKeys() > 0 && 1.0f < Curve.GetLastKey().Time) { Curve.DeleteKey(Curve.GetLastKeyHandle()); } diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchEditor.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchEditor.h index 50cb89abaa06..43265b7ca25c 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchEditor.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Editors/ModulationPatchEditor.h @@ -24,6 +24,8 @@ class SCurveEditorPanel; class UCurveBase; class USoundModulationPatch; +struct FSoundControlModulationInput; + class FModulationPatchEditor : public FAssetEditorToolkit, public FNotifyHook, public FEditorUndoClient { @@ -49,7 +51,7 @@ protected: virtual void PostRedo(bool bSuccess) override; private: - void SetCurve(FRichCurve& InRichCurve, EModPatchOutputEditorCurveSource InSource, UCurveFloat* InSharedCurve = nullptr); + void SetCurve(int32 InInputIndex, FRichCurve& InRichCurve, EModPatchOutputEditorCurveSource InSource); /** Spawns the tab allowing for editing/viewing the output curve(s) */ TSharedRef SpawnTab_OutputCurve(const FSpawnTabArgs& Args); @@ -62,22 +64,40 @@ private: /** Get the orientation for the snap value controls. */ EOrientation GetSnapLabelOrientation() const; - /** Updates patch's output curve. */ - void UpdateCurve(); + /** Updates patch's input curves. */ + void RefreshCurves(); /** Trims keys out-of-bounds in provided output transform's curve */ - void TrimKeys(FSoundModulationOutputTransform& OutTransform) const; + static void TrimKeys(FSoundModulationTransform& OutTransform); - void GenerateExpressionCurve(const FSoundModulationOutputTransform& InTransform, bool bIsUnset = false); + /** Clears the expression curve at the given input index */ + void ClearExpressionCurve(int32 InInputIndex); + + /** Generates expression curve at the given index. */ + void GenerateExpressionCurve(int32 InInputIndex, EModPatchOutputEditorCurveSource InSource, bool bInIsUnset = false); + + void InitCurves(); + + void ResetCurves(); + + bool RequiresNewCurve(int32 InInputIndex, const FRichCurve& InRichCurve) const; TSharedPtr ToolbarCurveTargetCommands; TSharedPtr CurveEditor; TSharedPtr CurvePanel; - TSharedPtr ExpressionCurve; + struct FCurveData + { + FCurveModelID ModelID; + TSharedPtr ExpressionCurve; - FCurveModelID CurveModel; + FCurveData() + : ModelID(FCurveModelID::Unique()) + { + } + }; + TArray CurveData; /** Properties tab */ TSharedPtr PropertiesView; diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulationGeneratorLFOFactory.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulationGeneratorLFOFactory.cpp new file mode 100644 index 000000000000..997a9a980dee --- /dev/null +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulationGeneratorLFOFactory.cpp @@ -0,0 +1,19 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SoundModulationGeneratorLFOFactory.h" +#include "SoundModulationGeneratorLFO.h" + + +USoundModulationGeneratorLFOFactory::USoundModulationGeneratorLFOFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = USoundModulationGeneratorLFO::StaticClass(); + bCreateNew = true; + bEditorImport = false; + bEditAfterNew = true; +} + +UObject* USoundModulationGeneratorLFOFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return NewObject(InParent, Name, Flags); +} diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulatorLFOFactory.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulationGeneratorLFOFactory.h similarity index 56% rename from Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulatorLFOFactory.h rename to Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulationGeneratorLFOFactory.h index 1d0e93d57a4e..282a1290a66f 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulatorLFOFactory.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulationGeneratorLFOFactory.h @@ -1,18 +1,13 @@ // Copyright Epic Games, Inc. All Rights Reserved. - -//============================================================================= -// USoundModulatorLFOFactory -//============================================================================= - #pragma once #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" #include "Factories/Factory.h" -#include "SoundModulatorLFOFactory.generated.h" +#include "SoundModulationGeneratorLFOFactory.generated.h" UCLASS(hidecategories=Object, MinimalAPI) -class USoundModulatorLFOFactory : public UFactory +class USoundModulationGeneratorLFOFactory : public UFactory { GENERATED_UCLASS_BODY() diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulatorLFOFactory.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulatorLFOFactory.cpp deleted file mode 100644 index 26931e08c810..000000000000 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Factories/SoundModulatorLFOFactory.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "SoundModulatorLFOFactory.h" -#include "SoundModulatorLFO.h" - - -USoundModulatorLFOFactory::USoundModulatorLFOFactory(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - SupportedClass = USoundBusModulatorLFO::StaticClass(); - bCreateNew = true; - bEditorImport = false; - bEditAfterNew = true; -} - -UObject* USoundModulatorLFOFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - return NewObject(InParent, Name, Flags); -} diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlBusMixChannelLayout.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlBusMixStageLayout.cpp similarity index 87% rename from Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlBusMixChannelLayout.cpp rename to Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlBusMixStageLayout.cpp index e8acd0163c83..85a72c5e8dd1 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlBusMixChannelLayout.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlBusMixStageLayout.cpp @@ -1,5 +1,5 @@ // Copyright Epic Games, Inc. All Rights Reserved. -#include "SoundControlBusMixChannelLayout.h" +#include "SoundControlBusMixStageLayout.h" #include "Audio.h" #include "AudioModulationStyle.h" @@ -83,11 +83,11 @@ namespace AudioModulationEditorUtils } } -void FSoundControlBusMixChannelLayoutCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) +void FSoundControlBusMixStageLayoutCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) { } -void FSoundControlBusMixChannelLayoutCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +void FSoundControlBusMixStageLayoutCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { uint32 NumChildren; StructPropertyHandle->GetNumChildren(NumChildren); @@ -101,7 +101,7 @@ void FSoundControlBusMixChannelLayoutCustomization::CustomizeChildren(TSharedRef PropertyHandles.Add(PropertyName, ChildHandle); } - TSharedRef BusHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundControlBusMixChannel, Bus)).ToSharedRef(); + TSharedRef BusHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundControlBusMixStage, Bus)).ToSharedRef(); TAttribute BusInfoVisibility = TAttribute::Create([BusHandle]() { @@ -112,8 +112,8 @@ void FSoundControlBusMixChannelLayoutCustomization::CustomizeChildren(TSharedRef ChildBuilder.AddProperty(BusHandle); - TSharedRef LinearValueHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FSoundModulationValue, TargetValue)).ToSharedRef(); - TSharedRef UnitValueHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FSoundModulationValue, TargetUnitValue)).ToSharedRef(); + TSharedRef LinearValueHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, TargetValue)).ToSharedRef(); + TSharedRef UnitValueHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, TargetUnitValue)).ToSharedRef(); // When editor opens, set unit value in case bus unit has changed while editor was closed. AudioModulationEditorUtils::HandleConvertLinearToUnit(BusHandle, LinearValueHandle, UnitValueHandle); @@ -204,10 +204,10 @@ void FSoundControlBusMixChannelLayoutCustomization::CustomizeChildren(TSharedRef return EVisibility::Visible; })); - TSharedRef AttackTimeHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FSoundModulationValue, AttackTime)).ToSharedRef(); + TSharedRef AttackTimeHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, AttackTime)).ToSharedRef(); ChildBuilder.AddProperty(AttackTimeHandle); - TSharedRef ReleaseTimeHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FSoundModulationValue, ReleaseTime)).ToSharedRef(); + TSharedRef ReleaseTimeHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FSoundModulationMixValue, ReleaseTime)).ToSharedRef(); ChildBuilder.AddProperty(ReleaseTimeHandle); } #undef LOCTEXT_NAMESPACE // SoundModulationFloat diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlBusMixChannelLayout.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlBusMixStageLayout.h similarity index 83% rename from Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlBusMixChannelLayout.h rename to Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlBusMixStageLayout.h index 96ca95e4527c..f96d569e9154 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlBusMixChannelLayout.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlBusMixStageLayout.h @@ -11,12 +11,12 @@ #include "SoundControlBus.h" -class FSoundControlBusMixChannelLayoutCustomization : public IPropertyTypeCustomization +class FSoundControlBusMixStageLayoutCustomization : public IPropertyTypeCustomization { public: static TSharedRef MakeInstance() { - return MakeShared(); + return MakeShared(); } //~ Begin IPropertyTypeCustomization diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlModulationPatchLayout.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlModulationPatchLayout.cpp index a14e4654025b..b4af19f877df 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlModulationPatchLayout.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlModulationPatchLayout.cpp @@ -66,109 +66,27 @@ namespace AudioModulationEditorUtils } } // namespace AudioModulationEditorUtils -void FSoundModulationPatchLayoutCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) -{ -} - -void FSoundModulationPatchLayoutCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +void FSoundControlModulationPatchLayoutCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { TMap> PropertyHandles; AudioModulationEditorUtils::GetPropertyHandleMap(StructPropertyHandle, PropertyHandles); - CustomizeControl(PropertyHandles, ChildBuilder); -} -TAttribute FSoundModulationPatchLayoutCustomization::CustomizeControl(TMap> &PropertyHandles, IDetailChildrenBuilder &ChildBuilder) -{ - TSharedRefBypassHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationPatchBase, bBypass)).ToSharedRef(); + TSharedRef BypassHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundControlModulationPatch, bBypass)).ToSharedRef(); + TSharedRef InputsHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundControlModulationPatch, Inputs)).ToSharedRef(); + TSharedRef ParameterHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundControlModulationPatch, OutputParameter)).ToSharedRef(); + ChildBuilder.AddProperty(BypassHandle); - - TAttribute VisibilityAttribute = TAttribute::Create([this, BypassHandle]() - { - bool bIsBypassed = false; - BypassHandle->GetValue(bIsBypassed); - return bIsBypassed ? EVisibility::Hidden : EVisibility::Visible; - }); - - return VisibilityAttribute; -} - -TAttribute FSoundControlModulationPatchLayoutCustomization::CustomizeControl(TMap>& PropertyHandles, IDetailChildrenBuilder& ChildBuilder) -{ - TSharedRefBypassHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationPatchBase, bBypass)).ToSharedRef(); - ChildBuilder.AddProperty(BypassHandle); - - TAttribute BypassedVisibilityAttribute = TAttribute::Create([this, BypassHandle]() - { - bool bIsBypassed = false; - BypassHandle->GetValue(bIsBypassed); - return bIsBypassed ? EVisibility::Hidden : EVisibility::Visible; - }); - - TSharedRef InputsHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundControlModulationPatch, Inputs)).ToSharedRef(); - TSharedRef InputParameterHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundControlModulationPatch, InputParameter)).ToSharedRef(); - TSharedRef OutputParameterHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundControlModulationPatch, OutputParameter)).ToSharedRef(); - TSharedRef TransformHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundControlModulationPatch, Transform)).ToSharedRef(); - - ChildBuilder.AddProperty(InputParameterHandle) - .Visibility(BypassedVisibilityAttribute); - - ChildBuilder.AddCustomRow(LOCTEXT("ModulationPatchLayout_UnitMismatchHeadingWarning", "Bus Unit Mismatch Warning")) - .ValueContent() - .MinDesiredWidth(150.0f) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(1.0f, 0.0f, 0.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Font(IDetailLayoutBuilder::GetDetailFontBold()) - .Text(TAttribute::Create([InputsHandle, InputParameterHandle]() - { - UObject* Object = nullptr; - InputParameterHandle->GetValue(Object); - - if (USoundModulationParameter* Parameter = Cast(Object)) - { - return FText::Format( - LOCTEXT("ModulationPatchLayout_UnitMismatchHeading", "{0} bus(es) with parameter mismatch"), - FText::AsNumber(AudioModulationEditorUtils::GetMismatchedBuses(InputsHandle, Parameter).Num()) - ); - } - - return FText(); - })) - ] - ] - .Visibility(TAttribute::Create([BypassHandle, InputsHandle, InputParameterHandle]() + ChildBuilder.AddProperty(ParameterHandle); + ChildBuilder.AddProperty(InputsHandle) + .Visibility(TAttribute::Create([this, BypassHandle]() { bool bIsBypassed = false; BypassHandle->GetValue(bIsBypassed); - if (bIsBypassed) - { - return EVisibility::Hidden; - } - - UObject* Object = nullptr; - InputParameterHandle->GetValue(Object); - - if (USoundModulationParameter* Parameter = Cast(Object)) - { - const int32 NumMismatched = AudioModulationEditorUtils::GetMismatchedBuses(InputsHandle, Parameter).Num(); - return NumMismatched > 0 ? EVisibility::Visible : EVisibility::Hidden; - } - - return EVisibility::Hidden; + return bIsBypassed ? EVisibility::Hidden : EVisibility::Visible; })); +} - ChildBuilder.AddProperty(InputsHandle) - .Visibility(BypassedVisibilityAttribute); - - ChildBuilder.AddProperty(TransformHandle) - .Visibility(BypassedVisibilityAttribute); - - ChildBuilder.AddProperty(OutputParameterHandle); - return BypassedVisibilityAttribute; +void FSoundControlModulationPatchLayoutCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) +{ } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlModulationPatchLayout.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlModulationPatchLayout.h index 0b55f3cdba61..7c83dd922925 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlModulationPatchLayout.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundControlModulationPatchLayout.h @@ -16,20 +16,15 @@ class SSearchableComboBox; struct FSoundModulationSettings; -class FSoundModulationPatchLayoutCustomization : public IPropertyTypeCustomization +class FSoundControlModulationPatchLayoutCustomization : public IPropertyTypeCustomization { public: static TSharedRef MakeInstance() { - return MakeShared(); + return MakeShared(); } - //~ Begin IPropertyTypeCustomization - virtual void CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - virtual void CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - //~ End IPropertyTypeCustomization - -protected: +private: template void AddPatchProperties(TAttribute VisibilityAttribute, TMap>& PropertyHandles, IDetailChildrenBuilder& ChildBuilder) { @@ -42,60 +37,10 @@ protected: .Visibility(VisibilityAttribute); } - virtual TAttribute CustomizeControl(TMap>& PropertyHandles, IDetailChildrenBuilder& ChildBuilder); -}; + //~ Begin IPropertyTypeCustomization + virtual void CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + virtual void CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + //~ End IPropertyTypeCustomization -class FSoundVolumeModulationPatchLayoutCustomization : public FSoundModulationPatchLayoutCustomization -{ -public: - static TSharedRef MakeInstance() - { - return MakeShared(); - } - - virtual TAttribute CustomizeControl(TMap>& PropertyHandles, IDetailChildrenBuilder& ChildBuilder) override; -}; - -class FSoundPitchModulationPatchLayoutCustomization : public FSoundModulationPatchLayoutCustomization -{ -public: - static TSharedRef MakeInstance() - { - return MakeShared(); - } - - virtual TAttribute CustomizeControl(TMap>& PropertyHandles, IDetailChildrenBuilder& ChildBuilder) override; -}; - -class FSoundLPFModulationPatchLayoutCustomization : public FSoundModulationPatchLayoutCustomization -{ -public: - static TSharedRef MakeInstance() - { - return MakeShared(); - } - - virtual TAttribute CustomizeControl(TMap>& PropertyHandles, IDetailChildrenBuilder& ChildBuilder) override; -}; - -class FSoundHPFModulationPatchLayoutCustomization : public FSoundModulationPatchLayoutCustomization -{ -public: - static TSharedRef MakeInstance() - { - return MakeShared(); - } - - virtual TAttribute CustomizeControl(TMap>& PropertyHandles, IDetailChildrenBuilder& ChildBuilder) override; -}; - -class FSoundControlModulationPatchLayoutCustomization : public FSoundModulationPatchLayoutCustomization -{ -public: - static TSharedRef MakeInstance() - { - return MakeShared(); - } - - virtual TAttribute CustomizeControl(TMap>& PropertyHandles, IDetailChildrenBuilder& ChildBuilder) override; + TAttribute CustomizeControl(TMap>& PropertyHandles, IDetailChildrenBuilder& ChildBuilder); }; \ No newline at end of file diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundModulationParameterSettingsLayout.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundModulationParameterSettingsLayout.cpp index 19cdc88db12e..02f5fe0b39c9 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundModulationParameterSettingsLayout.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundModulationParameterSettingsLayout.cpp @@ -107,7 +107,7 @@ void FSoundModulationParameterSettingsLayoutCustomization::CustomizeChildren(TSh SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(0.4f) - .Padding(4.0f, 0.0f, 0.0f, 0.0f) + .Padding(1.0f, 0.0f, 0.0f, 0.0f) .VAlign(VAlign_Center) [ UnitValueHandle->CreatePropertyValueWidget() diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundModulationTransformLayout.cpp b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundModulationTransformLayout.cpp index e0990b2ac007..9ee94ee4ae67 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundModulationTransformLayout.cpp +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundModulationTransformLayout.cpp @@ -27,11 +27,11 @@ #define LOCTEXT_NAMESPACE "SoundModulationOutputTransform" -void FSoundModulationOutputTransformLayoutCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) +void FSoundModulationTransformLayoutCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) { } -void FSoundModulationOutputTransformLayoutCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +void FSoundModulationTransformLayoutCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { uint32 NumChildren; StructPropertyHandle->GetNumChildren(NumChildren); @@ -45,23 +45,22 @@ void FSoundModulationOutputTransformLayoutCustomization::CustomizeChildren(TShar PropertyHandles.Add(PropertyName, ChildHandle); } - TSharedRefCurveHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationOutputTransform, Curve)).ToSharedRef(); + TSharedRefCurveHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationTransform, Curve)).ToSharedRef(); ChildBuilder.AddProperty(CurveHandle); - TSharedRef ScalarHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationOutputTransform, Scalar)).ToSharedRef(); - const TArray ScalarFilters = { ESoundModulatorOutputCurve::Exp, ESoundModulatorOutputCurve::Log }; + TSharedRef ScalarHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationTransform, Scalar)).ToSharedRef(); ChildBuilder.AddProperty(ScalarHandle) - .EditCondition(TAttribute::Create([this, CurveHandle, ScalarFilters]() { return IsScaleableCurve(CurveHandle, ScalarFilters); }), nullptr) - .Visibility(TAttribute::Create([this, CurveHandle, ScalarFilters]() { return IsScaleableCurve(CurveHandle, ScalarFilters) ? EVisibility::Visible : EVisibility::Hidden; })); + .EditCondition(TAttribute::Create([this, CurveHandle]() { return IsScaleableCurve(CurveHandle); }), nullptr) + .Visibility(TAttribute::Create([this, CurveHandle]() { return IsScaleableCurve(CurveHandle) ? EVisibility::Visible : EVisibility::Hidden; })); - TSharedRef SharedCurveHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationOutputTransform, CurveShared)).ToSharedRef(); + TSharedRef SharedCurveHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationTransform, CurveShared)).ToSharedRef(); ChildBuilder.AddProperty(SharedCurveHandle) - .EditCondition(TAttribute::Create([this, CurveHandle, ScalarFilters]() { return IsSharedCurve(CurveHandle); }), nullptr) - .Visibility(TAttribute::Create([this, CurveHandle, ScalarFilters]() { return IsSharedCurve(CurveHandle) ? EVisibility::Visible : EVisibility::Hidden; })); + .EditCondition(TAttribute::Create([this, CurveHandle]() { return IsSharedCurve(CurveHandle); }), nullptr) + .Visibility(TAttribute::Create([this, CurveHandle]() { return IsSharedCurve(CurveHandle) ? EVisibility::Visible : EVisibility::Hidden; })); } -bool FSoundModulationOutputTransformLayoutCustomization::IsScaleableCurve(TSharedPtr CurveHandle, const TArray& Filters) const +bool FSoundModulationTransformLayoutCustomization::IsScaleableCurve(TSharedPtr CurveHandle) const { if (!CurveHandle.IsValid()) { @@ -70,7 +69,9 @@ bool FSoundModulationOutputTransformLayoutCustomization::IsScaleableCurve(TShare uint8 CurveString; CurveHandle->GetValue(CurveString); - for (ESoundModulatorOutputCurve Filter : Filters) + + static const TArray ScalarFilters = { ESoundModulatorCurve::Exp, ESoundModulatorCurve::Exp_Inverse, ESoundModulatorCurve::Log }; + for (ESoundModulatorCurve Filter : ScalarFilters) { if (CurveString == static_cast(Filter)) { @@ -81,7 +82,7 @@ bool FSoundModulationOutputTransformLayoutCustomization::IsScaleableCurve(TShare return false; } -bool FSoundModulationOutputTransformLayoutCustomization::IsSharedCurve(TSharedPtr CurveHandle) const +bool FSoundModulationTransformLayoutCustomization::IsSharedCurve(TSharedPtr CurveHandle) const { if (!CurveHandle.IsValid()) { @@ -90,6 +91,6 @@ bool FSoundModulationOutputTransformLayoutCustomization::IsSharedCurve(TSharedPt uint8 CurveValue; CurveHandle->GetValue(CurveValue); - return CurveValue == static_cast(ESoundModulatorOutputCurve::Shared); + return CurveValue == static_cast(ESoundModulatorCurve::Shared); } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundModulationTransformLayout.h b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundModulationTransformLayout.h index 0fc20cca3567..2ba495f3284d 100644 --- a/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundModulationTransformLayout.h +++ b/Engine/Plugins/Runtime/AudioModulation/Source/AudioModulationEditor/Private/Layouts/SoundModulationTransformLayout.h @@ -13,14 +13,14 @@ // Forward Declarations -struct FSoundModulationOutputTransform; +struct FSoundModulationTransform; -class FSoundModulationOutputTransformLayoutCustomization : public IPropertyTypeCustomization +class FSoundModulationTransformLayoutCustomization : public IPropertyTypeCustomization { public: static TSharedRef MakeInstance() { - return MakeShared(); + return MakeShared(); } //~ Begin IPropertyTypeCustomization @@ -29,6 +29,6 @@ public: //~ End IPropertyTypeCustomization private: - bool IsScaleableCurve(TSharedPtr CurveHandle, const TArray& Filters) const; + bool IsScaleableCurve(TSharedPtr CurveHandle) const; bool IsSharedCurve(TSharedPtr CurveHandle) const; }; diff --git a/Engine/Plugins/Runtime/CableComponent/Source/CableComponent/Classes/CableComponent.h b/Engine/Plugins/Runtime/CableComponent/Source/CableComponent/Classes/CableComponent.h index a4486db58f4b..9edc7f66167c 100644 --- a/Engine/Plugins/Runtime/CableComponent/Source/CableComponent/Classes/CableComponent.h +++ b/Engine/Plugins/Runtime/CableComponent/Source/CableComponent/Classes/CableComponent.h @@ -49,6 +49,7 @@ public: virtual bool HasAnySockets() const override; virtual bool DoesSocketExist(FName InSocketName) const override; virtual FTransform GetSocketTransform(FName InSocketName, ERelativeTransformSpace TransformSpace = RTS_World) const override; + virtual void OnVisibilityChanged() override; //~ End USceneComponent Interface. //~ Begin UPrimitiveComponent Interface. diff --git a/Engine/Plugins/Runtime/CableComponent/Source/CableComponent/Private/CableComponent.cpp b/Engine/Plugins/Runtime/CableComponent/Source/CableComponent/Private/CableComponent.cpp index f75a1cdadf03..53f98cf9911a 100644 --- a/Engine/Plugins/Runtime/CableComponent/Source/CableComponent/Private/CableComponent.cpp +++ b/Engine/Plugins/Runtime/CableComponent/Source/CableComponent/Private/CableComponent.cpp @@ -623,12 +623,24 @@ void UCableComponent::GetEndPositions(FVector& OutStartPosition, FVector& OutEnd } +void UCableComponent::OnVisibilityChanged() +{ + Super::OnVisibilityChanged(); + + // Does not interact well with any other states that would be blocking tick + if (bSkipCableUpdateWhenNotVisible) + { + SetComponentTickEnabled(IsVisible()); + } +} + void UCableComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (bSkipCableUpdateWhenNotVisible && !IsVisible()) { + SetComponentTickEnabled(false); return; } diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/Abilities/GameplayAbility.cpp b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/Abilities/GameplayAbility.cpp index 81eb29f6b593..80d2a698d513 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/Abilities/GameplayAbility.cpp +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/Abilities/GameplayAbility.cpp @@ -91,7 +91,8 @@ int32 UGameplayAbility::GetFunctionCallspace(UFunction* Function, FFrame* Stack) { if (HasAnyFlags(RF_ClassDefaultObject) || !IsSupportedForNetworking()) { - return FunctionCallspace::Local; + // This handles absorbing authority/cosmetic + return GEngine->GetGlobalFunctionCallspace(Function, this, Stack); } check(GetOuter() != nullptr); return GetOuter()->GetFunctionCallspace(Function, Stack); @@ -538,7 +539,7 @@ void UGameplayAbility::CancelAbility(const FGameplayAbilitySpecHandle Handle, co } // Replicate the the server/client if needed - if (bReplicateCancelAbility && ActorInfo) + if (bReplicateCancelAbility && ActorInfo && ActorInfo->AbilitySystemComponent.IsValid()) { ActorInfo->AbilitySystemComponent->ReplicateEndOrCancelAbility(Handle, ActivationInfo, this, true); } @@ -1216,9 +1217,11 @@ AActor* UGameplayAbility::GetGameplayTaskAvatar(const UGameplayTask* Task) const void UGameplayAbility::OnGameplayTaskInitialized(UGameplayTask& Task) { UAbilityTask* AbilityTask = Cast(&Task); - if (AbilityTask) + const FGameplayAbilityActorInfo* ActorInfo = GetCurrentActorInfo(); + + if (AbilityTask && ActorInfo) { - AbilityTask->SetAbilitySystemComponent(GetCurrentActorInfo()->AbilitySystemComponent.Get()); + AbilityTask->SetAbilitySystemComponent(ActorInfo->AbilitySystemComponent.Get()); AbilityTask->Ability = this; } } diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cpp b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cpp index 7216f249b390..7078f21f217e 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cpp +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cpp @@ -875,7 +875,7 @@ void UAbilitySystemComponent::CancelAllAbilities(UGameplayAbility* Ignore) ABILITYLIST_SCOPE_LOCK(); for (FGameplayAbilitySpec& Spec : ActivatableAbilities.Items) { - if (Spec.Ability && Spec.Ability->IsActive()) + if (Spec.IsActive()) { CancelAbilitySpec(Spec, Ignore); } diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayCueManager.cpp b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayCueManager.cpp index 6896c9e9069b..5207efef012b 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayCueManager.cpp +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayCueManager.cpp @@ -418,7 +418,7 @@ AGameplayCueNotify_Actor* UGameplayCueManager::GetInstancedCueActor(AActor* Targ } // outside of replays, this should not happen. GC Notifies should not be actually destroyed. - checkf(World->DemoNetDriver, TEXT("Spawned Cue is pending kill or null: %s."), *GetNameSafe(SpawnedCue)); + checkf(World->IsPlayingReplay(), TEXT("Spawned Cue is pending kill or null: %s."), *GetNameSafe(SpawnedCue)); if (PreallocatedList->Num() <= 0) { @@ -496,7 +496,7 @@ void UGameplayCueManager::NotifyGameplayCueActorFinished(AGameplayCueNotify_Acto { if (Actor->IsPendingKill()) { - ensureMsgf(GetWorld()->DemoNetDriver, TEXT("GameplayCueNotify %s is pending kill in ::NotifyGameplayCueActorFinished (and not in network demo)"), *GetNameSafe(Actor)); + ensureMsgf(GetWorld()->IsPlayingReplay(), TEXT("GameplayCueNotify %s is pending kill in ::NotifyGameplayCueActorFinished (and not in network demo)"), *GetNameSafe(Actor)); return; } Actor->bInRecycleQueue = true; @@ -1408,7 +1408,7 @@ void UGameplayCueManager::FlushPendingCues() { if (bHasAuthority) { - PendingCue.OwningComponent->ForceReplication(); + RepInterface->ForceReplication(); if (PendingCue.GameplayCueTags.Num() > 1) { RepInterface->Call_InvokeGameplayCuesExecuted_WithParams(FGameplayTagContainer::CreateFromArray(PendingCue.GameplayCueTags), PendingCue.PredictionKey, PendingCue.CueParameters); @@ -1653,7 +1653,7 @@ void UGameplayCueManager::OnPreReplayScrub(UWorld* World) // among all level collections, this would clear all current preallocated instances from the list, // but there's no need to, and the actor instances would still be around, causing a leak. const FLevelCollection* const DuplicateLevelCollection = World ? World->FindCollectionByType(ELevelCollectionType::DynamicDuplicatedLevels) : nullptr; - if (DuplicateLevelCollection && DuplicateLevelCollection->GetDemoNetDriver() == World->DemoNetDriver) + if (DuplicateLevelCollection && DuplicateLevelCollection->GetDemoNetDriver() == World->GetDemoNetDriver()) { return; } diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayDebuggerCategory_Abilities.cpp b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayDebuggerCategory_Abilities.cpp index 4e6c5a05fb03..ffc4e42bbcf2 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayDebuggerCategory_Abilities.cpp +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayDebuggerCategory_Abilities.cpp @@ -9,6 +9,8 @@ #include "GameplayEffect.h" #include "AbilitySystemGlobals.h" #include "AbilitySystemComponent.h" +#include "Engine/Canvas.h" + FGameplayDebuggerCategory_Abilities::FGameplayDebuggerCategory_Abilities() { @@ -108,9 +110,55 @@ void FGameplayDebuggerCategory_Abilities::CollectData(APlayerController* OwnerPC } } +bool FGameplayDebuggerCategory_Abilities::WrapStringAccordingToViewport(const FString& StrIn, FString& StrOut, FGameplayDebuggerCanvasContext& CanvasContext, float ViewportWitdh) +{ + if (!StrIn.IsEmpty()) + { + // Clamp the Width + ViewportWitdh = FMath::Max(ViewportWitdh, 10.0f); + + float StrWidth = 0.0f, StrHeight = 0.0f; + // Calculate the length(in pixel) of the tags + CanvasContext.MeasureString(StrIn, StrWidth, StrHeight); + + int32 SubDivision = FMath::CeilToInt(StrWidth / ViewportWitdh); + if (SubDivision > 1) + { + // Copy the string + StrOut = StrIn; + const int32 Step = StrOut.Len() / SubDivision; + // Start sub divide if needed + for (int32 i = SubDivision - 1; i > 0; --i) + { + // Insert Line Feed + StrOut.InsertAt(i * Step - 1, '\n'); + } + return true; + } + } + // No need to wrap the text + return false; +} + void FGameplayDebuggerCategory_Abilities::DrawData(APlayerController* OwnerPC, FGameplayDebuggerCanvasContext& CanvasContext) { - CanvasContext.Printf(TEXT("Owned Tags: {yellow}%s"), *DataPack.OwnedTags); + FVector2D ViewPortSize; + GEngine->GameViewport->GetViewportSize( /*out*/ViewPortSize); + + const float BackgroundPadding = 5.0f; + const FVector2D BackgroundSize(ViewPortSize.X - 2 * BackgroundPadding, ViewPortSize.Y); + const FLinearColor BackgroundColor(0.1f, 0.1f, 0.1f, 0.8f); + + // Draw a transparent background so that the text is easier to look at + FCanvasTileItem Background(FVector2D(0.0f, 0.0f), BackgroundSize, BackgroundColor); + Background.BlendMode = SE_BLEND_Translucent; + CanvasContext.DrawItem(Background, CanvasContext.DefaultX - BackgroundPadding, CanvasContext.DefaultY - BackgroundPadding); + + FString WrappedOwnedTagsStr; + // If need to wrap string, use the wrapped string, else use the DataPack one, avoid string copying. + const FString& OwnedTagsRef = WrapStringAccordingToViewport(DataPack.OwnedTags, WrappedOwnedTagsStr, CanvasContext, BackgroundSize.X) ? WrappedOwnedTagsStr : DataPack.OwnedTags; + + CanvasContext.Printf(TEXT("Owned Tags: \n{yellow}%s"), *OwnedTagsRef); AActor* LocalDebugActor = FindLocalDebugActor(); UAbilitySystemComponent* AbilityComp = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(LocalDebugActor); @@ -120,7 +168,12 @@ void FGameplayDebuggerCategory_Abilities::DrawData(APlayerController* OwnerPC, F OwnerTags.Reset(); AbilityComp->GetOwnedGameplayTags(OwnerTags); - CanvasContext.Printf(TEXT("Local Tags: {cyan}%s"), *OwnerTags.ToStringSimple()); + TArray OwnerTagsStrArray = OwnerTags.ToStringsMaxLen(1024); + FString OwnerTagsStr = OwnerTagsStrArray.Num() > 0 ? OwnerTagsStrArray[0] : TEXT(""); + FString WrappedOwnerTagsStr; + + const FString& OwnerTagsStrRef = WrapStringAccordingToViewport(OwnerTagsStr, WrappedOwnerTagsStr, CanvasContext, BackgroundSize.X) ? WrappedOwnerTagsStr : OwnerTagsStr; + CanvasContext.Printf(TEXT("Local Tags: \n{cyan}%s"), *OwnerTagsStrRef); } CanvasContext.Printf(TEXT("Gameplay Effects: {yellow}%d"), DataPack.GameplayEffects.Num()); @@ -150,12 +203,41 @@ void FGameplayDebuggerCategory_Abilities::DrawData(APlayerController* OwnerPC, F } CanvasContext.Printf(TEXT("Gameplay Abilities: {yellow}%d"), DataPack.Abilities.Num()); - for (int32 Idx = 0; Idx < DataPack.Abilities.Num(); Idx++) + int32 HalfNum = FMath::CeilToInt(DataPack.Abilities.Num() / 2.f); + for (int32 Idx = 0; Idx < HalfNum; Idx++) { - const FRepData::FGameplayAbilityDebug& ItemData = DataPack.Abilities[Idx]; + if (2 * Idx + 1 < DataPack.Abilities.Num()) + { + const FRepData::FGameplayAbilityDebug* ItemData[2] = { &DataPack.Abilities[2 * Idx], &DataPack.Abilities[2 * Idx + 1] }; + FString Abilities[2]; + float WidthSum = 0.0f; + for (size_t j = 0; j < 2; ++j) + { + Abilities[j] = FString::Printf(TEXT("\t{yellow}%s {grey}source:{white}%s {grey}level:{white}%d {grey}active:{white}%s"), + *ItemData[j]->Ability, *ItemData[j]->Source, ItemData[j]->Level, ItemData[j]->bIsActive ? TEXT("YES") : TEXT("NO")); + float TempWidth = 0.0f; + float TempHeight = 0.0f; + CanvasContext.MeasureString(Abilities[j], TempWidth, TempHeight); + WidthSum += TempWidth; + } - CanvasContext.Printf(TEXT("\t{yellow}%s {grey}source:{white}%s {grey}level:{white}%d {grey}active:{white}%s"), - *ItemData.Ability, *ItemData.Source, ItemData.Level, ItemData.bIsActive ? TEXT("YES") : TEXT("no")); + if (WidthSum < BackgroundSize.X) + { + CanvasContext.Print(Abilities[0].Append(Abilities[1])); + } + else + { + CanvasContext.Print(Abilities[0]); + CanvasContext.Print(Abilities[1]); + } + } + else + { + const FRepData::FGameplayAbilityDebug& ItemData = DataPack.Abilities[2 * Idx]; + // Only display one + CanvasContext.Printf(TEXT("\t{yellow}%s {grey}source:{white}%s {grey}level:{white}%d {grey}active:{white}%s"), + *ItemData.Ability, *ItemData.Source, ItemData.Level, ItemData.bIsActive ? TEXT("YES") : TEXT("NO")); + } } } diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayDebuggerCategory_Abilities.h b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayDebuggerCategory_Abilities.h index 8457f0614bf2..ed68c6d79c4b 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayDebuggerCategory_Abilities.h +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayDebuggerCategory_Abilities.h @@ -48,6 +48,8 @@ protected: void Serialize(FArchive& Ar); }; FRepData DataPack; + + bool WrapStringAccordingToViewport(const FString& iStr, FString& oStr, FGameplayDebuggerCanvasContext& CanvasContext, float ViewportWitdh); }; #endif // WITH_GAMEPLAY_DEBUGGER diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayEffect.cpp b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayEffect.cpp index ee709567667f..781aef3a39ca 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayEffect.cpp +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayEffect.cpp @@ -3821,7 +3821,7 @@ bool FActiveGameplayEffectsContainer::NetDeltaSerialize(FNetDeltaSerializeInfo& UNetConnection* Connection = Client->GetConnection(); // Even in mixed mode, we should always replicate out to client side recorded replays so it has all information. - if (Connection->GetDriver()->NetDriverName != NAME_DemoNetDriver || IsNetAuthority()) + if (!Connection->IsReplay() || IsNetAuthority()) { // In mixed mode, we only want to replicate to the owner of this channel, minimal replication // data will go to everyone else. diff --git a/Engine/Plugins/Runtime/HTTPChunkInstaller/Source/Private/HTTPChunkInstaller.cpp b/Engine/Plugins/Runtime/HTTPChunkInstaller/Source/Private/HTTPChunkInstaller.cpp index 1b8a4747c874..8058d949ac5e 100644 --- a/Engine/Plugins/Runtime/HTTPChunkInstaller/Source/Private/HTTPChunkInstaller.cpp +++ b/Engine/Plugins/Runtime/HTTPChunkInstaller/Source/Private/HTTPChunkInstaller.cpp @@ -21,6 +21,7 @@ #include "HAL/RunnableThread.h" #include "Misc/CommandLine.h" #include "Modules/ModuleManager.h" +#include "Stats/Stats.h" #define LOCTEXT_NAMESPACE "HTTPChunkInstaller" @@ -392,6 +393,8 @@ public: /** Used to check that async tasks have completed and can be completed */ virtual void Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FOnlineTitleFileHttp_Tick); + TArray ItemsToRemove; ItemsToRemove.Reserve(AsyncLocalReads.Num()); diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionCues.cpp b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionCues.cpp index bab4f5f872a7..7c7e3907e14e 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionCues.cpp +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionCues.cpp @@ -200,7 +200,7 @@ void TestCues() // ------------------------------------- FNetBitWriter TempWriter(1024 << 3); - ServerDispatcher.NetSerializeSavedCues(TempWriter, ENetSimCueReplicationTarget::All, true); + ServerDispatcher.NetSendSavedCues(TempWriter, ENetSimCueReplicationTarget::All, true); // ------------------------------------- // Receive @@ -208,7 +208,8 @@ void TestCues() FNetBitReader TempReader(nullptr, TempWriter.GetData(), TempWriter.GetNumBits()); TNetSimCueDispatcher ClientDispatcher; - ClientDispatcher.NetSerializeSavedCues(TempReader, ENetSimCueReplicationTarget::All, true); + ClientDispatcher.SetReceiveReplicationTarget(ENetSimCueReplicationTarget::All); + ClientDispatcher.NetRecvSavedCues(TempReader, true, 0, 0); TestHandlerChild MyObject; diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionPhysics.cpp b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionPhysics.cpp index cbe553c31a8a..a317c0b2ffec 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionPhysics.cpp +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionPhysics.cpp @@ -6,37 +6,24 @@ #include "Components/PrimitiveComponent.h" // ------------------------------------------------------------------------------------------------------------------------- -// Interpolation related functions. These currently require calls to the UPrimitiveComponent. -// It may be possible one day to make the calls directly to the FPhysicsActorHandle, but for now kinematic bodies are -// a one way street. The possible physics calls are commented out in the function bodies. -// +// Interpolation related functions. These require calls to the UPrimitiveComponent and cannot be implemented via FBodyInstance // // If you are landing here with a nullptr Driver, see notes in FNetworkPredictionDriverBase::SafeCastDriverToPrimitiveComponent // ------------------------------------------------------------------------------------------------------------------------- -void FNetworkPredictionPhysicsState::BeginInterpolation(UPrimitiveComponent* Driver, FPhysicsActorHandle ActorHandle) +void FNetworkPredictionPhysicsState::BeginInterpolation(UPrimitiveComponent* Driver) { npCheckSlow(Driver); Driver->SetSimulatePhysics(false); - - // FPhysicsCommand::ExecuteWrite(ActorHandle, [&](const FPhysicsActorHandle& Actor) - // { - // FPhysicsInterface::SetIsKinematic_AssumesLocked(Actor, true); - // }); } -void FNetworkPredictionPhysicsState::EndInterpolation(UPrimitiveComponent* Driver, FPhysicsActorHandle ActorHandle) +void FNetworkPredictionPhysicsState::EndInterpolation(UPrimitiveComponent* Driver) { npCheckSlow(Driver); Driver->SetSimulatePhysics(true); - - // FPhysicsCommand::ExecuteWrite(ActorHandle, [&](const FPhysicsActorHandle& Actor) - // { - // FPhysicsInterface::SetIsKinematic_AssumesLocked(Actor, false); - // }); } -void FNetworkPredictionPhysicsState::FinalizeInterpolatedPhysics(UPrimitiveComponent* Driver, FPhysicsActorHandle ActorHandle, FNetworkPredictionPhysicsState* InterpolatedState) +void FNetworkPredictionPhysicsState::FinalizeInterpolatedPhysics(UPrimitiveComponent* Driver, FNetworkPredictionPhysicsState* InterpolatedState) { npCheckSlow(Driver); npCheckSlow(InterpolatedState); @@ -46,9 +33,4 @@ void FNetworkPredictionPhysicsState::FinalizeInterpolatedPhysics(UPrimitiveCompo npEnsureSlow(InterpolatedState->Rotation.ContainsNaN() == false); Driver->SetWorldLocationAndRotation(InterpolatedState->Location, InterpolatedState->Rotation, false); - - // FPhysicsCommand::ExecuteWrite(ActorHandle, [NewTransform](const FPhysicsActorHandle& Actor) - // { - // FPhysicsInterface::SetKinematicTarget_AssumesLocked(Actor, NewTransform); - // }); } \ No newline at end of file diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionPhysicsComponent.cpp b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionPhysicsComponent.cpp index d0f894c8e199..ace58a40ca6a 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionPhysicsComponent.cpp +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionPhysicsComponent.cpp @@ -54,7 +54,7 @@ void UNetworkPredictionPhysicsComponent::InitializeComponent() ReplicationProxy.Init(&NetworkPredictionProxy, EReplicationProxyTarget::SimulatedProxy); // Init NP proxy with generic physics def - NetworkPredictionProxy.Init(GetWorld(), GetReplicationProxies(), nullptr, UpdatedPrimitive, PhysicsActorHandle); + NetworkPredictionProxy.Init(GetWorld(), GetReplicationProxies(), nullptr, UpdatedPrimitive); CheckOwnerRoleChange(); } diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionWorldManager.cpp b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionWorldManager.cpp index 9a8222f1bf96..a277a3167a57 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionWorldManager.cpp +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Private/NetworkPredictionWorldManager.cpp @@ -11,6 +11,9 @@ #include "ChaosSolversModule.h" #include "ChaosSolvers/Public/RewindData.h" +// Do extra checks to make sure Physics and GameThread (PrimitiveComponent) are in sync at verious points in the rollback process +#define NP_ENSURE_PHYSICS_GT_SYNC !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + UNetworkPredictionWorldManager* UNetworkPredictionWorldManager::ActiveInstance=nullptr; // ----------------------------------------------------------------------------------------------- @@ -150,6 +153,9 @@ void UNetworkPredictionWorldManager::ReconcileSimulationsPostNetworkUpdate() Chaos::EThreadingModeTemp PreThreading = Chaos::EThreadingModeTemp::SingleThread; + // --------------------------------------------------- + // Rollback physics to local historic state (corrections not injected yet) + // --------------------------------------------------- if (bDoPhysics) { PhysicsFrame = RollbackFrame + FixedTickState.PhysicsOffset; @@ -159,16 +165,9 @@ void UNetworkPredictionWorldManager::ReconcileSimulationsPostNetworkUpdate() Physics.Solver->SetThreadingMode_External(Chaos::EThreadingModeTemp::SingleThread); } - FixedTickState.PendingFrame = RollbackFrame; - const int32 ServerRollbackFrame = RollbackFrame + FixedTickState.Offset; - const int32 BeginRollbackTimeMS = ServerRollbackFrame * FixedTickState.FixedStepMS; - - for (TUniquePtr& Ptr : Services.FixedRollback.Array) - { - Ptr->BeginRollback(RollbackFrame, BeginRollbackTimeMS, ServerRollbackFrame); - } + bool bFirstStep = true; - // Do rollback if necessary + // Do rollback as necessary for (int32 Frame=RollbackFrame; Frame < EndFrame; ++Frame) { FixedTickState.PendingFrame = Frame; @@ -178,15 +177,33 @@ void UNetworkPredictionWorldManager::ReconcileSimulationsPostNetworkUpdate() const int32 ServerInputFrame = Frame + FixedTickState.Offset; UE_NP_TRACE_PUSH_TICK(Step.TotalSimulationTime, FixedTickState.FixedStepMS, Step.Frame); + // Everyone must apply corrections and flush as necessary before anyone runs the next sim tick + // bFirstStep will indicate that even if they don't have a correction, they need to rollback their historic state for (TUniquePtr& Ptr : Services.FixedRollback.Array) { - Ptr->StepRollback(Step, ServiceStep, FixedTickState.Offset); + Ptr->PreStepRollback(Step, ServiceStep, FixedTickState.Offset, bFirstStep); } + EnsurePhysicsGTSync(TEXT("PreStepRollback")); + // Run Sim ticks + for (TUniquePtr& Ptr : Services.FixedRollback.Array) + { + Ptr->StepRollback(Step, ServiceStep); + } + EnsurePhysicsGTSync(TEXT("PreResimPhysics")); + + // Advance physics if (bDoPhysics) { AdvancePhysicsResimFrame(PhysicsFrame); + for (TUniquePtr& Ptr : Services.FixedPhysics.Array) + { + Ptr->PostResimulate(&FixedTickState); + } } + + EnsurePhysicsGTSync(TEXT("PostResimulate")); + bFirstStep = false; } FixedTickState.PendingFrame = EndFrame; @@ -194,11 +211,6 @@ void UNetworkPredictionWorldManager::ReconcileSimulationsPostNetworkUpdate() if (bDoPhysics) { Physics.Solver->SetThreadingMode_External(PreThreading); - - for (TUniquePtr& Ptr : Services.FixedPhysics.Array) - { - Ptr->PostResimulate(&FixedTickState); - } } } else if (RollbackFrame == FixedTickState.PendingFrame) @@ -460,18 +472,16 @@ void UNetworkPredictionWorldManager::BeginNewSimulationFrame(UWorld* InWorld, EL // ------------------------------------------------------------------------- const int32 FixedTotalSimTimeMS = FixedTickState.GetTotalSimTimeMS(); const int32 FixedServerFrame = FixedTickState.PendingFrame + FixedTickState.Offset; - const int32 FixedServerConfirmedFrame = FixedTickState.ConfirmedFrame + FixedTickState.Offset;; for (TUniquePtr& Ptr : Services.FixedFinalize.Array) { - Ptr->FinalizeFrame(DeltaTimeSeconds, FixedServerFrame, FixedTotalSimTimeMS, FixedServerConfirmedFrame); + Ptr->FinalizeFrame(DeltaTimeSeconds, FixedServerFrame, FixedTotalSimTimeMS, FixedTickState.FixedStepMS); } const int32 IndependentTotalSimTimeMS = VariableTickState.Frames[VariableTickState.PendingFrame].TotalMS; const int32 IndependentFrame = VariableTickState.PendingFrame; - const int32 IndependentConfirmedFrame = VariableTickState.ConfirmedFrame; for (TUniquePtr& Ptr : Services.IndependentLocalFinalize.Array) { - Ptr->FinalizeFrame(DeltaTimeSeconds, IndependentFrame, IndependentTotalSimTimeMS, IndependentConfirmedFrame); + Ptr->FinalizeFrame(DeltaTimeSeconds, IndependentFrame, IndependentTotalSimTimeMS, 0); } for (TUniquePtr& Ptr : Services.IndependentRemoteFinalize.Array) @@ -554,6 +564,16 @@ void UNetworkPredictionWorldManager::SetUsingPhysics() } } +void UNetworkPredictionWorldManager::EnsurePhysicsGTSync(const TCHAR* Context) const +{ +#if NP_ENSURE_PHYSICS_GT_SYNC + for (const TUniquePtr& Ptr : Services.FixedPhysics.Array) + { + Ptr->EnsureDataInSync(Context); + } +#endif +} + ENetworkPredictionTickingPolicy UNetworkPredictionWorldManager::PreferredDefaultTickingPolicy() const { return Settings.PreferredTickingPolicy; diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionCues.h b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionCues.h index 93cb9b1648db..45d3f41f2b83 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionCues.h +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionCues.h @@ -4,6 +4,7 @@ #include "Engine/EngineTypes.h" #include "NetworkPredictionCueTraits.h" #include "NetworkPredictionCheck.h" +#include "Misc/StringBuilder.h" /*============================================================================= Networked Simulation Cues @@ -43,6 +44,9 @@ NETWORKPREDICTION_API DECLARE_LOG_CATEGORY_EXTERN(LogNetworkPredictionCues, Disp using FNetSimCueTypeId = NETSIMCUE_TYPEID_TYPE; +template +struct FNetworkPredictionDriver; + // ---------------------------------------------------------------------------------------------------------------------------------------------- // Wrapper: wraps the actual user NetSimCue. We want to avoid virtualizing functions on the actual user types. // ---------------------------------------------------------------------------------------------------------------------------------------------- @@ -324,12 +328,12 @@ public: } } - void Dispatch(FSavedCue& SavedCue, TCueHandler& Handler, const int32& DispatchTime) + void Dispatch(FSavedCue& SavedCue, TCueHandler& Handler, const int32& TimeSinceInvocation) { if (FCueTypeInfo* TypeInfo = CueTypeInfoMap.Find(SavedCue.ID)) { check(TypeInfo->Dispatch); - TypeInfo->Dispatch(SavedCue.CueInstance.Get(), Handler, {DispatchTime - SavedCue.Time, SavedCue.bAllowRollback ? &SavedCue.Callbacks : nullptr }); + TypeInfo->Dispatch(SavedCue.CueInstance.Get(), Handler, {TimeSinceInvocation, SavedCue.bAllowRollback ? &SavedCue.Callbacks : nullptr }); } else { @@ -372,6 +376,8 @@ template struct TCueDispatcherTraits : public FCueDispatcherTraitsBa // Non-templated, "networking model independent" base: this is what the pure simulation code gets to invoke cues. struct FNetSimCueDispatcher { + virtual ~FNetSimCueDispatcher() = default; + // Invoke - this is how to invoke a cue from simulation code. This will construct the CueType T emplace in the saved cue record. // // Best way to call: @@ -414,6 +420,7 @@ struct FNetSimCueDispatcher // In resimulate case, we have to see if we already predicted it if (Context.TickContext == ESimulationTickContext::Resimulate) + { npEnsure(RollbackFrame >= 0 && RollbackFrame <= Context.Frame); @@ -441,7 +448,7 @@ struct FNetSimCueDispatcher else { // Not resimulate case is simple: construct the new cue emplace in the appropriate list - auto& SavedCue = GetBuffer(bTransient).Emplace_GetRef(T::ID, Context.Frame, Context.CurrentSimTime, bAllowRollback, bNetConfirmed, bSupportsResimulate, new TNetSimCueWrapper(Forward(Args)...)); + auto& SavedCue = GetBuffer(bTransient).Emplace_GetRef(T::ID, Context.Frame, Context.CurrentSimTime, bAllowRollback, bNetConfirmed, bSupportsResimulate, new TNetSimCueWrapper(Forward(Args)...)); UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Invoking Cue %s. Context: %d. Transient: %d. bAllowRollback: %d. bNetConfirmed: %d. Time: %d"), *GetDebugName(), *SavedCue.GetDebugName(), Context.TickContext, bTransient, bAllowRollback, bNetConfirmed, Context.CurrentSimTime); } } @@ -452,7 +459,7 @@ struct FNetSimCueDispatcher } } - TFunction GetDebugName = []() { return TEXT("Unset"); }; + virtual FString GetDebugName() const = 0; protected: @@ -478,134 +485,139 @@ protected: }; // Templated cue dispatcher that can be specialized per networking model definition. This is what the system actually uses internally, but is not exposed to user code. -template +template struct TNetSimCueDispatcher : public FNetSimCueDispatcher { + using DriverType = typename ModelDef::Driver; + // Serializes all saved cues // bSerializeFrameNumber - whether to serialize Frame# or Time to this target. Frame is more accurate but some cases require time based replication. - void NetSerializeSavedCues(FArchive& Ar, ENetSimCueReplicationTarget ReplicationMask, bool bSerializeFrameNumber) + void NetSendSavedCues(FArchive& Ar, ENetSimCueReplicationTarget ReplicationMask, bool bSerializeFrameNumber) { - if (Ar.IsSaving()) + npCheckSlow(Ar.IsSaving()); + + // FIXME: requires two passes to count how many elements are valid for this replication mask. + // We could count this as saved cues are added or possibly modify the bitstream after writing the elements (tricky and would require casting to FNetBitWriter which feels real bad) + FNetSimCueTypeId NumCues = 0; + for (FSavedCue& SavedCue : SavedCues) { - // FIXME: requires two passes to count how many elements are valid for this replication mask. - // We could count this as saved cues are added or possibly modify the bitstream after writing the elements (tricky and would require casting to FNetBitWriter which feels real bad) - FNetSimCueTypeId NumCues = 0; - for (FSavedCue& SavedCue : SavedCues) + if (EnumHasAnyFlags(SavedCue.ReplicationTarget, ReplicationMask)) { - if (EnumHasAnyFlags(SavedCue.ReplicationTarget, ReplicationMask)) - { - NumCues++; - } - } - - Ar << NumCues; - - for (FSavedCue& SavedCue : SavedCues) - { - if (EnumHasAnyFlags(SavedCue.ReplicationTarget, ReplicationMask)) - { - SavedCue.NetSerialize(Ar, bSerializeFrameNumber); - } + NumCues++; } } - else + + Ar << NumCues; + + for (FSavedCue& SavedCue : SavedCues) { - FNetSimCueTypeId NumCues; - Ar << NumCues; - - // This is quite inefficient right now. - // -We are replicating cues in the last X seconds/frames (ReplicationWindow) redundantly - // -Client has to deserialize them (+ heap allocation) and check for uniqueness (have they already processed) - // -If already processed (quite common), they are thrown out. - // -Would be better if we maybe serialized "net hash" and could skip ahead in the bunch if already processed - - int32 StartingNum = SavedCues.Num(); - - for (int32 CueIdx=0; CueIdx < NumCues; ++CueIdx) + if (EnumHasAnyFlags(SavedCue.ReplicationTarget, ReplicationMask)) { - FSavedCue SerializedCue(true); - SerializedCue.NetSerialize(Ar, bSerializeFrameNumber); + SavedCue.NetSerialize(Ar, bSerializeFrameNumber); + } + } + + } - // Decide if we should accept the cue: - // ReplicationTarget: Cues can be set to only replicate to interpolators - if ( EnumHasAnyFlags(SerializedCue.ReplicationTarget, ReplicationMask) == false ) + void NetRecvSavedCues(FArchive& Ar, const bool bSerializeFrameNumber, const int32 InLastRecvFrame, const int32 InLastRecvTime) + { + npCheckSlow(Ar.IsLoading()); + + FNetSimCueTypeId NumCues; + Ar << NumCues; + + // This is quite inefficient right now. + // -We are replicating cues in the last X seconds/frames (ReplicationWindow) redundantly + // -Client has to deserialize them (+ heap allocation) and check for uniqueness (have they already processed) + // -If already processed (quite common), they are thrown out. + // -Would be better if we maybe serialized "net hash" and could skip ahead in the bunch if already processed + + int32 StartingNum = SavedCues.Num(); + + for (int32 CueIdx=0; CueIdx < NumCues; ++CueIdx) + { + FSavedCue SerializedCue(true); + SerializedCue.NetSerialize(Ar, bSerializeFrameNumber); + + // Decide if we should accept the cue: + // ReplicationTarget: Cues can be set to only replicate to interpolators + if (EnumHasAnyFlags(SerializedCue.ReplicationTarget, RecvReplicationMask) == false) + { + UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Discarding replicated NetSimCue %s that is not intended for us. CueMask: %d. Our Mask: %d"), *GetDebugName(), *SerializedCue.GetDebugName(), SerializedCue.ReplicationTarget, RecvReplicationMask); + continue; + } + + // Due to redundant sending, we may get frames older than what we've already processed. Early out rather than searching for them in saved cues. + // This also ensures that if we get a very stale cue that we have pruned locally, we won't incorrectly invoke it again. + if (SerializedCue.Frame != INDEX_NONE) + { + if (SerializedCue.Frame <= LastRecvFrame) { - UE_LOG(LogNetworkPredictionCues, Log, TEXT("Discarding replicated NetSimCue %s that is not intended for us. CueMask: %d. Our Mask: %d"), *SerializedCue.GetDebugName(), SerializedCue.ReplicationTarget, ReplicationMask); + UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Discarding replicated NetSimCue %s because it is older (%d) than confirmed frame (%d). CueMask: %d. Our Mask: %d"), *GetDebugName(), *SerializedCue.GetDebugName(), SerializedCue.Frame, LastRecvFrame, SerializedCue.ReplicationTarget, RecvReplicationMask); continue; } - - if (SerializedCue.Frame != INDEX_NONE && SerializedCue.Frame <= UserConfirmedFrame) - { - UE_LOG(LogNetworkPredictionCues, Log, TEXT("Discarding replicated NetSimCue %s because it is older (%d) than confirmed frame (%d). CueMask: %d. Our Mask: %d"), *SerializedCue.GetDebugName(), SerializedCue.Frame, UserConfirmedFrame, SerializedCue.ReplicationTarget, ReplicationMask); - continue; - } - + } + else if (SerializedCue.Time <= LastRecvMS) + { + UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Discarding replicated NetSimCue %s because it is older (%d) than confirmed TimeMS (%d). CueMask: %d. Our Mask: %d"), *GetDebugName(), *SerializedCue.GetDebugName(), SerializedCue.Frame, LastRecvFrame, SerializedCue.ReplicationTarget, RecvReplicationMask); + continue; + } - // Uniqueness: have we already received/predicted it? - // Note: we are basically ignoring invocation time when matching right now. This could potentially be a trait of the cue if needed. - // This could create issues if a cue is invoked several times in quick succession, but that can be worked around with arbitrary counter parameters on the cue itself (to force NetUniqueness) - bool bUniqueCue = true; - for (int32 ExistingIdx=0; ExistingIdx < StartingNum; ++ExistingIdx) + // Uniqueness: have we already received/predicted it? + // Note: we are basically ignoring invocation time when matching right now. This could potentially be a trait of the cue if needed. + // This could create issues if a cue is invoked several times in quick succession, but that can be worked around with arbitrary counter parameters on the cue itself (to force NetUniqueness) + bool bUniqueCue = true; + for (int32 ExistingIdx=0; ExistingIdx < StartingNum; ++ExistingIdx) + { + FSavedCue& ExistingCue = SavedCues[ExistingIdx]; + if (SerializedCue.NetIdentical(ExistingCue)) { - FSavedCue& ExistingCue = SavedCues[ExistingIdx]; - if (SerializedCue.NetIdentical(ExistingCue)) - { - // These cues are not unique ("close enough") so we are skipping receiving this one - UE_LOG(LogNetworkPredictionCues, Log, TEXT("Discarding replicated NetSimCue %s because we've already processed it. (Matched %s)"), *SerializedCue.GetDebugName(), *ExistingCue.GetDebugName()); - bUniqueCue = false; - ExistingCue.bNetConfirmed = true; - break; - } - } - - if (bUniqueCue) - { - auto& SavedCue = SavedCues.Emplace_GetRef(MoveTemp(SerializedCue)); - UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Received !NetIdentical Cue: %s. (Num replicated cue sent this bunch: %d. UserConfirmedFrame: %d)."), *GetDebugName(), *SerializedCue.GetDebugName(), NumCues, UserConfirmedFrame); + // These cues are not unique ("close enough") so we are skipping receiving this one + UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Discarding replicated NetSimCue %s because we've already processed it. (Matched %s)"), *GetDebugName(), *SerializedCue.GetDebugName(), *ExistingCue.GetDebugName()); + bUniqueCue = false; + ExistingCue.bNetConfirmed = true; + break; } } + + if (bUniqueCue) + { + auto& SavedCue = SavedCues.Emplace_GetRef(MoveTemp(SerializedCue)); + UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Received !NetIdentical Cue: %s. (Num replicated cue sent this bunch: %d. LastRecvFrame: %d. Our Mask: %d)."), *GetDebugName(), *SerializedCue.GetDebugName(), NumCues, LastRecvFrame, RecvReplicationMask); + } } + + LastRecvFrame = InLastRecvFrame; + LastRecvMS = InLastRecvTime; } // Dispatches and prunes saved/transient cues template - void DispatchCueRecord(T& Handler, const int32 SimFrame, const int32 CurrentSimTime, const int32 InConfirmedFrame) + void DispatchCueRecord(T& Handler, const int32 SimFrame, const int32 CurrentSimTime, const int32 FixedTimeStepMS) { - auto CalcPruneFrame = [&]() - { - int32 Frame = SimFrame - TCueDispatcherTraits::ReplicationWindowFrames(); - if (InConfirmedFrame != INDEX_NONE) - { - Frame = FMath::Min(Frame, InConfirmedFrame); - } - return Frame; - }; + auto CalcPrune = [](int32 Head, int32 Confirm, int32 Window) { return Confirm > 0 ? FMath::Min(Confirm, Head - Window) : Head - Window; }; - const int32 SavedCuePruneTimeMS = CurrentSimTime - TCueDispatcherTraits::ReplicationWindowMS(); - const int32 SavedCuePruneFrame = CalcPruneFrame(); - - npEnsureSlow(InConfirmedFrame >= SavedCuePruneFrame || InConfirmedFrame == INDEX_NONE); + const int32 SavedCuePruneTimeMS = CalcPrune(CurrentSimTime, LastRecvMS, TCueDispatcherTraits::ReplicationWindowMS()); + const int32 SavedCuePruneFrame = CalcPrune(SimFrame, LastRecvFrame, TCueDispatcherTraits::ReplicationWindowFrames()); // Prune cues prior to this idx int32 SavedCuePruneIdx = INDEX_NONE; - UserConfirmedFrame = InConfirmedFrame; - // ------------------------------------------------------------------------ // Rollback events if necessary // Fixme - this code was written for clarity, it could be sped up considerably by taking advantage of sorting by time, or keeping acceleration lists for this type of pruning // ------------------------------------------------------------------------ - if (UserConfirmedFrame != INDEX_NONE) + if (LastRecvFrame != INDEX_NONE) { // Look for cues that should have been matched by now, but were not for (auto It = SavedCues.CreateIterator(); It; ++It) { FSavedCue& SavedCue = *It; npEnsureSlow(SavedCue.Frame != INDEX_NONE); - if (!SavedCue.bNetConfirmed && SavedCue.Frame <= UserConfirmedFrame) + if (!SavedCue.bNetConfirmed && SavedCue.Frame <= LastRecvFrame) { - UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Calling OnRollback for SavedCue NetSimCue %s. Cue has not been matched but it <= UserConfirmedFrame %d."), *GetDebugName(), *SavedCue.GetDebugName(), UserConfirmedFrame); + UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Calling OnRollback for SavedCue NetSimCue %s. Cue has not been matched but it <= LastRecvFrame %d."), *GetDebugName(), *SavedCue.GetDebugName(), LastRecvFrame); SavedCue.Callbacks.OnRollback.Broadcast(); SavedCues.RemoveAt(It.GetIndex(), 1, false); } @@ -646,17 +658,38 @@ struct TNetSimCueDispatcher : public FNetSimCueDispatcher if (!SavedCue.bDispatched) { - // Prefer frame comparison if SavedCue has one - const bool bDispatchCue = (SavedCue.Frame == INDEX_NONE) ? (SavedCue.Time <= CurrentSimTime) : (SavedCue.Frame <= SimFrame); - if (bDispatchCue) + bool bDispatchedCue = false; + int32 TimeSinceInvocationMS = 0; + + if (SavedCue.Frame != INDEX_NONE) { - UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s Dispatching NetSimCue {Frame: %d, Time: %d}"), *GetDebugName(), *SavedCue.GetDebugName(), SavedCue.Frame, SavedCue.Time); - SavedCue.bDispatched = true; - TCueDispatchTable::Get().Dispatch(SavedCue, Handler, CurrentSimTime); + // Frame based comparison (prefer if available) + if (SavedCue.Frame <= SimFrame) + { + const int32 FramesSinceInvoke = SimFrame - SavedCue.Frame; + TimeSinceInvocationMS = FramesSinceInvoke * FixedTimeStepMS; + bDispatchedCue = true; + } } else { - UE_LOG(LogNetworkPredictionCues, Log, TEXT("Withholding Cue %s. %d/%d > %d/%d"), *SavedCue.GetDebugName(), SavedCue.Frame, SavedCue.Time, SimFrame, CurrentSimTime); + // Time based comparison + if (SavedCue.Time <= CurrentSimTime) + { + TimeSinceInvocationMS = CurrentSimTime - SavedCue.Time; + bDispatchedCue = true; + } + } + + if (bDispatchedCue) + { + UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s Dispatching NetSimCue %s {Frame: %d, Time: %d}"), *GetDebugName(), *SavedCue.GetDebugName(), SavedCue.Frame, SavedCue.Time); + SavedCue.bDispatched = true; + TCueDispatchTable::Get().Dispatch(SavedCue, Handler, TimeSinceInvocationMS); + } + else + { + UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s Withholding Cue %s. %d/%d > %d/%d"), *GetDebugName(), *SavedCue.GetDebugName(), SavedCue.Frame, SavedCue.Time, SimFrame, CurrentSimTime); } } } @@ -679,7 +712,7 @@ struct TNetSimCueDispatcher : public FNetSimCueDispatcher for (int32 i=0; i <= SavedCuePruneIdx; ++i) { UE_CLOG(!SavedCues[i].bDispatched, LogNetworkPredictionCues, Warning, TEXT("Non-Dispatched Cue is about to be pruned! %s. SavedCuePruneTimeMS: %d. SavedCuePruneFrame: %d"), *SavedCues[i].GetDebugName(), SavedCuePruneTimeMS, SavedCuePruneFrame); - UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Pruning Cue %s. Invoke Time: %d. Current Time: %d."), *GetDebugName(), *SavedCues[i].GetDebugName(), SavedCues[i].Time, CurrentSimTime); + UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Pruning Cue %s. Prune: (%d/%d). Invoked: (%d/%d). Current Time: %d."), *GetDebugName(), *SavedCues[i].GetDebugName(), SavedCuePruneFrame, SavedCuePruneTimeMS, SavedCues[i].Frame, SavedCues[i].Time, CurrentSimTime); } #endif @@ -708,7 +741,7 @@ struct TNetSimCueDispatcher : public FNetSimCueDispatcher if (SavedCue.bResimulates && SavedCue.Frame >= InRollbackFrame) { SavedCue.bPendingResimulateRollback = true; - UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Marking %s bPendingResimulateRollback."), *GetDebugName(), *SavedCue.GetDebugName()); + UE_LOG(LogNetworkPredictionCues, Log, TEXT("%s. Marking %s bPendingResimulateRollback. (RollbackFrame: %d/%d)"), *GetDebugName(), *SavedCue.GetDebugName(), RollbackFrame, InRollbackFrame); } } } @@ -717,9 +750,33 @@ struct TNetSimCueDispatcher : public FNetSimCueDispatcher void PushContext(const FContext& InContext) { Context = InContext; } void PopContext() { Context = FContext(); } + // Set which cues we should accept locally (if they get sent to us) + void SetReceiveReplicationTarget(ENetSimCueReplicationTarget InReplicationMask) + { + RecvReplicationMask = InReplicationMask; + } + + DriverType* Driver = nullptr; + FString GetDebugName() const final override + { + FString Str; + if (Driver) + { + TStringBuilder<128> Builder; + FNetworkPredictionDriver::GetDebugString(Driver, Builder); + + Str = FString(Builder.ToString()); + } + + return Str; + } + private: + + ENetSimCueReplicationTarget RecvReplicationMask; - mutable int32 UserConfirmedFrame = INDEX_NONE; // (If set) latest confirmed simulation time. If not set, we assume we are authority and will never rollback. + int32 LastRecvFrame = INDEX_NONE; + int32 LastRecvMS = 0; }; // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionDriver.h b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionDriver.h index 23b8777780f1..aa5eba4d9003 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionDriver.h +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionDriver.h @@ -10,7 +10,9 @@ #include "NetworkPredictionStateView.h" #include "Misc/StringBuilder.h" #include "GameFramework/Actor.h" +#include "Components/PrimitiveComponent.h" +struct FBodyInstance; namespace Chaos { @@ -159,12 +161,12 @@ struct FNetworkPredictionDriverBase static void GetDebugString(AActor* Actor, FStringBuilderBase& Builder) { - Builder.Appendf(TEXT("%s. Driver: %s. Role: %s."), ModelDef::GetName(), *Actor->GetPathName(), *UEnum::GetValueAsString(TEXT("Engine.ENetRole"), Actor->GetLocalRole())); + Builder.Appendf(TEXT("%s %s"), ModelDef::GetName(), *UEnum::GetValueAsString(TEXT("Engine.ENetRole"), Actor->GetLocalRole())); } static void GetDebugString(UActorComponent* ActorComp, FStringBuilderBase& Builder) { - Builder.Appendf(TEXT("%s. Driver: %s. Role: %s."), ModelDef::GetName(), *ActorComp->GetPathName(), *UEnum::GetValueAsString(TEXT("Engine.ENetRole"), ActorComp->GetOwnerRole())); + Builder.Appendf(TEXT("%s %s"), ModelDef::GetName(), *UEnum::GetValueAsString(TEXT("Engine.ENetRole"), ActorComp->GetOwnerRole())); } static void GetDebugString(void* NoDriver, FStringBuilderBase& Builder) @@ -172,6 +174,21 @@ struct FNetworkPredictionDriverBase Builder.Append(ModelDef::GetName()); } + static void GetDebugStringFull(AActor* Actor, FStringBuilderBase& Builder) + { + Builder.Appendf(TEXT("%s. Driver: %s. Role: %s."), ModelDef::GetName(), *Actor->GetPathName(), *UEnum::GetValueAsString(TEXT("Engine.ENetRole"), Actor->GetLocalRole())); + } + + static void GetDebugStringFull(UActorComponent* ActorComp, FStringBuilderBase& Builder) + { + Builder.Appendf(TEXT("%s. Driver: %s. Role: %s."), ModelDef::GetName(), *ActorComp->GetPathName(), *UEnum::GetValueAsString(TEXT("Engine.ENetRole"), ActorComp->GetOwnerRole())); + } + + static void GetDebugStringFull(void* NoDriver, FStringBuilderBase& Builder) + { + Builder.Append(ModelDef::GetName()); + } + static void GetTraceString(UActorComponent* ActorComp, FStringBuilderBase& Builder) { Builder.Appendf(TEXT("%s: %s %s"), ModelDef::GetName(), *ActorComp->GetOwner()->GetName(), *ActorComp->GetName()); @@ -338,6 +355,48 @@ struct FNetworkPredictionDriverBase npCheckf(!HasNpState(), TEXT("No FinalizeFrame implementation found. Implement DriverType::FinalizeFrame or ModelDef::FinalizeFrame")); } + // ----------------------------------------------------------------------------------------------------------------------------------- + // RestoreFrame + // + // Called prior to beginning rollback frames. This instance should put itself in whatever state it needs to be in for resimulation to + // run. In practice this should mean getting right collision+component states in sync so that any scene queries will get the correct + // data. + // + // This can be automated for physics (the PhysicsState can generically marshal the data). We can't do this for kinematic sims. + // + // + // ----------------------------------------------------------------------------------------------------------------------------------- + + static void RestoreFrame(DriverType* Driver, const SyncType* SyncState, const AuxType* AuxState) + { + FNetworkPredictionDriver::MarshalPhysicsToComponent(Driver); + FNetworkPredictionDriver::CallRestoreFrameMemberFunc(Driver, SyncState, AuxState); + } + + struct CRestoreFrameMemberFuncable + { + template + auto Requires(InDriverType* Driver, const SyncType* S, const AuxType* A) -> decltype(Driver->RestoreFrame(S, A)); + }; + + static constexpr bool HasRestoreFrame = TModels::Value; + + template + static typename TEnableIf::Type CallRestoreFrameMemberFunc(DriverType* Driver, const SyncType* SyncState, const AuxType* AuxState) + { + npCheckSlow(Driver); + Driver->RestoreFrame(SyncState, AuxState); + } + + template + static typename TEnableIf::Type CallRestoreFrameMemberFunc(DriverType* Driver, const SyncType* SyncState, const AuxType* AuxState) + { + // This isn't a problem but we should probably do something if there is no RestoreFrame function: + // -Warn/complain (but user may not care in all cases. So may need a trait to opt out?) + // -Call FinalizeFrame: less boiler plate to add (but causes confusion and could lead to slow FinalizeFrames being called too often) + // -Force both Restore/Finalize Frame to be implemented but always implicitly call RestoreFrame before FinalizeFrame? (nah) + } + // ----------------------------------------------------------------------------------------------------------------------------------- // CallServerRPC // @@ -378,13 +437,13 @@ struct FNetworkPredictionDriverBase // ----------------------------------------------------------------------------------------------------------------------------------- template - static void DispatchCues(TNetSimCueDispatcher* CueDispatcher, InDriverType* Driver, int32 SimFrame, int32 SimTimeMS, int32 ConfirmedFrame) + static void DispatchCues(TNetSimCueDispatcher* CueDispatcher, InDriverType* Driver, int32 SimFrame, int32 SimTimeMS, const int32 FixedStepMS) { npCheckSlow(Driver); - CueDispatcher-> template DispatchCueRecord(*Driver, SimFrame, SimTimeMS, ConfirmedFrame); + CueDispatcher-> template DispatchCueRecord(*Driver, SimFrame, SimTimeMS, FixedStepMS); } - static void DispatchCues(TNetSimCueDispatcher* CueDispatcher, void* Driver, int32 SimFrame, int32 SimTimeMS, int32 ConfirmedFrame) + static void DispatchCues(TNetSimCueDispatcher* CueDispatcher, void* Driver, int32 SimFrame, int32 SimTimeMS, const int32 FixedStepMS) { } @@ -469,7 +528,7 @@ struct FNetworkPredictionDriverBase { // If you are landing here with a nullptr Driver: // You are using interpolated physics, we need to get a UPrimitiveComponent from your ModelDef Driver. 3 choices: - // -A) Your ModelDef::Driver could be a UPrimitiveComponent. Nothing else required. + // -A) Your ModelDef::Driver could be a UPrimitiveComponent (or inherit from one). Nothing else required. // -B) Your ModelDef::Driver class can implement a UPrimitiveComponent* GetPhysicsPrimitiveComponent() function and return it. Good for "UpdatedPrimitive pattern". // -C) You can specialize FNetworkPredictionDriver::GetPhysicsPrimitiveComponent to do something else. Good for non intrusive case. npCheckf(false, TEXT("Could not obtain UPrimitiveComponent from driver. See notes above in NetworkPredictionDriver.h")); @@ -481,17 +540,45 @@ struct FNetworkPredictionDriverBase return Driver; } + // ------------------------------------------ + // GetPhysicsBodyInstance + // ------------------------------------------ + + struct CGetPhysicsBodyInstanceMemberFuncable + { + template + auto Requires(InDriverType* Driver) -> decltype(Driver->GetPhysicsBodyInstance()); + }; + + static constexpr bool HasGetBodyInstance = TModels::Value; + + template + static typename TEnableIf::Type GetPhysicsBodyInstance(DriverType* Driver) + { + npCheckSlow(Driver); + return Driver->GetPhysicsBodyInstance(); + } + + template + static typename TEnableIf::Type GetPhysicsBodyInstance(DriverType* Driver) + { + // Non explicit version: get the primitive component and get the body instance off of that + UPrimitiveComponent* PrimitiveComponent = FNetworkPredictionDriver::GetPhysicsPrimitiveComponent(Driver); + npCheckSlow(PrimitiveComponent); + return PrimitiveComponent->GetBodyInstance(); + } + // ------------------------------------------ // ShouldReconcilePhysics // ------------------------------------------ template - static typename TEnableIf::Type ShouldReconcilePhysics(int32 PhysicsFrame, Chaos::FRewindData* RewindData, FConditionalPhysicsActorHandle& Actor, TConditionalState& RecvState) + static typename TEnableIf::Type ShouldReconcilePhysics(int32 PhysicsFrame, Chaos::FRewindData* RewindData, DriverType* Driver, TConditionalState& RecvState) { - return PhysicsState::ShouldReconcile(PhysicsFrame, RewindData, Actor, RecvState); + return PhysicsState::ShouldReconcile(PhysicsFrame, RewindData, FNetworkPredictionDriver::GetPhysicsBodyInstance(Driver), RecvState); } template - static typename TEnableIf::Type ShouldReconcilePhysics(int32 PhysicsFrame, Chaos::FRewindData* RewindData, FConditionalPhysicsActorHandle& Actor, TConditionalState& RecvState) + static typename TEnableIf::Type ShouldReconcilePhysics(int32 PhysicsFrame, Chaos::FRewindData* RewindData, DriverType* Driver, TConditionalState& RecvState) { return false; } @@ -499,26 +586,43 @@ struct FNetworkPredictionDriverBase // ------------------------------------------ // PerformPhysicsRollback // ------------------------------------------ + template - static typename TEnableIf::Type PerformPhysicsRollback(FConditionalPhysicsActorHandle& Actor, TConditionalState& RecvState) + static typename TEnableIf::Type PerformPhysicsRollback(DriverType* Driver, TConditionalState& RecvState) { - PhysicsState::PerformRollback(Actor, RecvState); + PhysicsState::PerformRollback(FNetworkPredictionDriver::GetPhysicsBodyInstance(Driver), RecvState); } template - static typename TEnableIf::Type PerformPhysicsRollback(FConditionalPhysicsActorHandle& Actor, TConditionalState& RecvState) {} + static typename TEnableIf::Type PerformPhysicsRollback(DriverType* Driver, TConditionalState& RecvState) {} // ------------------------------------------ // PostPhysicsResimulate // ------------------------------------------ template - static typename TEnableIf::Type PostPhysicsResimulate(DriverType* Driver, FConditionalPhysicsActorHandle& Actor) + static typename TEnableIf::Type PostPhysicsResimulate(DriverType* Driver) { - PhysicsState::PostResimulate(FNetworkPredictionDriver::GetPhysicsPrimitiveComponent(Driver), Actor); + PhysicsState::PostResimulate(FNetworkPredictionDriver::GetPhysicsPrimitiveComponent(Driver), FNetworkPredictionDriver::GetPhysicsBodyInstance(Driver)); } template - static typename TEnableIf::Type PostPhysicsResimulate(DriverType* Driver, FConditionalPhysicsActorHandle& Actor) {} + static typename TEnableIf::Type PostPhysicsResimulate(DriverType* Driver) + { + npCheck(false); // Not expected to be called in non physics based sims + } + + // ------------------------------------------ + // MarshalPhysicsToComponent + // ------------------------------------------ + template + static typename TEnableIf::Type MarshalPhysicsToComponent(DriverType* Driver) + { + npCheckSlow(Driver); + PhysicsState::MarshalPhysicsToComponent(FNetworkPredictionDriver::GetPhysicsPrimitiveComponent(Driver), FNetworkPredictionDriver::GetPhysicsBodyInstance(Driver)); + } + + template + static typename TEnableIf::Type MarshalPhysicsToComponent(DriverType* Driver) { } // ------------------------------------------ // InterpolatePhysics @@ -538,49 +642,49 @@ struct FNetworkPredictionDriverBase // E.g, the physics sim is not running // ------------------------------------------ template - static typename TEnableIf::Type FinalizeInterpolatedPhysics(DriverType* Driver, FConditionalPhysicsActorHandle& Actor, TConditionalState& Physics) + static typename TEnableIf::Type FinalizeInterpolatedPhysics(DriverType* Driver, TConditionalState& Physics) { - PhysicsState::FinalizeInterpolatedPhysics(FNetworkPredictionDriver::GetPhysicsPrimitiveComponent(Driver), Actor, Physics); + PhysicsState::FinalizeInterpolatedPhysics(FNetworkPredictionDriver::GetPhysicsPrimitiveComponent(Driver), Physics); } template - static typename TEnableIf::Type FinalizeInterpolatedPhysics(DriverType* Driver, FConditionalPhysicsActorHandle& Actor, TConditionalState& Physics) {} + static typename TEnableIf::Type FinalizeInterpolatedPhysics(DriverType* Driver, TConditionalState& Physics) {} // ------------------------------------------ // BeginInterpolatedPhysics - turn off physics simulation so that NP can interpolate the physics state // ------------------------------------------ template - static typename TEnableIf::Type BeginInterpolatedPhysics(DriverType* Driver, FConditionalPhysicsActorHandle& Actor) + static typename TEnableIf::Type BeginInterpolatedPhysics(DriverType* Driver) { - PhysicsState::BeginInterpolation(FNetworkPredictionDriver::GetPhysicsPrimitiveComponent(Driver), Actor); + PhysicsState::BeginInterpolation(FNetworkPredictionDriver::GetPhysicsPrimitiveComponent(Driver)); } template - static typename TEnableIf::Type BeginInterpolatedPhysics(DriverType* Driver, FConditionalPhysicsActorHandle& Actor) {} + static typename TEnableIf::Type BeginInterpolatedPhysics(DriverType* Driver) {} // ------------------------------------------ // EndInterpolatedPhysics - turn on physics simulation // ------------------------------------------ template - static typename TEnableIf::Type EndInterpolatedPhysics(DriverType* Driver, FConditionalPhysicsActorHandle& Actor) + static typename TEnableIf::Type EndInterpolatedPhysics(DriverType* Driver) { - PhysicsState::EndInterpolation(FNetworkPredictionDriver::GetPhysicsPrimitiveComponent(Driver), Actor); + PhysicsState::EndInterpolation(FNetworkPredictionDriver::GetPhysicsPrimitiveComponent(Driver)); } template - static typename TEnableIf::Type EndInterpolatedPhysics(DriverType* Driver, FConditionalPhysicsActorHandle& Actor) {} + static typename TEnableIf::Type EndInterpolatedPhysics(DriverType* Driver) {} // ------------------------------------------ // PhysicsNetSend // ------------------------------------------ template - static typename TEnableIf::Type PhysicsNetSend(const FNetSerializeParams& P, FConditionalPhysicsActorHandle& Actor) + static typename TEnableIf::Type PhysicsNetSend(const FNetSerializeParams& P, DriverType* Driver) { - PhysicsState::NetSend(P, Actor); + PhysicsState::NetSend(P, FNetworkPredictionDriver::GetPhysicsBodyInstance(Driver)); } template - static typename TEnableIf::Type PhysicsNetSend(const FNetSerializeParams& P, FConditionalPhysicsActorHandle& Actor) {} + static typename TEnableIf::Type PhysicsNetSend(const FNetSerializeParams& P, DriverType* Driver) {} // ------------------------------------------ // PhysicsNetRecv @@ -594,6 +698,24 @@ struct FNetworkPredictionDriverBase template static typename TEnableIf::Type PhysicsNetRecv(const FNetSerializeParams& P, TConditionalState& RecvState) {} + // ------------------------------------------ + // PhysicsStateIsConsistent + // Only used in debugging - checks if UPrimitiveComponent and FPhysicsBody state are in sync with each other + // ------------------------------------------ + template + static typename TEnableIf::Type PhysicsStateIsConsistent(DriverType* Driver) + { + return PhysicsState::StateIsConsistent(FNetworkPredictionDriver::GetPhysicsPrimitiveComponent(Driver), + FNetworkPredictionDriver::GetPhysicsBodyInstance(Driver)); + } + + template + static typename TEnableIf::Type PhysicsStateIsConsistent(DriverType* Driver) + { + npCheck(false); + return false; + } + // ------------------------------------------ // LogPhysicsState // ------------------------------------------ @@ -610,16 +732,16 @@ struct FNetworkPredictionDriverBase static typename TEnableIf::Type LogPhysicsState(TConditionalState& RecvState, FOutputDevice& Ar=*GLog) { } template - static typename TEnableIf::Type LogPhysicsState(int32 PhysicsFrame, Chaos::FRewindData* RewindData, FConditionalPhysicsActorHandle& Actor, FOutputDevice& Ar=*GLog) - { + static typename TEnableIf::Type LogPhysicsState(int32 PhysicsFrame, Chaos::FRewindData* RewindData, DriverType* Driver, FOutputDevice& Ar=*GLog) + { TAnsiStringBuilder<256> Builder; - PhysicsState::ToString(PhysicsFrame, RewindData, Actor, Builder); + PhysicsState::ToString(PhysicsFrame, RewindData, FNetworkPredictionDriver::GetPhysicsBodyInstance(Driver), Builder); Ar.Log(StringCast(Builder.ToString()).Get()); } template - static typename TEnableIf::Type LogPhysicsState(int32 PhysicsFrame, Chaos::FRewindData* RewindData, FConditionalPhysicsActorHandle& Actor, FOutputDevice& Ar=*GLog) {} + static typename TEnableIf::Type LogPhysicsState(int32 PhysicsFrame, Chaos::FRewindData* RewindData, DriverType* Driver, FOutputDevice& Ar=*GLog) {} // ------------------------------------------ // TracePhysicsState @@ -637,23 +759,23 @@ struct FNetworkPredictionDriverBase // Current latest physics state template - static typename TEnableIf::Type TracePhysicsState(const FConditionalPhysicsActorHandle& Actor, FAnsiStringBuilderBase& Builder) + static typename TEnableIf::Type TracePhysicsState(DriverType* Driver, FAnsiStringBuilderBase& Builder) { - PhysicsState::ToString(Actor, Builder); + PhysicsState::ToString(FNetworkPredictionDriver::GetPhysicsBodyInstance(Driver), Builder); } template - static typename TEnableIf::Type TracePhysicsState(const FConditionalPhysicsActorHandle& Actor, FAnsiStringBuilderBase& Builder) {} + static typename TEnableIf::Type TracePhysicsState(DriverType* Driver, FAnsiStringBuilderBase& Builder) {} // Local historic physics state template - static typename TEnableIf::Type TracePhysicsState(int32 PhysicsFrame, Chaos::FRewindData* RewindData, const FConditionalPhysicsActorHandle& Actor, FAnsiStringBuilderBase& Builder) + static typename TEnableIf::Type TracePhysicsState(int32 PhysicsFrame, Chaos::FRewindData* RewindData, DriverType* Driver, FAnsiStringBuilderBase& Builder) { - PhysicsState::ToString(PhysicsFrame, RewindData, Actor, Builder); + PhysicsState::ToString(PhysicsFrame, RewindData, FNetworkPredictionDriver::GetPhysicsBodyInstance(Driver), Builder); } template - static typename TEnableIf::Type TracePhysicsState(int32 PhysicsFrame, Chaos::FRewindData* RewindData, const FConditionalPhysicsActorHandle& Actor, FAnsiStringBuilderBase& Builder) {} + static typename TEnableIf::Type TracePhysicsState(int32 PhysicsFrame, Chaos::FRewindData* RewindData, DriverType* Driver, FAnsiStringBuilderBase& Builder) {} // ----------------------------------------------------------------------------------------------------------------------------------- // ToString diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionModelDef.h b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionModelDef.h index 9d51dbb333f3..ff5a5b8097b5 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionModelDef.h +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionModelDef.h @@ -65,26 +65,6 @@ struct FConditionalSimulationPtr FConditionalSimulationPtr(void* v=nullptr) { } }; -template -struct FConditionalPhysicsActorHandle -{ - enum { Valid = true }; - FConditionalPhysicsActorHandle(FPhysicsActorHandle InHandle=nullptr) : Handle(InHandle) { } - FPhysicsActorHandle operator->() const { return Handle; } - operator FPhysicsActorHandle() const { return Handle; } - -private: - FPhysicsActorHandle Handle = nullptr; -}; - -template -struct FConditionalPhysicsActorHandle -{ - enum { Valid = false }; - FConditionalPhysicsActorHandle(FPhysicsActorHandle=nullptr) { } - operator FPhysicsActorHandle() const { return FPhysicsActorHandle(); } -}; - // ---------------------------------------------------------------------- template @@ -97,8 +77,7 @@ struct TNetworkPredictionModelInfo FConditionalSimulationPtr Simulation; // Object that ticks this instance DriverType* Driver = nullptr; // Object that handles input/out struct FNetworkPredictionStateView* View = nullptr; // Game side view of state to update - FConditionalPhysicsActorHandle Physics; // Physics handle - TNetworkPredictionModelInfo(SimulationType* InSimulation=nullptr, DriverType* InDriver=nullptr, FNetworkPredictionStateView* InView=nullptr, FPhysicsActorHandle InPhysics=nullptr) - : Simulation(InSimulation), Driver(InDriver), View(InView), Physics(InPhysics) { } + TNetworkPredictionModelInfo(SimulationType* InSimulation=nullptr, DriverType* InDriver=nullptr, FNetworkPredictionStateView* InView=nullptr) + : Simulation(InSimulation), Driver(InDriver), View(InView) { } }; diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionPhysics.h b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionPhysics.h index 49b8ec33c326..8a38cce33914 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionPhysics.h +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionPhysics.h @@ -16,6 +16,7 @@ #include "Containers/StringFwd.h" #include "NetworkPredictionTrace.h" #include "Components/PrimitiveComponent.h" +#include "PhysicsEngine/BodyInstance.h" namespace NetworkPredictionPhysicsCvars { @@ -38,8 +39,11 @@ struct NETWORKPREDICTION_API FNetworkPredictionPhysicsState Chaos::FVec3 LinearVelocity; Chaos::FVec3 AngularVelocity; - static void NetSend(const FNetSerializeParams& P, FPhysicsActorHandle Handle) + static void NetSend(const FNetSerializeParams& P, FBodyInstance* BodyInstance) { + npCheckSlow(BodyInstance); + FPhysicsActorHandle& Handle = BodyInstance->GetPhysicsActorHandle(); + if (NetworkPredictionPhysicsCvars::FullPrecision()) { P.Ar << const_cast(Handle->X()); @@ -91,8 +95,11 @@ struct NETWORKPREDICTION_API FNetworkPredictionPhysicsState npEnsureSlow(!RecvState->ContainsNaN()); } - static bool ShouldReconcile(int32 PhysicsFrame, Chaos::FRewindData* RewindData, FPhysicsActorHandle Handle, FNetworkPredictionPhysicsState* RecvState) + static bool ShouldReconcile(int32 PhysicsFrame, Chaos::FRewindData* RewindData, FBodyInstance* BodyInstance, FNetworkPredictionPhysicsState* RecvState) { + npCheckSlow(BodyInstance); + FPhysicsActorHandle& Handle = BodyInstance->GetPhysicsActorHandle(); + auto CompareVector = [](const FVector& Local, const FVector& Authority, const float Tolerance, const TCHAR* DebugStr) { const FVector Delta = Local - Authority; @@ -164,8 +171,22 @@ struct NETWORKPREDICTION_API FNetworkPredictionPhysicsState npEnsureMsgfSlow(!Out->ContainsNaN(), TEXT("Out interpolation state contains NaN. %f"), PCT); } - static void PerformRollback(FPhysicsActorHandle Handle, FNetworkPredictionPhysicsState* RecvState) + static void PerformRollback(UPrimitiveComponent* PrimitiveComponent, FNetworkPredictionPhysicsState* RecvState) { + npCheckSlow(PrimitiveComponent); + npCheckSlow(RecvState); + + // We want to update the component and physics state together without dispatching events or getting into circular loops + // This seems like the simplest approach (update physics first then manually move the component, skipping physics update) + FBodyInstance* BodyInstance = PrimitiveComponent->GetBodyInstance(); + PerformRollback(BodyInstance, RecvState); + MarshalPhysicsToComponent(PrimitiveComponent, BodyInstance); + } + + static void PerformRollback(FBodyInstance* BodyInstance, FNetworkPredictionPhysicsState* RecvState) + { + FPhysicsActorHandle& Handle = BodyInstance->GetPhysicsActorHandle(); + npCheckSlow(RecvState); npEnsureSlow(RecvState->Rotation.IsNormalized()); @@ -184,23 +205,36 @@ struct NETWORKPREDICTION_API FNetworkPredictionPhysicsState } } - static void PostResimulate(UPrimitiveComponent* Driver, FPhysicsActorHandle ActorHandle) + static void MarshalPhysicsToComponent(UPrimitiveComponent* PrimitiveComponent, const FBodyInstance* BodyInstance) { - npCheckSlow(Driver); + const FTransform UnrealTransform = BodyInstance->GetUnrealWorldTransform(); + const FVector MoveBy = UnrealTransform.GetLocation() - PrimitiveComponent->GetComponentTransform().GetLocation(); + PrimitiveComponent->MoveComponent(MoveBy, UnrealTransform.GetRotation(), false, nullptr, MOVECOMP_SkipPhysicsMove); + } + + static void PostResimulate(UPrimitiveComponent* PrimitiveComponent, const FBodyInstance* BodyInstance) + { + MarshalPhysicsToComponent(PrimitiveComponent, BodyInstance); // We need to force a marshal of physics data -> PrimitiveComponent in cases where a sleeping object was asleep before and after a correction, // but waking up and calling SyncComponentToRBPhysics() is causing some bad particle data to feed back into physics. This is probably not the right // way. Disabling for now to avoid the asserts but will cause the sleeping object-not-updated bug to reappear. - //Driver->WakeAllRigidBodies(); - //Driver->SyncComponentToRBPhysics(); + //PrimitiveComponent->WakeAllRigidBodies(); + //PrimitiveComponent->SyncComponentToRBPhysics(); //npEnsureSlow(ActorHandle->R().IsNormalized()); } + static bool StateIsConsistent(UPrimitiveComponent* PrimitiveComponent, FBodyInstance* BodyInstance) + { + const FTransform PhysicsTransform = BodyInstance->GetUnrealWorldTransform(); + return PhysicsTransform.EqualsNoScale(PrimitiveComponent->GetComponentTransform()); + } + // Interpolation related functions currently need to go through a UPrimitiveComponent - static void FinalizeInterpolatedPhysics(UPrimitiveComponent* Driver, FPhysicsActorHandle Handle, FNetworkPredictionPhysicsState* InterpolatedState); - static void BeginInterpolation(UPrimitiveComponent* Driver, FPhysicsActorHandle ActorHandle); - static void EndInterpolation(UPrimitiveComponent* Driver, FPhysicsActorHandle ActorHandle); + static void FinalizeInterpolatedPhysics(UPrimitiveComponent* Driver, FNetworkPredictionPhysicsState* InterpolatedState); + static void BeginInterpolation(UPrimitiveComponent* Driver); + static void EndInterpolation(UPrimitiveComponent* Driver); // Networked state to string static void ToString(FNetworkPredictionPhysicsState* RecvState, FAnsiStringBuilderBase& Builder) @@ -209,15 +243,19 @@ struct NETWORKPREDICTION_API FNetworkPredictionPhysicsState } // Locally stored state to string - static void ToString(int32 PhysicsFrame, Chaos::FRewindData* RewindData, FPhysicsActorHandle Handle, FAnsiStringBuilderBase& Builder) + static void ToString(int32 PhysicsFrame, Chaos::FRewindData* RewindData, FBodyInstance* BodyInstance, FAnsiStringBuilderBase& Builder) { + FPhysicsActorHandle& Handle = BodyInstance->GetPhysicsActorHandle(); + const Chaos::FGeometryParticleState LocalState = RewindData->GetPastStateAtFrame(*Handle, PhysicsFrame); ToStringInternal(LocalState.X(), LocalState.R(), LocalState.V(), LocalState.W(), Builder); } // Current state to string - static void ToString(FPhysicsActorHandle Handle, FAnsiStringBuilderBase& Builder) + static void ToString(FBodyInstance* BodyInstance, FAnsiStringBuilderBase& Builder) { + FPhysicsActorHandle& Handle = BodyInstance->GetPhysicsActorHandle(); + Chaos::TKinematicGeometryParticle* Kinematic = Handle->CastToKinematicParticle(); if (npEnsure(Kinematic)) { @@ -249,7 +287,7 @@ struct FGenericPhysicsModelDef : FNetworkPredictionModelDef NP_MODEL_BODY(); using PhysicsState = FNetworkPredictionPhysicsState; - using Driver = UPrimitiveComponent; // this is required for interpolation mode to work, see FNetworkPredictionPhysicsState::FinalizeInterpolatedPhysics. Would like to not require it one day. + using Driver = UPrimitiveComponent; static const TCHAR* GetName() { return TEXT("Generic Physics Actor"); } static constexpr int32 GetSortPriority() { return (int32)ENetworkPredictionSortPriority::Physics; } diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionProxy.h b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionProxy.h index 9fe667d1a2ae..8ce18d66b8fa 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionProxy.h +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionProxy.h @@ -39,7 +39,7 @@ struct FNetworkPredictionProxy // The init function that you need to call. This is defined in NetworkPredictionProxyInit.h (which should only be included by your .cpp file) template - void Init(UWorld* World, const FReplicationProxySet& RepProxies, typename ModelDef::Simulation* Simulation=nullptr, typename ModelDef::Driver* Driver=nullptr, FPhysicsActorHandle PhysicsHandle=nullptr); + void Init(UWorld* World, const FReplicationProxySet& RepProxies, typename ModelDef::Simulation* Simulation=nullptr, typename ModelDef::Driver* Driver=nullptr); // When network role changes, initializes role storage and logic controller void InitForNetworkRole(ENetRole Role, bool bHasNetConnection) diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionProxyInit.h b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionProxyInit.h index d726399cb9d5..835239a3a36d 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionProxyInit.h +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionProxyInit.h @@ -12,7 +12,7 @@ // The init function binds to the templated methods on UNetworkPredictionMAnager. This will "bring in" all the templated systems on NP, so this file should only be // included in your .cpp file that is calling Init. template -void FNetworkPredictionProxy::Init(UWorld* World, const FReplicationProxySet& RepProxies, typename ModelDef::Simulation* Simulation, typename ModelDef::Driver* Driver, FPhysicsActorHandle PhysicsHandle) +void FNetworkPredictionProxy::Init(UWorld* World, const FReplicationProxySet& RepProxies, typename ModelDef::Simulation* Simulation, typename ModelDef::Driver* Driver) { using StateTypes = typename ModelDef::StateTypes; using InputType = typename StateTypes::InputType; @@ -37,7 +37,7 @@ void FNetworkPredictionProxy::Init(UWorld* World, const FReplicationProxySet& Re ID = WorldManager->CreateSimulationID(World->GetNetMode() == NM_Client); } - WorldManager->RegisterInstance(ID, TNetworkPredictionModelInfo(Simulation, Driver, &View, PhysicsHandle)); + WorldManager->RegisterInstance(ID, TNetworkPredictionModelInfo(Simulation, Driver, &View)); ConfigFunc = [RepProxies](FNetworkPredictionProxy* const This, FNetworkPredictionID NewID, EConfigAction Action) { diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionSerialization.h b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionSerialization.h index 0e60e9a13bcd..114b689974ac 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionSerialization.h +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionSerialization.h @@ -311,8 +311,6 @@ public: FNetworkPredictionDriver::PhysicsNetRecv(P, ClientRecvState.Physics); // 3. Physics - InstanceData.CueDispatcher->NetSerializeSavedCues(P.Ar, ENetSimCueReplicationTarget::AutoProxy, true); // 4. NetSimCues - UE_NP_TRACE_USER_STATE_SYNC(ModelDef, ClientRecvState.SyncState.Get()); UE_NP_TRACE_USER_STATE_AUX(ModelDef, ClientRecvState.AuxState.Get()); } @@ -324,9 +322,7 @@ public: FNetworkPredictionDriver::NetSerialize(FrameData.SyncState, P); // 1. Sync FNetworkPredictionDriver::NetSerialize(FrameData.AuxState, P); // 2. Aux - FNetworkPredictionDriver::PhysicsNetSend(P, InstanceData.Info.Physics); // 3. Physics - - InstanceData.CueDispatcher->NetSerializeSavedCues(P.Ar, ENetSimCueReplicationTarget::AutoProxy, true); // 4. NetSimCues + FNetworkPredictionDriver::PhysicsNetSend(P, InstanceData.Info.Driver); // 3. Physics } }; @@ -382,10 +378,12 @@ public: ClientRecvState.ServerFrame = ServerFrame; UE_NP_TRACE_NET_RECV(ServerFrame, ServerFrame * TickState->FixedStepMS); - npEnsureSlow(ClientRecvState.InstanceIdx != 0); + npEnsureSlow(ClientRecvState.InstanceIdx >= 0); TInstanceData& InstanceData = DataStore->Instances.GetByIndexChecked(ClientRecvState.InstanceIdx); TCommonReplicator_AP::NetRecv(P, InstanceData, ClientRecvState); // 3. Common + + InstanceData.CueDispatcher->NetRecvSavedCues(P.Ar, true, ServerFrame, 0); // 4. NetSimCues } // ------------------------------------------------------------------------------------------------------------ @@ -415,6 +413,8 @@ public: FNetworkPredictionSerialization::WriteCompressedFrame(Ar, PendingFrame); // 2. PendingFrame (Server's frame) TCommonReplicator_AP::NetSend(P, *Instance, Frames->Buffer[PendingFrame]); // 3. Common + + Instance->CueDispatcher->NetSendSavedCues(P.Ar, ENetSimCueReplicationTarget::AutoProxy, true); // 4. NetSimCues } }; @@ -480,7 +480,7 @@ public: using ModelDef = InModelDef; template - static void NetRecv(const FNetSerializeParams& P, ClientRecvDataType& ClientRecvState, TModelDataStore* DataStore, const bool bSerializeCueFrames) + static void NetRecv(const FNetSerializeParams& P, ClientRecvDataType& ClientRecvState, TModelDataStore* DataStore) { FArchive& Ar = P.Ar; NETSIM_CHECKSUM(Ar); @@ -491,23 +491,18 @@ public: FNetworkPredictionDriver::PhysicsNetRecv(P, ClientRecvState.Physics); // 4. Physics - npEnsureSlow(ClientRecvState.InstanceIdx >= 0); - TInstanceData& InstanceData = DataStore->Instances.GetByIndexChecked(ClientRecvState.InstanceIdx); - InstanceData.CueDispatcher->NetSerializeSavedCues(Ar, ENetSimCueReplicationTarget::SimulatedProxy | ENetSimCueReplicationTarget::Interpolators, bSerializeCueFrames); // 5. NetSimCues - UE_NP_TRACE_USER_STATE_INPUT(ModelDef, ClientRecvState.InputCmd.Get()); UE_NP_TRACE_USER_STATE_SYNC(ModelDef, ClientRecvState.SyncState.Get()); UE_NP_TRACE_USER_STATE_AUX(ModelDef, ClientRecvState.AuxState.Get()); } - static void NetSend(const FNetSerializeParams& P, FNetworkPredictionID ID, TModelDataStore* DataStore, int32 PendingFrame, const bool bSerializeCueFrames) + static void NetSend(const FNetSerializeParams& P, FNetworkPredictionID ID, TModelDataStore* DataStore, TInstanceData* InstanceData, int32 PendingFrame) { + npCheckSlow(InstanceData); + FArchive& Ar = P.Ar; NETSIM_CHECKSUM(Ar); - TInstanceData* Instance = DataStore->Instances.Find(ID); - npCheckSlow(Instance); - TInstanceFrameState* Frames = DataStore->Frames.Find(ID); npCheckSlow(Frames); @@ -516,8 +511,7 @@ public: FNetworkPredictionDriver::NetSerialize(FrameData.SyncState, P); // 2. Sync FNetworkPredictionDriver::NetSerialize(FrameData.AuxState, P); // 3. Aux - FNetworkPredictionDriver::PhysicsNetSend(P, Instance->Info.Physics); // 4. Physics - Instance->CueDispatcher->NetSerializeSavedCues(Ar, ENetSimCueReplicationTarget::SimulatedProxy | ENetSimCueReplicationTarget::Interpolators, bSerializeCueFrames); // 5. NetSimCues + FNetworkPredictionDriver::PhysicsNetSend(P, InstanceData->Info.Driver); // 4. Physics } }; @@ -537,9 +531,14 @@ public: npEnsure(ClientRecvState.ServerFrame >= 0); UE_NP_TRACE_NET_RECV(ClientRecvState.ServerFrame, ClientRecvState.ServerFrame * TickState->FixedStepMS); + + TCommonReplicator_SP::NetRecv(P, ClientRecvState, DataStore); // 2, Common + + npEnsureSlow(ClientRecvState.InstanceIdx >= 0); + TInstanceData& InstanceData = DataStore->Instances.GetByIndexChecked(ClientRecvState.InstanceIdx); const bool bSerializeCueFrames = true; // Fixed tick can use Frame numbers for SP serialization - TCommonReplicator_SP::NetRecv(P, ClientRecvState, DataStore, bSerializeCueFrames); // 2, Common + InstanceData.CueDispatcher->NetRecvSavedCues(P.Ar, bSerializeCueFrames, ClientRecvState.ServerFrame, 0); // 3. NetSimCues } // ------------------------------------------------------------------------------------------------------------ @@ -549,11 +548,16 @@ public: { const int32 PendingFrame = TickState->PendingFrame; npEnsure(PendingFrame >= 0); + + TInstanceData* Instance = DataStore->Instances.Find(ID); + npCheckSlow(Instance); FNetworkPredictionSerialization::WriteCompressedFrame(P.Ar, PendingFrame); // 1. PendingFrame (Server's frame) + + TCommonReplicator_SP::NetSend(P, ID, DataStore, Instance, PendingFrame); // 2. Common const bool bSerializeCueFrames = true; // Fixed tick can use Frame numbers for SP serialization - TCommonReplicator_SP::NetSend(P, ID, DataStore, PendingFrame, bSerializeCueFrames); // 2. Common + Instance->CueDispatcher->NetSendSavedCues(P.Ar, ENetSimCueReplicationTarget::SimulatedProxy | ENetSimCueReplicationTarget::Interpolators, bSerializeCueFrames); // 3. NetSimCues } }; @@ -582,9 +586,14 @@ public: npEnsure(TraceFrame >= 0); UE_NP_TRACE_NET_RECV(TraceFrame, TickState->Frames[TickState->PendingFrame].TotalMS); + + TCommonReplicator_SP::NetRecv(P, ClientRecvState, DataStore); // 2. Common - const bool bSerializeCueFrames = false; // Independent tick cannot use Frame numbers for SP serialization (use time instead) - TCommonReplicator_SP::NetRecv(P, ClientRecvState, DataStore, bSerializeCueFrames); // 2. Common + npEnsureSlow(ClientRecvState.InstanceIdx >= 0); + TInstanceData& InstanceData = DataStore->Instances.GetByIndexChecked(ClientRecvState.InstanceIdx); + + const bool bSerializeCueFrames = true; // Fixed tick can use Frame numbers for SP serialization + InstanceData.CueDispatcher->NetRecvSavedCues(P.Ar, bSerializeCueFrames, INDEX_NONE, ClientRecvState.SimTimeMS); // 3. NetSimCues } // ------------------------------------------------------------------------------------------------------------ @@ -616,12 +625,15 @@ private: static void NetSend(const FNetSerializeParams& P, FNetworkPredictionID ID, TModelDataStore* DataStore, int32 TotalSimTime, int32 PendingFrame) { - const bool bSerializeCueFrames = false; // Independent tick cannot use Frame numbers for SP serialization (use time instead) - FNetworkPredictionSerialization::SerializeTimeMS(P.Ar, TotalSimTime); // 1. TotalSimTime - TCommonReplicator_SP::NetSend(P, ID, DataStore, PendingFrame, bSerializeCueFrames); // 2. Common - } + TInstanceData* Instance = DataStore->Instances.Find(ID); + npCheckSlow(Instance); - + FNetworkPredictionSerialization::SerializeTimeMS(P.Ar, TotalSimTime); // 1. TotalSimTime + TCommonReplicator_SP::NetSend(P, ID, DataStore, Instance, PendingFrame); // 2. Common + + const bool bSerializeCueFrames = false; // Independent tick cannot use Frame numbers for SP serialization (use time instead) + Instance->CueDispatcher->NetSendSavedCues(P.Ar, ENetSimCueReplicationTarget::SimulatedProxy | ENetSimCueReplicationTarget::Interpolators, bSerializeCueFrames); // 3. NetSimCues + } }; // --------------------------------------------------------------------------------------------------------------------------- diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionTrace.h b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionTrace.h index 24bce559e20d..7ff3a22beb13 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionTrace.h +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionTrace.h @@ -67,8 +67,8 @@ #define UE_NP_TRACE_USER_STATE_SYNC(ModelDef, UserState) FNetworkPredictionTrace::TraceUserState(UserState, FNetworkPredictionTrace::ETraceUserState::Sync) #define UE_NP_TRACE_USER_STATE_AUX(ModelDef, UserState) FNetworkPredictionTrace::TraceUserState(UserState, FNetworkPredictionTrace::ETraceUserState::Aux) -#define UE_NP_TRACE_PHYSICS_STATE_CURRENT(ModelDef, ActorHandle) FNetworkPredictionTrace::TracePhysicsStateCurrent(ActorHandle) -#define UE_NP_TRACE_PHYSICS_STATE_AT_FRAME(ModelDef, Frame, RewindData, ActorHandle) FNetworkPredictionTrace::TracePhysicsStateAtFrame(Frame, RewindData, ActorHandle) +#define UE_NP_TRACE_PHYSICS_STATE_CURRENT(ModelDef, Driver) FNetworkPredictionTrace::TracePhysicsStateCurrent(Driver) +#define UE_NP_TRACE_PHYSICS_STATE_AT_FRAME(ModelDef, Frame, RewindData, Driver) FNetworkPredictionTrace::TracePhysicsStateAtFrame(Frame, RewindData, Driver) #define UE_NP_TRACE_PHYSICS_STATE_RECV(ModelDef, NpPhysicsState) FNetworkPredictionTrace::TracePhysicsStateReceived(NpPhysicsState) #else @@ -220,11 +220,12 @@ public: #endif } - template - static void TracePhysicsStateCurrent(const FConditionalPhysicsActorHandle& Handle) + template + static void TracePhysicsStateCurrent(DriverType* Driver) { #if UE_NP_TRACE_USER_STATES_ENABLED - if (!FConditionalPhysicsActorHandle::Valid) + + if (!FNetworkPredictionDriver::HasPhysics()) { return; } @@ -232,27 +233,27 @@ public: if (UE_TRACE_CHANNELEXPR_IS_ENABLED(NetworkPredictionChannel)) { TAnsiStringBuilder<512> Builder; - FNetworkPredictionDriver::TracePhysicsState(Handle, Builder); + FNetworkPredictionDriver::TracePhysicsState(Driver, Builder); TraceUserState_Internal(ETraceUserState::Physics, Builder); } #endif } - template - static void TracePhysicsStateAtFrame(int32 PhysicsFrame, Chaos::FRewindData* RewindData, const FConditionalPhysicsActorHandle& Handle) + template + static void TracePhysicsStateAtFrame(int32 PhysicsFrame, Chaos::FRewindData* RewindData, DriverType* Driver) { #if UE_NP_TRACE_USER_STATES_ENABLED - if (!FConditionalPhysicsActorHandle::Valid) + + + if (!FNetworkPredictionDriver::HasPhysics()) { return; } if (UE_TRACE_CHANNELEXPR_IS_ENABLED(NetworkPredictionChannel)) { - npCheckSlow((FPhysicsActorHandle)Handle); - TAnsiStringBuilder<512> Builder; - FNetworkPredictionDriver::TracePhysicsState(PhysicsFrame, RewindData, Handle, Builder); + FNetworkPredictionDriver::TracePhysicsState(PhysicsFrame, RewindData, Driver, Builder); TraceUserState_Internal(ETraceUserState::Physics, Builder); } #endif @@ -262,7 +263,8 @@ public: static void TracePhysicsStateRecv(const PhysicsStateType* State) { #if UE_NP_TRACE_USER_STATES_ENABLED - if (!PhysicsStateType::Valid) + + if (!FNetworkPredictionDriver::HasPhysics()) { return; } diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionWorldManager.h b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionWorldManager.h index 8ace799f966a..0af2b7ddd314 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionWorldManager.h +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/NetworkPredictionWorldManager.h @@ -12,6 +12,7 @@ #include "NetworkPredictionSerialization.h" #include "NetworkPredictionTrace.h" #include "NetworkPredictionSettings.h" +#include "NetworkPredictionCues.h" #include "NetworkPredictionWorldManager.generated.h" @@ -57,6 +58,8 @@ public: TModelDataStore* DataStore = Services.GetDataStore(); TInstanceData& InstanceData = DataStore->Instances.FindOrAdd(ID); InstanceData.Info = ModelInfo; + InstanceData.TraceID = ID.GetTraceID(); + InstanceData.CueDispatcher->Driver = ModelInfo.Driver; // Awkward: we should convert Cues to a service so this isn't needed. } template @@ -96,6 +99,7 @@ private: void SetUsingPhysics(); void InitPhysicsCapture(); void AdvancePhysicsResimFrame(int32& PhysicsFrame); + void EnsurePhysicsGTSync(const TCHAR* Context) const; FNetworkPredictionSettings Settings; @@ -338,6 +342,19 @@ void UNetworkPredictionWorldManager::ConfigureInstance(FNetworkPredictionID ID, } } + // Net Cues: set which replicated cues we should accept based on if we are FP or interpolated + if (Role != ROLE_Authority) + { + if (EnumHasAnyFlags(ServiceMask, ENetworkPredictionService::FixedInterpolate | ENetworkPredictionService::IndependentInterpolate)) + { + InstanceData.CueDispatcher->SetReceiveReplicationTarget(ENetSimCueReplicationTarget::Interpolators); + } + else + { + InstanceData.CueDispatcher->SetReceiveReplicationTarget( Role == ROLE_AutonomousProxy ? ENetSimCueReplicationTarget::AutoProxy : ENetSimCueReplicationTarget::SimulatedProxy); + } + } + // Register with selected services Services.RegisterInstance(ID, InstanceData, ServiceMask); diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionInstanceData.h b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionInstanceData.h index e42b81b2cc42..74f77a9fecad 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionInstanceData.h +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionInstanceData.h @@ -79,9 +79,6 @@ struct TInstanceFrameState TNetworkPredictionBuffer Buffer; - // If >= 0, this frame should be preserved. E.g, do not write at a frame >= TailFrame + Buffer.Capacity(). - int32 TailFrame = INDEX_NONE; - TInstanceFrameState() : Buffer(64) { } // fixme }; diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Finalize.inl b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Finalize.inl index 43b440f4cb87..b395db6e9bb4 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Finalize.inl +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Finalize.inl @@ -7,7 +7,7 @@ class IFinalizeService public: virtual ~IFinalizeService() = default; - virtual void FinalizeFrame(float DeltaTimeSeconds, const int32 SimFrame, const int32 SimTimeMS, const int32 ConfirmedFrame) = 0; + virtual void FinalizeFrame(float DeltaTimeSeconds, const int32 SimFrame, const int32 SimTimeMS, const int32 FixedStepMS) = 0; }; template @@ -37,7 +37,7 @@ public: FinalizeBitArray[InstanceIdx] = false; } - void FinalizeFrame(float DeltaTimeSeconds, const int32 ServerSimFrame, const int32 SimTimeMS, const int32 ConfirmedFrame) final override + void FinalizeFrame(float DeltaTimeSeconds, const int32 ServerSimFrame, const int32 SimTimeMS, const int32 FixedStepMS) final override { for (TConstSetBitIterator<> BitIt(FinalizeBitArray); BitIt; ++BitIt) { @@ -53,7 +53,7 @@ public: // Dispatch Cues: it may be better to take two passes here. Do all FinalizeFrame calls then all Dispatch Cues // (Dispatch Cues can go deep into user code, may be more cache efficient to take two passes). - FNetworkPredictionDriver::DispatchCues(&InstanceData.CueDispatcher.Get(), InstanceData.Info.Driver, ServerSimFrame, SimTimeMS, ConfirmedFrame); + FNetworkPredictionDriver::DispatchCues(&InstanceData.CueDispatcher.Get(), InstanceData.Info.Driver, ServerSimFrame, SimTimeMS, FixedStepMS); } } @@ -117,7 +117,7 @@ public: // Dispatch Cues: it may be better to take two passes here. Do all FinalizeFrame calls then all Dispatch Cues // (Dispatch Cues can go deep into user code, may be more cache efficient to take two passes). - FNetworkPredictionDriver::DispatchCues(&InstanceData.CueDispatcher.Get(), InstanceData.Info.Driver, ServerRecvData.PendingFrame, ServerRecvData.TotalSimTimeMS, INDEX_NONE); + FNetworkPredictionDriver::DispatchCues(&InstanceData.CueDispatcher.Get(), InstanceData.Info.Driver, ServerRecvData.PendingFrame, ServerRecvData.TotalSimTimeMS, 0); } } diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Interpolate.inl b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Interpolate.inl index df846f55d184..ae5dcb66aa98 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Interpolate.inl +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Interpolate.inl @@ -16,15 +16,7 @@ // In other words, all interpolated simulations are "in sync": they have the same amount of buffered frames/time. // // -// TODO: -// The interpolated result frame is not stored anywhere, so accessing latest state via the FNetworkPredictionProxy -// will never show this data. -// -// We probably need to distinguish between the "Frame Final" state and the "Simulation State" in FNetworkPredictionProxy. -// Also probably need better write protection. E.g, interpolated simulations simply cannot be predictively written to on -// clients. -// -// Other notes: +// Notes: // -We never allow extrapolation. If we under run an interpolation buffer we just use latest state ("cap at 100%") // -This could be allowed/opt in option on the ModelDef. @@ -44,6 +36,9 @@ namespace NetworkPredictionCVars // The main concerns here are keeping valid data in the Frame buffer. // As we NetRecv, make sure we have a continuous line of valid frames between to ToFrame and received frame. // When interpolating, make sure we haven't been starved on replication. +// +// Note: Interpolation ignores the TickState->Offset that forward predicted services use. Instead, we copy +// into the local frame buffers @ the server frame numbers. // ------------------------------------------------------------------------------ class IFixedInterpolateService { @@ -89,7 +84,7 @@ public: if (bHasPhysics) { - FNetworkPredictionDriver::BeginInterpolatedPhysics(InstanceData.Info.Driver, InstanceData.Info.Physics); + FNetworkPredictionDriver::BeginInterpolatedPhysics(InstanceData.Info.Driver); } } @@ -103,7 +98,7 @@ public: if (bHasPhysics) { - FNetworkPredictionDriver::EndInterpolatedPhysics(InstanceData.Info.Driver, InstanceData.Info.Physics); + FNetworkPredictionDriver::EndInterpolatedPhysics(InstanceData.Info.Driver); } @@ -156,7 +151,7 @@ public: const int32 GapFromFrame = bLastWrittenFrameValid ? InterpolationData.LastWrittenFrame : LocalFrame; const int32 GapToFrame = LocalFrame; - npEnsureSlow(StartFrame - EndFrame <= Frames.Buffer.Capacity()); + npEnsureMsgfSlow(StartFrame - EndFrame <= Frames.Buffer.Capacity(), TEXT("Gap longer than expected. StartFrame: %d. EndFrame: %d (%d)"), StartFrame, EndFrame, StartFrame - EndFrame); for (int32 Frame = StartFrame; Frame < EndFrame; ++Frame) { @@ -241,13 +236,13 @@ public: TConditionalState PhysicsState; FNetworkPredictionDriver::InterpolatePhysics(Instance.PhysicsBuffer[FromFrame], Instance.PhysicsBuffer[ToFrame], PCT, PhysicsState); - FNetworkPredictionDriver::FinalizeInterpolatedPhysics(InstanceData.Info.Driver, InstanceData.Info.Physics, PhysicsState); + FNetworkPredictionDriver::FinalizeInterpolatedPhysics(InstanceData.Info.Driver, PhysicsState); } // Update SimulationView with the frame we are interpolating to InstanceData.Info.View->UpdateView(ToFrame, ToFrameData.InputCmd, ToFrameData.SyncState, ToFrameData.AuxState); - FNetworkPredictionDriver::DispatchCues(&InstanceData.CueDispatcher.Get(), InstanceData.Info.Driver, FromFrame, InterpolatedTimeMS, FromFrame); // FromFrame is the ConfirmedFrame + FNetworkPredictionDriver::DispatchCues(&InstanceData.CueDispatcher.Get(), InstanceData.Info.Driver, FromFrame, InterpolatedTimeMS, 0); } } } @@ -498,7 +493,7 @@ public: InstanceData.Info.View->UpdateView(ToFrame, nullptr, ToFromData.SyncState, ToFromData.AuxState); - FNetworkPredictionDriver::DispatchCues(&InstanceData.CueDispatcher.Get(), InstanceData.Info.Driver, FromFrame, InterpolationTimeMS, FromFrame); // FromFrame is the ConfirmedFrame + FNetworkPredictionDriver::DispatchCues(&InstanceData.CueDispatcher.Get(), InstanceData.Info.Driver, FromFrame, InterpolationTimeMS, 0); if (NetworkPredictionCVars::DrawInterpolation()) { @@ -540,7 +535,7 @@ public: InstanceData.Info.View->UpdateView(FromFrame, nullptr, FrameData.SyncState, FrameData.AuxState); - FNetworkPredictionDriver::DispatchCues(&InstanceData.CueDispatcher.Get(), InstanceData.Info.Driver, FromFrame, ActualTimeMS, FromFrame); // FromFrame is the "confirmed frame" for interpolation + FNetworkPredictionDriver::DispatchCues(&InstanceData.CueDispatcher.Get(), InstanceData.Info.Driver, FromFrame, ActualTimeMS, 0); if (NetworkPredictionCVars::DrawInterpolation()) { diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Physics.inl b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Physics.inl index ef0670bafc2b..c18a970cbece 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Physics.inl +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Physics.inl @@ -17,6 +17,8 @@ public: virtual void PostResimulate(const FFixedTickState* TickState) = 0; virtual void PostNetworkPredictionFrame(const FFixedTickState* TickState) = 0; virtual void PostPhysics() = 0; // todo + + virtual void EnsureDataInSync(const TCHAR* ContextStr) = 0; }; template @@ -54,7 +56,7 @@ public: { TInstanceData& InstanceData = DataStore->Instances.GetByIndexChecked(BitIt.GetIndex()); - FNetworkPredictionDriver::PostPhysicsResimulate(InstanceData.Info.Driver, InstanceData.Info.Physics); + FNetworkPredictionDriver::PostPhysicsResimulate(InstanceData.Info.Driver); } } @@ -93,7 +95,7 @@ public: } } - UE_NP_TRACE_PHYSICS_STATE_CURRENT(ModelDef, InstanceData.Info.Physics); + UE_NP_TRACE_PHYSICS_STATE_CURRENT(ModelDef, InstanceData.Info.Driver); } } @@ -103,6 +105,22 @@ public: // Tracing is the obvious example though it complicates the general tracing system a bit since it adds // another point in the frame that we need to sample (pre NP tick (Net Recv/Rollback), post NP tick, post Physics tick) } + + void EnsureDataInSync(const TCHAR* ContextStr) + { + for (TConstSetBitIterator<> BitIt(InstanceBitArray); BitIt; ++BitIt) + { + TInstanceData& InstanceData = DataStore->Instances.GetByIndexChecked(BitIt.GetIndex()); + + if (!FNetworkPredictionDriver::PhysicsStateIsConsistent(InstanceData.Info.Driver)) + { + TStringBuilder<128> Builder; + FNetworkPredictionDriver::GetDebugString(InstanceData.Info.Driver, Builder); + UE_LOG(LogNetworkPrediction, Warning, TEXT("Physics State out of sync on %s (Context: %s)"), Builder.ToString(), ContextStr); + npEnsure(false); + } + } + } private: diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Rollback.inl b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Rollback.inl index e892cfe5faf9..21ef4c5b7b14 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Rollback.inl +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Rollback.inl @@ -1,6 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. #pragma once +#include "Stats/Stats.h" namespace NetworkPredictionCVars { @@ -14,8 +15,9 @@ public: virtual ~IFixedRollbackService() = default; virtual int32 QueryRollback(const FFixedTickState* TickState) = 0; - virtual void BeginRollback(const int32 LocalFrame, const int32 StartTimeMS, const int32 ServerFrame) = 0; - virtual void StepRollback(const FNetSimTimeStep& Step, const FServiceTimeStep& ServiceStep, const int32 Offset) = 0; + + virtual void PreStepRollback(const FNetSimTimeStep& Step, const FServiceTimeStep& ServiceStep, const int32 Offset, const bool bFirstStepInResim) = 0; + virtual void StepRollback(const FNetSimTimeStep& Step, const FServiceTimeStep& ServiceStep) = 0; }; template @@ -27,7 +29,7 @@ public: using StateTypes = typename ModelDef::StateTypes; using SyncAuxType = TSyncAuxPair; - static constexpr bool bNeedsTickTickService = FNetworkPredictionDriver::HasSimulation(); + static constexpr bool bNeedsTickService = FNetworkPredictionDriver::HasSimulation(); TFixedRollbackService(TModelDataStore* InDataStore) : DataStore(InDataStore), InternalTickService(InDataStore) { } @@ -37,7 +39,7 @@ public: const int32 ClientRecvIdx = DataStore->ClientRecv.GetIndexChecked(ID); NpResizeAndSetBit(InstanceBitArray, ClientRecvIdx); - if (bNeedsTickTickService) + if (bNeedsTickService) { InternalTickService.RegisterInstance(ID); } @@ -48,7 +50,7 @@ public: const int32 ClientRecvIdx = DataStore->ClientRecv.GetIndexChecked(ID); InstanceBitArray[ClientRecvIdx] = false; - if (bNeedsTickTickService) + if (bNeedsTickService) { InternalTickService.UnregisterInstance(ID); } @@ -100,7 +102,7 @@ public: // Maybe consider putting a copy of PhysicsActor on TClientRecvData if this lookup ever shows up in profiler TInstanceData& InstanceData = DataStore->Instances.GetByIndexChecked(ClientRecvData.InstanceIdx); - if (FNetworkPredictionDriver::ShouldReconcilePhysics(PhysicsFrame, TickState->PhysicsRewindData, InstanceData.Info.Physics, ClientRecvData.Physics)) + if (FNetworkPredictionDriver::ShouldReconcilePhysics(PhysicsFrame, TickState->PhysicsRewindData, InstanceData.Info.Driver, ClientRecvData.Physics)) { UE_NP_TRACE_SHOULD_RECONCILE(ClientRecvData.TraceID); // TODO: need a way to trace physics state bDoRollback = true; @@ -113,7 +115,7 @@ public: FNetworkPredictionDriver::LogPhysicsState(ClientRecvData.Physics); UE_LOG(LogNetworkPrediction, Warning, TEXT("Local:")); - FNetworkPredictionDriver::LogPhysicsState(PhysicsFrame, TickState->PhysicsRewindData, InstanceData.Info.Physics); + FNetworkPredictionDriver::LogPhysicsState(PhysicsFrame, TickState->PhysicsRewindData, InstanceData.Info.Driver); } } } @@ -146,15 +148,50 @@ public: return RollbackFrame; } - void BeginRollback(const int32 LocalFrame, const int32 StartTimeMS, const int32 ServerFrame) + void PreStepRollback(const FNetSimTimeStep& Step, const FServiceTimeStep& ServiceStep, const int32 Offset, const bool bFirstStepInResim) { - InternalTickService.BeginRollback(LocalFrame, StartTimeMS, ServerFrame); - } - - void StepRollback(const FNetSimTimeStep& Step, const FServiceTimeStep& ServiceStep, const int32 Offset) - { - const int32 InputFrame = ServiceStep.LocalInputFrame; + if (bFirstStepInResim) + { + // Apply corrections for the instances that have corrections on this frame + ApplyCorrection(ServiceStep.LocalInputFrame, Offset); + // Everyone must rollback Cue dispatcher and flush + InternalTickService.BeginRollback(ServiceStep.LocalInputFrame, Step.TotalSimulationTime, Step.Frame); + + // Everyone we are managing needs to rollback to this frame, even if they don't have a correction + // (this frame or this rollback - they will need to restore their collision data since we are about to retick everyone in step) + + QUICK_SCOPE_CYCLE_COUNTER(NP_Rollback_RestoreFrame); + + for (TConstSetBitIterator<> BitIt(InstanceBitArray); BitIt; ++BitIt) + { + TClientRecvData& ClientRecvData = DataStore->ClientRecv.GetByIndexChecked(BitIt.GetIndex()); + TInstanceData& InstanceData = DataStore->Instances.GetByIndexChecked(ClientRecvData.InstanceIdx); + TInstanceFrameState& Frames = DataStore->Frames.GetByIndexChecked(ClientRecvData.FramesIdx); + typename TInstanceFrameState::FFrame& LocalFrameData = Frames.Buffer[ServiceStep.LocalInputFrame]; + + FNetworkPredictionDriver::RestoreFrame(InstanceData.Info.Driver, LocalFrameData.SyncState.Get(), LocalFrameData.AuxState.Get()); + } + } + else + { + ApplyCorrection(ServiceStep.LocalInputFrame, Offset); + } + } + + void StepRollback(const FNetSimTimeStep& Step, const FServiceTimeStep& ServiceStep) final override + { + if (bNeedsTickService) + { + InternalTickService.TickResim(Step, ServiceStep); + } + } + +private: + + template + void ApplyCorrection(const int32 LocalInputFrame, const int32 Offset) + { // Insert correction data on the right frame for (TConstSetBitIterator<> BitIt(RollbackBitArray); BitIt; ++BitIt) { @@ -162,7 +199,7 @@ public: TClientRecvData& ClientRecvData = DataStore->ClientRecv.GetByIndexChecked(ClientRecvIdx); const int32 LocalFrame = ClientRecvData.ServerFrame - Offset; - if (LocalFrame == InputFrame) + if (LocalFrame == LocalInputFrame) { // Time to inject TInstanceFrameState& Frames = DataStore->Frames.GetByIndexChecked(ClientRecvData.FramesIdx); @@ -170,13 +207,14 @@ public: LocalFrameData.SyncState = ClientRecvData.SyncState; LocalFrameData.AuxState = ClientRecvData.AuxState; - + + TInstanceData& InstanceData = DataStore->Instances.GetByIndexChecked(ClientRecvData.InstanceIdx); + if (FNetworkPredictionDriver::HasPhysics()) { - TInstanceData& InstanceData = DataStore->Instances.GetByIndexChecked(ClientRecvData.InstanceIdx); - FNetworkPredictionDriver::PerformPhysicsRollback(InstanceData.Info.Physics, ClientRecvData.Physics); + FNetworkPredictionDriver::PerformPhysicsRollback(InstanceData.Info.Driver, ClientRecvData.Physics); } - + // Copy input cmd if SP if (ClientRecvData.NetRole == ROLE_SimulatedProxy) { @@ -185,16 +223,15 @@ public: RollbackBitArray[ClientRecvIdx] = false; UE_NP_TRACE_ROLLBACK_INJECT(ClientRecvData.TraceID); + + if (FlushCorrection) + { + // Push to component/collision scene immediately (we aren't garunteed to tick next, so get our collision right) + FNetworkPredictionDriver::RestoreFrame(InstanceData.Info.Driver, LocalFrameData.SyncState.Get(), LocalFrameData.AuxState.Get()); + } } } - - if (bNeedsTickTickService) - { - InternalTickService.TickResim(Step, ServiceStep); - } - } - -private: + } TBitArray<> InstanceBitArray; // Indices into DataStore->ClientRecv that we are managing TBitArray<> RollbackBitArray; // Indices into DataStore->ClientRecv that we should rollback @@ -272,12 +309,14 @@ public: LocalFrameData.SyncState = ClientRecvData.SyncState; LocalFrameData.AuxState = ClientRecvData.AuxState; + TInstanceData& Instance = DataStore->Instances.GetByIndexChecked(ClientRecvData.InstanceIdx); + + FNetworkPredictionDriver::RestoreFrame(Instance.Info.Driver, LocalFrameData.SyncState.Get(), LocalFrameData.AuxState.Get()); + // Do rollback const int32 EndFrame = TickState->PendingFrame; for (int32 Frame = LocalFrame; Frame < EndFrame; ++Frame) { - TInstanceData& Instance = DataStore->Instances.GetByIndexChecked(ClientRecvData.InstanceIdx); - const int32 InputFrame = Frame; const int32 OutputFrame = Frame+1; diff --git a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Ticking.inl b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Ticking.inl index 2bc388366500..a3ad68cfa637 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Ticking.inl +++ b/Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/Public/Services/NetworkPredictionService_Ticking.inl @@ -41,7 +41,7 @@ struct TTickUtil UE_NP_TRACE_USER_STATE_SYNC(ModelDef, OutputFrameData.SyncState.Get()); UE_NP_TRACE_USER_STATE_AUX(ModelDef, OutputFrameData.AuxState.Get()); - UE_NP_TRACE_PHYSICS_STATE_CURRENT(ModelDef, Instance.Info.Physics); + UE_NP_TRACE_PHYSICS_STATE_CURRENT(ModelDef, Instance.Info.Driver); } template diff --git a/Engine/Plugins/Runtime/NetworkPrediction/readme.txt b/Engine/Plugins/Runtime/NetworkPrediction/readme.txt index f6a1f99441bc..36a3c6620a2d 100644 --- a/Engine/Plugins/Runtime/NetworkPrediction/readme.txt +++ b/Engine/Plugins/Runtime/NetworkPrediction/readme.txt @@ -2,12 +2,45 @@ // Network Prediction Plugin // -------------------------------------------------------------------------------------------------------------------- -7-6-2020: +8-10-2020: -There are some issues with physics integration in the main branch (IsGameThread ensures). We will fix these asap -but may take a week due to summer vacations. Hold off on using physics with NP until then. +Initial mock root motion checked in. This is the begining of root motion networking which will find its way into the new movement system. +Initial focus is just on having montage-based root motion support back in, plus non animation based sources like curves (more to come here). +The animation team is planning for other animation based root motion sources. +7-31-2020: + +Couple notable changes related to physics and UPrimitiveComponents + +The system now defaults to "UPrimitiveComponents are always in sync with their physics data when NP SimulationTick functions run". +If you look at the previous version of FMockPhysicsSimulation::SimulationTick, we had to be very careful to interface directly +with the underlying PhysicsActorHandle, rather than reading data off of any UPrimitiveComponents. This causes some pretty +nasty anti-patterns, especially around scene queries. + +The cost is that we have to take a seperate pass during rollback for everyone to "RestoreFrame" prior to *anyone* resimulating +a tick step. So, an extra pass through the registered instances and touching more memory than we did before. + +It would be possible to allow users to opt out of this: to say "I know what I'm doing and I am confident I can write all +of my NP Code to interface directly with the physics engine". + +We will see if this shows up in profiles as the system continues to mature and we build real stuff with it. For now we think +its wiser to error on the side of being user friendly and less error prone. + + +The specific changes here are: +-PhysicsActorHandle is no longer a side cart of data registered with the Driver/Simulation. +-We now require FNetworkPredictionDriver::GetPhysicsPrimitiveComponent() to get to the physics data. +-"RestoreFrame" is a driver-level function similiar to FinalizeFrame but is called only during resims where we want to push + the given sync/aux state to the physics scene (Whatever that means for you). + The default implementation is provided for physics and doesn't need the user to implement anything. + + + +7-24-2020: + +Physics issues should be fixed. Still tracking down a few more bugs in the cue and interpolation systems. + 7-2-2020: Big Update This checkin is a large refactor of the NetworkPrediction system. We hope this is the last big set of changes, though diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/NetworkPredictionExtras.uplugin b/Engine/Plugins/Runtime/NetworkPredictionExtras/NetworkPredictionExtras.uplugin index b770b2f4932a..c72d27ffdf9e 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionExtras/NetworkPredictionExtras.uplugin +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/NetworkPredictionExtras.uplugin @@ -3,7 +3,7 @@ "Version": 1, "VersionName": "0.1", "FriendlyName": "Network Prediction Extras", - "Description": "Non essential classes for Network Prediction. Samples, test maps, etc intended to help developers start using teh system. Not intended to be usesd directly in a shipping product.", + "Description": "Non essential classes for Network Prediction. Samples, test maps, etc intended to help developers start using the system. Not intended to be used directly in a shipping product.", "Category": "Gameplay", "CreatedBy": "Epic Games, Inc.", "CreatedByURL": "http://epicgames.com", diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/FlyingMovementComponent.cpp b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/FlyingMovementComponent.cpp index e06362636732..ca49888b7e84 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/FlyingMovementComponent.cpp +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/FlyingMovementComponent.cpp @@ -135,15 +135,19 @@ void UFlyingMovementComponent::ProduceInput(const int32 DeltaTimeMS, FFlyingMove ProduceInputDelegate.ExecuteIfBound(DeltaTimeMS, *Cmd); } +void UFlyingMovementComponent::RestoreFrame(const FFlyingMovementSyncState* SyncState, const FFlyingMovementAuxState* AuxState) +{ + FTransform Transform(SyncState->Rotation.Quaternion(), SyncState->Location, UpdatedComponent->GetComponentTransform().GetScale3D() ); + UpdatedComponent->SetWorldTransform(Transform, false, nullptr, ETeleportType::TeleportPhysics); + UpdatedComponent->ComponentVelocity = SyncState->Velocity; +} + void UFlyingMovementComponent::FinalizeFrame(const FFlyingMovementSyncState* SyncState, const FFlyingMovementAuxState* AuxState) { - // Does checking equality make any sense here? This is unfortunate + // The component will often be in the "right place" already on FinalizeFrame, so a comparison check makes sense before setting it. if (UpdatedComponent->GetComponentLocation().Equals(SyncState->Location) == false || UpdatedComponent->GetComponentQuat().Rotator().Equals(SyncState->Rotation, FFlyingMovementSimulation::ROTATOR_TOLERANCE) == false) { - FTransform Transform(SyncState->Rotation.Quaternion(), SyncState->Location, UpdatedComponent->GetComponentTransform().GetScale3D() ); - UpdatedComponent->SetWorldTransform(Transform, false, nullptr, ETeleportType::TeleportPhysics); - - UpdatedComponent->ComponentVelocity = SyncState->Velocity; + RestoreFrame(SyncState, AuxState); } } diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/FlyingMovementSimulation.cpp b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/FlyingMovementSimulation.cpp index 19eae2645618..18b62b2ec2c9 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/FlyingMovementSimulation.cpp +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/FlyingMovementSimulation.cpp @@ -151,9 +151,6 @@ void FFlyingMovementSimulation::SimulationTick(const FNetSimTimeStep& TimeStep, const FQuat OutputQuat = Output.Sync->Rotation.Quaternion(); - // After the rotation is known, we need to sync our driver to this state. This is unfortunate and probably not best for perf, but since the Driver owned - // primitive component is ultimately what does our scene queries, we have no choice: we must get the primitive component in the state we say it should be in. - PreSimSync(*Output.Sync); const FVector LocalSpaceMovementInput = Output.Sync->Rotation.RotateVector( Input.Cmd->MovementInput ); @@ -249,16 +246,4 @@ void FFlyingMovementSimulation::SimulationTick(const FNetSimTimeStep& TimeStep, void FFlyingMovementSimulation::OnBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* Other, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) { -} - -void FFlyingMovementSimulation::PreSimSync(const FFlyingMovementSyncState& SyncState) -{ - // Does checking equality make any sense here? This is unfortunate - if (UpdatedComponent->GetComponentLocation().Equals(SyncState.Location) == false || UpdatedComponent->GetComponentQuat().Rotator().Equals(SyncState.Rotation, FFlyingMovementSimulation::ROTATOR_TOLERANCE) == false) - { - FTransform Transform(SyncState.Rotation.Quaternion(), SyncState.Location, UpdatedComponent->GetComponentTransform().GetScale3D() ); - UpdatedComponent->SetWorldTransform(Transform, false, nullptr, ETeleportType::TeleportPhysics); - - UpdatedComponent->ComponentVelocity = SyncState.Velocity; - } } \ No newline at end of file diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockAbilitySimulation.cpp b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockAbilitySimulation.cpp index 4c832a2b54d8..709ba8854b08 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockAbilitySimulation.cpp +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockAbilitySimulation.cpp @@ -62,13 +62,26 @@ public: bool FMockAbilitySyncState::ShouldReconcile(const FMockAbilitySyncState& AuthorityState) const { - // FIXME + if (Stamina != AuthorityState.Stamina) + { + return true; + } + return FFlyingMovementSyncState::ShouldReconcile(AuthorityState); } bool FMockAbilityAuxState::ShouldReconcile(const FMockAbilityAuxState& AuthorityState) const { - // FIXME + if (MaxStamina != AuthorityState.MaxStamina + || StaminaRegenRate != AuthorityState.StaminaRegenRate + || DashTimeLeft != AuthorityState.DashTimeLeft + || BlinkWarmupLeft != AuthorityState.BlinkWarmupLeft + || PrimaryCooldown != AuthorityState.PrimaryCooldown + || bIsSprinting != AuthorityState.bIsSprinting) + { + return true; + } + return FFlyingMovementAuxState::ShouldReconcile(AuthorityState); } @@ -151,7 +164,7 @@ void FMockAbilitySimulation::SimulationTick(const FNetSimTimeStep& TimeStep, con }; const bool bBlinkActivate = (Input.Cmd->bBlinkPressed && Input.Sync->Stamina > BlinkCost && bAllowNewActivations); - if (bBlinkActivate) + if(bBlinkActivate) { LocalAux.BlinkWarmupLeft = BlinkWarmupMS; @@ -543,9 +556,9 @@ float UMockFlyingAbilityComponent::GetMaxStamina() const void UMockFlyingAbilityComponent::HandleCue(const FMockAbilityBlinkActivateCue& BlinkCue, const FNetSimCueSystemParamemters& SystemParameters) { - FString RoleStr = GetOwnerRole() == ROLE_Authority ? TEXT("Server") : TEXT("Client"); - UE_LOG(LogNetworkPrediction, Display, TEXT("[%s] BlinkActivatedCue!"), *RoleStr); - + FString RoleStr = GetOwnerRole() == ROLE_Authority ? TEXT("Server") : GetOwnerRole() == ROLE_SimulatedProxy ? TEXT("SP Client") : TEXT("AP Client"); + UE_LOG(LogNetworkPrediction, Display, TEXT("[%s] BlinkActivatedCue! TimeMS: %d"), *RoleStr, SystemParameters.TimeSinceInvocation); + this->OnBlinkActivateEvent.Broadcast(BlinkCue.Destination, BlinkCue.RandomType, (float)SystemParameters.TimeSinceInvocation / 1000.f); if (SystemParameters.Callbacks) diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockPhysicsComponent.cpp b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockPhysicsComponent.cpp index c1a6971eb79a..9070c2b856d5 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockPhysicsComponent.cpp +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockPhysicsComponent.cpp @@ -56,17 +56,13 @@ void UMockPhysicsComponent::InitializeNetworkPredictionProxy() // We need valid UpdatedPrimitive and PhysicsHandle to register // This code does not handle any "not ready yet" cases if (npEnsure(UpdatedPrimitive)) - { - FPhysicsActorHandle Handle = UpdatedPrimitive->BodyInstance.GetPhysicsActorHandle(); - if (npEnsure(Handle)) - { - OwnedSimulation = MakePimpl(); - InitMockPhysicsSimulation(OwnedSimulation.Get()); - npCheckSlow(ActiveSimulation); + { + OwnedSimulation = MakePimpl(); + InitMockPhysicsSimulation(OwnedSimulation.Get()); + npCheckSlow(ActiveSimulation); - ActiveSimulation->PhysicsActorHandle = Handle; - NetworkPredictionProxy.Init(GetWorld(), GetReplicationProxies(), ActiveSimulation, this, Handle); - } + ActiveSimulation->PrimitiveComponent = UpdatedPrimitive; + NetworkPredictionProxy.Init(GetWorld(), GetReplicationProxies(), ActiveSimulation, this); } } diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockPhysicsSimulation.cpp b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockPhysicsSimulation.cpp index 334410d2e8c7..457f66e40280 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockPhysicsSimulation.cpp +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockPhysicsSimulation.cpp @@ -15,160 +15,96 @@ #include "Engine/EngineTypes.h" #include "CollisionQueryParams.h" #include "PhysicsEngine/BodyInstance.h" +#include "DrawDebugHelpers.h" +#include "Logging/LogMacros.h" +#include "NetworkPredictionCues.h" NETSIMCUE_REGISTER(FMockPhysicsJumpCue, TEXT("MockPhysicsJumpCue")); NETSIMCUE_REGISTER(FMockPhysicsChargeCue, TEXT("FMockPhysicsChargeCue")); -// -------------------------------------------------------------------------------------------------------------- -// Super simple mock example of a NP simulation running on top of physics -// -------------------------------------------------------------------------------------------------------------- - -namespace MockPhysics -{ - // This stuff is lifted directly from the physics code. It is unclear if this is an OK path to go down or if we need to - // interact with the FBodyInstance instead of the FPhysicsActorHandle. (And if this is OK, how do we avoid duplicating the - // entire physics API). - // - // An ideal world is NP works directly with the lowest level physics types possible and we only push to the unreal layer - // at the end of the (physics) frame. Obviously lots of data and settings are controlled by the unreal layer but for the - // inner resimmable loop, removing jumps to unreal are desired. - // - // This is true even for non physics sims. We desire a kinematic character mover sim to talk to the lowest level physics - // layer as possible instead of having to route even basic stuff through UPrimitiveComponent::MoveComponentImpl for example. - - bool IsRigidBodyKinematic_AssumesLocked(const FPhysicsActorHandle& InActorRef) - { - if(FPhysicsInterface::IsRigidBody(InActorRef)) - { - return FPhysicsInterface::IsKinematic_AssumesLocked(InActorRef); - } - - return false; - } - - FChaosScene* GetPhysicsScene(const FPhysicsActorHandle& ActorHandle) - { - return FPhysicsInterface::GetCurrentScene(ActorHandle); - } - - void AddForce_AssumesLocked(const FPhysicsActorHandle& ActorHandle, const FVector& Force, bool bAllowSubstepping, bool bAccelChange) - { - Chaos::TPBDRigidParticle* Rigid = ActorHandle->CastToRigidParticle(); - if(Rigid) - { - Chaos::EObjectStateType ObjectState = Rigid->ObjectState(); - - if (npEnsure(ObjectState == Chaos::EObjectStateType::Dynamic || ObjectState == Chaos::EObjectStateType::Sleeping)) - { - Rigid->SetObjectState(Chaos::EObjectStateType::Dynamic); - - const Chaos::TVector CurrentForce = Rigid->F(); - if (bAccelChange) - { - const float Mass = Rigid->M(); - const Chaos::TVector TotalAcceleration = CurrentForce + (Force * Mass); - Rigid->SetF(TotalAcceleration); - } - else - { - Rigid->SetF(CurrentForce + Force); - } - } - } - } - - bool GeomOverlapMulti(UWorld* World, const FCollisionShape& InGeom, const FVector& InPosition, const FQuat& InRotation, TArray& OutOverlaps, ECollisionChannel TraceChannel, const FCollisionQueryParams& Params, const FCollisionResponseParams& ResponseParams, const FCollisionObjectQueryParams& ObjectParams) - { - return FGenericPhysicsInterface::GeomOverlapMulti(World, InGeom, InPosition, InRotation, OutOverlaps, TraceChannel, Params, ResponseParams, ObjectParams); - - } -} - -// ------------------------------------------------------- +DEFINE_LOG_CATEGORY_STATIC(LogMockPhysicsSimulation, Log, All); void FMockPhysicsSimulation::SimulationTick(const FNetSimTimeStep& TimeStep, const TNetSimInput& Input, const TNetSimOutput& Output) { - npCheckSlow(this->PhysicsActorHandle); + npCheckSlow(this->PrimitiveComponent); const bool bAllowSubstepping = false; const bool bAccelChange = true; + const FTransform CurrentTransform = PrimitiveComponent->GetComponentTransform(); + const FVector CurrentLocation = CurrentTransform.GetLocation(); - FVector Force = Input.Cmd->MovementInput; - Force *= (1000.f * Input.Aux->ForceMultiplier); - Force.Z = 0.f; - - // Apply constant force based on MovementInput vector - FPhysicsCommand::ExecuteWrite(PhysicsActorHandle, [&](const FPhysicsActorHandle& Actor) + if (Input.Cmd->MovementInput.IsNearlyZero() == false) { - if(!MockPhysics::IsRigidBodyKinematic_AssumesLocked(Actor)) - { - if(FChaosScene* PhysScene = MockPhysics::GetPhysicsScene(Actor)) - { - MockPhysics::AddForce_AssumesLocked(Actor, Force, bAllowSubstepping, bAccelChange); - } - } - - }); + FVector Force = Input.Cmd->MovementInput; + Force *= (1000.f * Input.Aux->ForceMultiplier); + Force.Z = 0.f; + + PrimitiveComponent->AddForce(Force, NAME_None, bAccelChange); + } // Apply jump force if bJumpedPressed and not on cooldown if (Input.Cmd->bJumpedPressed && Input.Aux->JumpCooldownTime < TimeStep.TotalSimulationTime) { - FPhysicsCommand::ExecuteWrite(PhysicsActorHandle, [&](const FPhysicsActorHandle& Actor) - { - if(!MockPhysics::IsRigidBodyKinematic_AssumesLocked(Actor)) - { - if(FChaosScene* PhysScene = MockPhysics::GetPhysicsScene(Actor)) - { - FVector JumpForce(0.f, 0.f, 50000.f); - MockPhysics::AddForce_AssumesLocked(Actor, JumpForce, bAllowSubstepping, bAccelChange); - } - } - }); - - Output.Aux.Get()->JumpCooldownTime = TimeStep.TotalSimulationTime + 2000; // 2 seccond cooldown + FVector JumpForce(0.f, 0.f, 50000.f); + PrimitiveComponent->AddForce(JumpForce, NAME_None, bAccelChange); + Output.Aux.Get()->JumpCooldownTime = TimeStep.TotalSimulationTime + 2000; // 2 second cooldown + // Jump cue: this will emit an event to the game code to play a particle or whatever they want - Output.CueDispatch.Invoke( PhysicsActorHandle->X() ); + Output.CueDispatch.Invoke(CurrentLocation); } - // Charge: do radial impulse when Charge input is released + some delay if (Input.Aux->ChargeEndTime != 0) { const int32 TimeSinceChargeEnd = TimeStep.TotalSimulationTime - Input.Aux->ChargeEndTime; if (TimeSinceChargeEnd > 100) { Output.Aux.Get()->ChargeEndTime = 0; - - UWorld* World = FPhysicsInterface::GetCurrentScene(PhysicsActorHandle)->GetSolver()->GetOwner()->GetWorld(); // Awkward isn't really actually needed (only used by GeomOverlapMultiImp to get physics scene and debug drawing) + UWorld* World = PrimitiveComponent->GetWorld(); + npCheckSlow(World); + + // Obviously not good to hardcode the collision settings. We could just use the default settings on the UPrimitiveComponent, or these could be + // custom "how do you scene query for the charge ability" settings on this simulation object. + + FVector TracePosition = CurrentLocation; FCollisionShape Shape = FCollisionShape::MakeSphere(250.f); - FVector TracePosition = PhysicsActorHandle->X(); - - FQuat Rotation = FQuat::Identity; - ECollisionChannel CollisionChannel = ECollisionChannel::ECC_PhysicsBody; // obviously not good to hardcode, could be some property accessed via this simulation object + ECollisionChannel CollisionChannel = ECollisionChannel::ECC_PhysicsBody; FCollisionQueryParams QueryParams = FCollisionQueryParams::DefaultQueryParam; FCollisionResponseParams ResponseParams = FCollisionResponseParams::DefaultResponseParam; FCollisionObjectQueryParams ObjectParams(ECollisionChannel::ECC_PhysicsBody); TArray Overlaps; - MockPhysics::GeomOverlapMulti(World, Shape, TracePosition, Rotation, Overlaps, CollisionChannel, QueryParams, ResponseParams, ObjectParams); + World->OverlapMultiByChannel(Overlaps, TracePosition, FQuat::Identity, CollisionChannel, Shape); for (FOverlapResult& Result : Overlaps) { //UE_LOG(LogTemp, Warning, TEXT(" Hit: %s"), *GetNameSafe(Result.Actor.Get())); if (UPrimitiveComponent* PrimitiveComp = Result.Component.Get()) { - // We can't use the Primitive component's data here because it is not up to date! - // We need to get the real physics data from the underlying body. - FBodyInstance* Instance = PrimitiveComp->GetBodyInstance(); - if (Instance) + // We are just applying an impulse to all simulating primitive components. + // A real game would have game specific logic determining which actors/components can be affected + if (PrimitiveComp->IsSimulatingPhysics()) { - FPhysicsActorHandle HitHandle = Instance->GetPhysicsActorHandle(); - FVector PhysicsLocation = HitHandle->X(); + // Debug: confirm the hit component and its underlying physics body are in sync + if (FBodyInstance* Instance = PrimitiveComp->GetBodyInstance()) + { + FPhysicsActorHandle HitHandle = Instance->GetPhysicsActorHandle(); + const FVector PhysicsLocation = HitHandle->X(); - FVector Dir = (PhysicsLocation - TracePosition); + const FVector Delta = PhysicsLocation - PrimitiveComp->GetComponentLocation(); + if (Delta.Size2D() > 0.1f) + { + UE_LOG(LogMockPhysicsSimulation, Warning, TEXT("FMockPhysicsSimulation Not in sync! %s %s (%s)"), *PhysicsLocation.ToString(), *PrimitiveComp->GetComponentLocation().ToString(), *Delta.ToString()); + DrawDebugSphere(PrimitiveComp->GetWorld(), PhysicsLocation, 100.f, 32, FColor::Red, true, 10.f); + DrawDebugSphere(PrimitiveComp->GetWorld(), PrimitiveComp->GetComponentLocation(), 100.f, 32, FColor::Green, true, 10.f); + npEnsure(false); + } + } + + FVector Dir = (PrimitiveComp->GetComponentLocation() - TracePosition); Dir.Z = 0.f; Dir.Normalize(); @@ -179,6 +115,7 @@ void FMockPhysicsSimulation::SimulationTick(const FNetSimTimeStep& TimeStep, con } } } + Output.CueDispatch.Invoke( TracePosition ); } } @@ -195,6 +132,7 @@ void FMockPhysicsSimulation::SimulationTick(const FNetSimTimeStep& TimeStep, con if (bWasCharging && !Input.Cmd->bChargePressed) { + // Release Output.Aux.Get()->ChargeStartTime = 0; Output.Aux.Get()->ChargeEndTime = TimeStep.TotalSimulationTime; } diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockRootMotionComponent.cpp b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockRootMotionComponent.cpp new file mode 100644 index 000000000000..58ee14e33b3b --- /dev/null +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockRootMotionComponent.cpp @@ -0,0 +1,191 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MockRootMotionComponent.h" +#include "NetworkPredictionModelDef.h" +#include "NetworkPredictionModelDefRegistry.h" +#include "NetworkPredictionProxyInit.h" +#include "Components/SkeletalMeshComponent.h" +#include "MockRootMotionSourceDataAsset.h" +#include "Animation/AnimMontage.h" +#include "NetworkPredictionProxyWrite.h" +#include "Curves/CurveVector.h" +#include "Animation/AnimInstance.h" +#include "Templates/UniquePtr.h" + +DEFINE_LOG_CATEGORY_STATIC(LogMockRootMotionComponent, Log, All); + +/** NetworkedSimulation Model type */ +class FMockRootMotionModelDef : public FNetworkPredictionModelDef +{ +public: + + NP_MODEL_BODY(); + + using StateTypes = MockRootMotionStateTypes; + using Simulation = FMockRootMotionSimulation; + using Driver = UMockRootMotionComponent; + + static const TCHAR* GetName() { return TEXT("MockRootMotion"); } + static constexpr int32 GetSortPriority() { return (int32)ENetworkPredictionSortPriority::PreKinematicMovers + 6; } +}; + +NP_MODEL_REGISTER(FMockRootMotionModelDef); + +// ------------------------------------------------------------------------------------------------------- +// UMockRootMotionComponent +// ------------------------------------------------------------------------------------------------------- + +void UMockRootMotionComponent::SetUpdatedComponent(USceneComponent* NewUpdatedComponent) +{ + Super::SetUpdatedComponent(NewUpdatedComponent); + FindAndCacheAnimInstance(); +} + +void UMockRootMotionComponent::FindAndCacheAnimInstance() +{ + if (AnimInstance != nullptr) + { + return; + } + + AActor* Owner = GetOwner(); + npCheckSlow(Owner); + + USkeletalMeshComponent* SkelMeshComp = Cast(Owner->GetRootComponent()); + if (SkelMeshComp == nullptr) + { + SkelMeshComp = Owner->FindComponentByClass(); + } + + if (SkelMeshComp) + { + AnimInstance = SkelMeshComp->GetAnimInstance(); + } +} + +void UMockRootMotionComponent::InitializeNetworkPredictionProxy() +{ + if (!npEnsureMsgf(RootMotionSourceDataAsset != nullptr, TEXT("No RootMotionSourceDataAsset set on %s. Skipping root motion init."), *GetPathName())) + { + return; + } + + if (!npEnsureMsgf(UpdatedComponent != nullptr, TEXT("No UpdatedComponent set on %s. Skipping root motion init."), *GetPathName())) + { + return; + } + + FindAndCacheAnimInstance(); + + if (!npEnsureMsgf(AnimInstance != nullptr, TEXT("No AnimInstance set on %s. Skipping root motion init."), *GetPathName())) + { + return; + } + + OwnedMockRootMotionSimulation = MakeUnique(); + OwnedMockRootMotionSimulation->SourceMap = this->RootMotionSourceDataAsset; + OwnedMockRootMotionSimulation->RootMotionComponent = AnimInstance->GetOwningComponent(); + OwnedMockRootMotionSimulation->SetComponents(UpdatedComponent, UpdatedPrimitive); + + NetworkPredictionProxy.Init(GetWorld(), GetReplicationProxies(), OwnedMockRootMotionSimulation.Get(), this); +} + +void UMockRootMotionComponent::InitializeSimulationState(FMockRootMotionSyncState* SyncState, FMockRootMotionAuxState* AuxState) +{ + // This assumes no animation is currently playing. Any "play anim on startup" should go through the same path + // as playing an animation at runttime wrt NP. + + npCheckSlow(UpdatedComponent); + npCheckSlow(SyncState); + + SyncState->Location = UpdatedComponent->GetComponentLocation(); + SyncState->Rotation = UpdatedComponent->GetComponentQuat().Rotator(); +} + +void UMockRootMotionComponent::ProduceInput(const int32 SimTimeMS, FMockRootMotionInputCmd* Cmd) +{ + npCheckSlow(Cmd); + *Cmd = PendingInputCmd; +} + +void UMockRootMotionComponent::RestoreFrame(const FMockRootMotionSyncState* SyncState, const FMockRootMotionAuxState* AuxState) +{ + npCheckSlow(UpdatedComponent); + + // Update component transform + FTransform Transform(SyncState->Rotation.Quaternion(), SyncState->Location, UpdatedComponent->GetComponentTransform().GetScale3D() ); + UpdatedComponent->SetWorldTransform(Transform, false, nullptr, ETeleportType::TeleportPhysics); +} + +void UMockRootMotionComponent::FinalizeFrame(const FMockRootMotionSyncState* SyncState, const FMockRootMotionAuxState* AuxState) +{ + npCheckSlow(AnimInstance); + npCheckSlow(RootMotionSourceDataAsset); + + RestoreFrame(SyncState, AuxState); + + // Update animation state (pose) - make sure it matches SyncState. + // This only needs to be done in FinalizeFrame because the pose does not directly affect the simulation + RootMotionSourceDataAsset->FinalizePose(SyncState, AnimInstance); +} + +template +int32 UMockRootMotionComponent::PlayRootMotionByAssetType(AssetType* Asset) +{ + if (!npEnsureMsgf(RootMotionSourceDataAsset != nullptr, TEXT("No RootMotionSourceDataAsset set on %s. Skipping PlayRootMotion call."), *GetPathName())) + { + return INDEX_NONE; + } + + int32 ID = RootMotionSourceDataAsset->FindRootMotionSourceID(Asset); + if (ID == INDEX_NONE) + { + UE_LOG(LogMockRootMotionComponent, Warning, TEXT("Invalid RootMotion asset %s. Not in %s. Skipping"), *Asset->GetName(), *RootMotionSourceDataAsset->GetPathName()); + return ID; + } + + PendingInputCmd.PlaySourceID = ID; + PendingInputCmd.PlayCount++; + + return ID; +} + +void UMockRootMotionComponent::Input_PlayRootMotionByMontage(UAnimMontage* Montage) +{ + PlayRootMotionByAssetType(Montage); +} + +void UMockRootMotionComponent::Input_PlayRootMotionByCurve(UCurveVector* CurveVector, FVector Scale) +{ + PlayRootMotionByAssetType(CurveVector); + PendingInputCmd.Parameters.SetByType(&Scale); +} + +void UMockRootMotionComponent::PlayRootMotionMontage(UAnimMontage* Montage, float PlayRate) +{ + if (!npEnsureMsgf(RootMotionSourceDataAsset != nullptr, TEXT("No RootMotionSourceDataAsset set on %s. Skipping PlayRootMotionMontage."), *GetPathName())) + { + return; + } + + int32 ID = RootMotionSourceDataAsset->FindRootMotionSourceID(Montage); + if (ID == INDEX_NONE) + { + UE_LOG(LogMockRootMotionComponent, Warning, TEXT("Invalid Montage %s. Not in %s. Skipping"), *Montage->GetName(), *RootMotionSourceDataAsset->GetPathName()); + return; + } + + // We are writing directly to the sync state here, not setting up the pending input cmd. + // The server can always do this - they are the authority and can set the sync state to whatever they want. + // The client is free to do this - but will get a mispredict if the server does not do it too. + // This type of prediction is "risky" - the client has to predict doing it at the same simulation time as the server + // but since this function (UMockRootMotionComponent::PlayRootMotionMontage) is callable from anywhere, its not possible + // to guaranteed. If you want guaranteed prediction, do it via InputCmd and the SimulationTick logic. + + NetworkPredictionProxy.WriteSyncState([ID, PlayRate](FMockRootMotionSyncState& Sync) + { + Sync.RootMotionSourceID = ID; + Sync.PlayRate = PlayRate; + Sync.PlayPosition = 0.f; + }, "UMockRootMotionComponent::PlayRootMotionMontage"); // The string here is for Insights tracing. "Who did this?" +} \ No newline at end of file diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockRootMotionSimulation.cpp b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockRootMotionSimulation.cpp new file mode 100644 index 000000000000..2be5a0febc8e --- /dev/null +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockRootMotionSimulation.cpp @@ -0,0 +1,104 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MockRootMotionSimulation.h" +#include "NetworkPredictionCheck.h" +#include "GameFramework/Actor.h" + +DEFINE_LOG_CATEGORY_STATIC(LogMockRootMotion, Log, All); + +// Core tick function for updating root motion. +// -Handle InputCmd: possibly turn input state into a playing RootMotion source +// -If RootMotion source is playing, call into RootMotionSourceMap to evaluate (get the delta transform and update the output Sync state) +// -Use delta transform to actually sweep move our collision through the world + +void FMockRootMotionSimulation::SimulationTick(const FNetSimTimeStep& TimeStep, const TNetSimInput& Input, const TNetSimOutput& Output) +{ + npCheckSlow(this->SourceMap); + npCheckSlow(UpdatedComponent); + npCheckSlow(RootMotionComponent); + + FMockRootMotionSyncState LocalSync = *Input.Sync; + const FMockRootMotionAuxState* LocalAuxPtr = Input.Aux; + + // See if InputCmd wants to play a new RootMotion (only if we aren't currently playing one) + if (Input.Cmd->PlayCount != Input.Sync->InputPlayCount && LocalSync.RootMotionSourceID == INDEX_NONE) + { + // NOTE: This is not a typical gameplay setup. This code essentially allows the client to + // start new RootMotionSources anytime it wants to. + // + // A real game is going to have higher level logic. Like "InputCmd wants to activate an ability, can we do that?" + // and if so; "update Sync State to reflect the new animation". + + LocalSync.RootMotionSourceID = Input.Cmd->PlaySourceID; + LocalSync.PlayRate = 1.f; // input initiated root motions are assumed to be 1.f play rate + LocalSync.PlayPosition = 0.f; // also assumed to start at t=0 + + // Copy the play parameters to the aux state + FMockRootMotionAuxState* OutAux = Output.Aux.Get(); + OutAux->Parameters = Input.Cmd->Parameters; + LocalAuxPtr = OutAux; + + // Question: should we advance the root motion here or not? When you play a new montage, do you expect the next render + // frame to be @ t=0? Or should we advance it by TimeStep.StepMS? + // + // It seems one frame of no movement would be bad. If we were chaining animations together, we wouldn't want to enforce + // a system one stationary frame (which will depend on TimeStep.MS!) + } + + // Always copy the playcount through (otherwise we end up queing new RootMotions while one is playing) + LocalSync.InputPlayCount = Input.Cmd->PlayCount; + + // Copy input to output + *Output.Sync = LocalSync; + + if (LocalSync.RootMotionSourceID == INDEX_NONE) + { + // We aren't playing root motion so there is nothing else to do, + return; + } + + // Call into root motion source map to actually update the root motino state + FTransform LocalDeltaTransform = this->SourceMap->StepRootMotion(TimeStep, &LocalSync, Output.Sync, LocalAuxPtr); + + // StepRootMotion should return local delta transform, we need to convert to world + FTransform DeltaWorldTransform; + { + // Calculate new actor transform after applying root motion to this component + // this was lifted from USkeletalMeshComponent::ConvertLocalRootMotionToWorld + const FTransform ActorToWorld = RootMotionComponent->GetOwner()->GetTransform(); + + const FTransform ComponentToActor = ActorToWorld.GetRelativeTransform(RootMotionComponent->GetComponentTransform()); + const FTransform NewComponentToWorld = LocalDeltaTransform * RootMotionComponent->GetComponentTransform(); + const FTransform NewActorTransform = ComponentToActor * NewComponentToWorld; + + const FVector DeltaWorldTranslation = NewActorTransform.GetTranslation() - ActorToWorld.GetTranslation(); + + const FQuat NewWorldRotation = RootMotionComponent->GetComponentTransform().GetRotation() * LocalDeltaTransform.GetRotation(); + const FQuat DeltaWorldRotation = NewWorldRotation * RootMotionComponent->GetComponentTransform().GetRotation().Inverse(); + + DeltaWorldTransform = FTransform(DeltaWorldRotation, DeltaWorldTranslation); + + /* + UE_LOG(LogRootMotion, Log, TEXT("ConvertLocalRootMotionToWorld LocalT: %s, LocalR: %s, WorldT: %s, WorldR: %s."), + *InTransform.GetTranslation().ToCompactString(), *InTransform.GetRotation().Rotator().ToCompactString(), + *DeltaWorldTransform.GetTranslation().ToCompactString(), *DeltaWorldTransform.GetRotation().Rotator().ToCompactString()); + */ + } + + // --------------------------------------------------------------------- + // Move the component via collision sweep + // -This could be better: to much converting between FTransforms, Rotators, quats, etc. + // -Problem of "movement can be blocked but rotation can't". Can be unclear exactly what to do + // (should block in movement cause a block in rotation?) + // --------------------------------------------------------------------- + FQuat NewRotation(DeltaWorldTransform.Rotator() + LocalSync.Rotation); + + // Actually do the sweep + FHitResult HitResult; + SafeMoveUpdatedComponent(DeltaWorldTransform.GetTranslation(), NewRotation, true, HitResult, ETeleportType::TeleportPhysics); + + // The component was actually moved, so pull transform back out + FTransform EndTransform = UpdatedComponent->GetComponentTransform(); + Output.Sync->Location = EndTransform.GetTranslation(); + Output.Sync->Rotation = EndTransform.GetRotation().Rotator(); +} \ No newline at end of file diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockRootMotionSourceDataAsset.cpp b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockRootMotionSourceDataAsset.cpp new file mode 100644 index 000000000000..a35516affaa6 --- /dev/null +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Private/MockRootMotionSourceDataAsset.cpp @@ -0,0 +1,161 @@ +// Copyright Epic Games, Inc. All Rights Reserved + + +#include "MockRootMotionSourceDataAsset.h" +#include "Curves/CurveVector.h" +#include "Animation/AnimMontage.h" +#include "NetworkPredictionCheck.h" +#include "Animation/AnimSequence.h" +#include "Animation/AnimCompositeBase.h" +#include "Animation/AnimInstance.h" + +DEFINE_LOG_CATEGORY_STATIC(LogMockRootMotionSourceDataAsset, Log, All); + +int32 UMockRootMotionSourceDataAsset::FindRootMotionSourceID(UAnimMontage* Montage) +{ + return Montages.Montages.Find(Montage); +} + +int32 UMockRootMotionSourceDataAsset::FindRootMotionSourceID(UCurveVector* Curve) +{ + int32 CurveIdx = Curves.Curves.Find(Curve); + if (CurveIdx != INDEX_NONE) + { + // Crude way of mapping ID into curves/montages. This wont scale well with lots of different + // types of RootMotion Sources + CurveIdx += Montages.Montages.Num(); + } + return CurveIdx; +} + +bool UMockRootMotionSourceDataAsset::IsValidSourceID(int32 RootMotionSourceID) const +{ + return Montages.Montages.IsValidIndex(RootMotionSourceID); +} + +FTransform UMockRootMotionSourceDataAsset::StepRootMotion(const FNetSimTimeStep& TimeStep, const FMockRootMotionSyncState* In, FMockRootMotionSyncState* Out, const FMockRootMotionAuxState* Aux) +{ + npCheckSlow(In); + npCheckSlow(In->RootMotionSourceID != INDEX_NONE); + + // Map ID to montage or curves and call the _Imply function. + if (Montages.Montages.IsValidIndex(In->RootMotionSourceID)) + { + return StepRootMotion_Montage(Montages.Montages[In->RootMotionSourceID], TimeStep, In, Out, Aux); + } + else + { + const int32 CurveIdx = In->RootMotionSourceID - Montages.Montages.Num(); + if (Curves.Curves.IsValidIndex(CurveIdx)) + { + return StepRootMotion_Curve(Curves.Curves[CurveIdx], TimeStep, In, Out, Aux); + } + else + { + npEnsureMsgf(false, TEXT("Invalid RootMotionSourceID: %d. Not mapped in %s. Skipping Update"), In->RootMotionSourceID, *GetName()); + } + } + + return FTransform::Identity; +} + +FTransform UMockRootMotionSourceDataAsset::StepRootMotion_Curve(UCurveVector* Curve, const FNetSimTimeStep& TimeStep, const FMockRootMotionSyncState* In, FMockRootMotionSyncState* Out, const FMockRootMotionAuxState* Aux) +{ + npCheckSlow(Curve); + npCheckSlow(In); + npCheckSlow(Out); + npCheckSlow(Aux); + + float MinPosition = 0.f; + float MaxPosition = 0.f; + Curve->GetTimeRange(MinPosition, MaxPosition); + + const float DeltaSeconds = (float)TimeStep.StepMS / 1000.f; + const float EndPosition = FMath::Clamp(In->PlayPosition + (DeltaSeconds * In->PlayRate), MinPosition, MaxPosition); + + if (EndPosition < MaxPosition) + { + Out->PlayPosition = EndPosition; + } + else + { + // The RootMotion is finished + // We will want to support looping/clamping options at some level. For now the RootMotion just finishes + Out->RootMotionSourceID = INDEX_NONE; + } + + const FVector Start = Curve->GetVectorValue(In->PlayPosition); + const FVector End = Curve->GetVectorValue(EndPosition); + + FVector DeltaV = (End - Start); + + // Allow Aux parameters to scale the translation from the curve + // Note that just shoving an FVector in here feels too fragile. We want a more formal binding of root motion source -> parameters + if (const FVector* Scale = Aux->Parameters.GetByType()) + { + DeltaV *= *Scale; + } + else + { + UE_LOG(LogMockRootMotionSourceDataAsset, Warning, TEXT("Invalid Aux parameters when evaluating curve motion. Size: %d"), Aux->Parameters.Data.Num()); + } + + FTransform DeltaTrans = FTransform::Identity; + DeltaTrans.SetTranslation(DeltaV); + + return DeltaTrans; +} + +FTransform UMockRootMotionSourceDataAsset::StepRootMotion_Montage(UAnimMontage* Montage, const FNetSimTimeStep& TimeStep, const FMockRootMotionSyncState* In, FMockRootMotionSyncState* Out, const FMockRootMotionAuxState* Aux) +{ + npCheckSlow(Montage); + npCheckSlow(In); + npCheckSlow(Out); + npCheckSlow(Aux); + + const float MinPosition = 0.f; + const float MaxPosition = Montage->GetPlayLength(); + + const float DeltaSeconds = (float)TimeStep.StepMS / 1000.f; + const float EndPosition = FMath::Clamp(In->PlayPosition + (DeltaSeconds * In->PlayRate), MinPosition, MaxPosition); + + if (EndPosition < MaxPosition) + { + Out->PlayPosition = EndPosition; + } + else + { + // The RootMotion is finished + // We will want to support looping/clamping options at some level. For now the RootMotion just finishes + Out->RootMotionSourceID = INDEX_NONE; + } + + + FTransform NewTransform; + + // Extract root motion from animation sequence + NewTransform = Montage->ExtractRootMotionFromTrackRange(In->PlayPosition, EndPosition); + + //UE_LOG(LogTemp, Warning, TEXT("%.4f: Delta: %s"), EndPosition, *NewTransform.GetTranslation().ToString()); + return NewTransform; +} + +void UMockRootMotionSourceDataAsset::FinalizePose(const FMockRootMotionSyncState* Sync, UAnimInstance* AnimInstance) +{ + npCheckSlow(Sync); + npCheckSlow(AnimInstance); + + // Push the authoratative sync state to the anim instance + // This is an important part of the system - the sync state can and will change out from underneath you + // so its important we have some way to tell the animinstance 'this is what you should look like right now' + if (Montages.Montages.IsValidIndex(Sync->RootMotionSourceID)) + { + UAnimMontage* Montage = Montages.Montages[Sync->RootMotionSourceID]; + if (!AnimInstance->Montage_IsPlaying(Montage)) + { + AnimInstance->Montage_Play(Montage); + } + + AnimInstance->Montage_SetPosition(Montage, Sync->PlayPosition); + } +} \ No newline at end of file diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/BaseMovementSimulation.h b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/BaseMovementSimulation.h index e1afbdb8ba93..ee894f2a1ba1 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/BaseMovementSimulation.h +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/BaseMovementSimulation.h @@ -34,7 +34,7 @@ public: /** Flags that control the behavior of calls to MoveComponent() on our UpdatedComponent. */ mutable EMoveComponentFlags MoveComponentFlags = MOVECOMP_NoFlags; // Mutable because we sometimes need to disable these flags ::ResolvePenetration. Better way may be possible - virtual void SetComponents(USceneComponent* InUpdatedComponent, UPrimitiveComponent* InPrimitiveComponent) + void SetComponents(USceneComponent* InUpdatedComponent, UPrimitiveComponent* InPrimitiveComponent) { UpdatedComponent = InUpdatedComponent; UpdatedPrimitive = InPrimitiveComponent; diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/FlyingMovementComponent.h b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/FlyingMovementComponent.h index cdd19b8556a2..a32021cd26c6 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/FlyingMovementComponent.h +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/FlyingMovementComponent.h @@ -41,6 +41,9 @@ public: // Get latest local input prior to simulation step void ProduceInput(const int32 DeltaTimeMS, FFlyingMovementInputCmd* Cmd); + // Restore a previous frame prior to resimulating + void RestoreFrame(const FFlyingMovementSyncState* SyncState, const FFlyingMovementAuxState* AuxState); + // Take output for simulation void FinalizeFrame(const FFlyingMovementSyncState* SyncState, const FFlyingMovementAuxState* AuxState); diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/FlyingMovementSimulation.h b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/FlyingMovementSimulation.h index 47f36796611d..cf823b1d2d1c 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/FlyingMovementSimulation.h +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/FlyingMovementSimulation.h @@ -129,11 +129,6 @@ public: /** Main update function */ NETWORKPREDICTIONEXTRAS_API void SimulationTick(const FNetSimTimeStep& TimeStep, const TNetSimInput& Input, const TNetSimOutput& Output); - // Called prior to running the sim to make sure to make sure the collision component is in the right place. - // This is unfortunate and not good, but is needed to ensure our collision and world position have not been moved out from under us. - // Refactoring primitive component movement to allow the sim to do all collision queries outside of the component code would be ideal. - void PreSimSync(const FFlyingMovementSyncState& SyncState); - // Callbacks void OnBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* Other, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockPhysicsSimulation.h b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockPhysicsSimulation.h index 29238420e23b..4186b5d38b8d 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockPhysicsSimulation.h +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockPhysicsSimulation.h @@ -102,7 +102,8 @@ public: void SimulationTick(const FNetSimTimeStep& TimeStep, const TNetSimInput& Input, const TNetSimOutput& Output); - FPhysicsActorHandle PhysicsActorHandle; + // Physics Component we are driving + UPrimitiveComponent* PrimitiveComponent = nullptr; }; struct FMockPhysicsJumpCue diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockRootMotionComponent.h b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockRootMotionComponent.h new file mode 100644 index 000000000000..81a4af6524fa --- /dev/null +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockRootMotionComponent.h @@ -0,0 +1,74 @@ +// Copyright Epic Games, Inc. All Rights Reserved + +#pragma once +#include "BaseMovementComponent.h" +#include "MockRootMotionSimulation.h" + +#include "MockRootMotionComponent.generated.h" + +class UAnimInstance; +class UAnimMontage; +class UMockRootMotionSourceDataAsset; +class UCurveVector; + +// This component acts as the Driver for the FMockRootMotionSimulation +// It is essentially a standin for the movement component, and would be replaced by "new movement system" component. +// If we support "root motion without movement component" then this could either be that component, or possibly +// built into or inherit from a USkeletalMeshComponent. +// +// The main thing this provides is: +// -Interface for initiating root motions through the NP system (via client Input and via server "OOB" writes) +// -FinalizeFrame: take the output of the NP simulation and push it to the movement/animation components +// =Place holder for UMockRootMotionSourceDataAsset (the temp thing that maps our RootMotionSourceIDs -> actual sources) + +UCLASS(BlueprintType, meta=(BlueprintSpawnableComponent)) +class NETWORKPREDICTIONEXTRAS_API UMockRootMotionComponent : public UBaseMovementComponent +{ +public: + + GENERATED_BODY() + + virtual void SetUpdatedComponent(USceneComponent* NewUpdatedComponent) override; + + void InitializeSimulationState(FMockRootMotionSyncState* SyncState, FMockRootMotionAuxState* AuxState); + void ProduceInput(const int32 SimTimeMS, FMockRootMotionInputCmd* Cmd); + void RestoreFrame(const FMockRootMotionSyncState* SyncState, const FMockRootMotionAuxState* AuxState); + void FinalizeFrame(const FMockRootMotionSyncState* SyncState, const FMockRootMotionAuxState* AuxState); + + // Callable by controlling client only. Queues root motion source to be played. By UAnimMontage in RootMotionSourceDataAsset. + UFUNCTION(BlueprintCallable, Category=Input) + void Input_PlayRootMotionByMontage(UAnimMontage* Montage); + + // Callable by controlling client only. Queues root motion source to be played. By UCurveVector in RootMotionSourceDataAsset. + // Scale is an optional scaler for the curve + UFUNCTION(BlueprintCallable, Category=Input) + void Input_PlayRootMotionByCurve(UCurveVector* CurveVector, FVector Scale = FVector(1.f)); + + // Callable by authority. Plays "out of band" animation: e.g, directly sets the RootMotionSourceID on the sync state, rather than the pending InputCmd. + // This is analogous to outside code teleporting the actor (outside of the core simulation function) + UFUNCTION(BlueprintCallable, Category=Animation) + void PlayRootMotionMontage(UAnimMontage* Montage, float PlayRate); + +protected: + + void FindAndCacheAnimInstance(); + + // Data asset that holds all RootMotinoSource mappings. This could have also been a global thing that all actors share. + // (though that would create the problem of not all sources are compatible with all meshes) + // This should be viewed as a holdover until we settle on a place for how we want to manage root motion sources. + UPROPERTY(EditDefaultsOnly, Category="Root Motion") + UMockRootMotionSourceDataAsset* RootMotionSourceDataAsset; + + // Next local InputCmd that will be submitted. This is a simple way of getting input to the system + FMockRootMotionInputCmd PendingInputCmd; + + void InitializeNetworkPredictionProxy() override; + TUniquePtr OwnedMockRootMotionSimulation; + + UAnimInstance* AnimInstance = nullptr; + +private: + + template + int32 PlayRootMotionByAssetType(AssetType* Asset); +}; \ No newline at end of file diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockRootMotionSimulation.h b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockRootMotionSimulation.h new file mode 100644 index 000000000000..632a8fb5f3a9 --- /dev/null +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockRootMotionSimulation.h @@ -0,0 +1,271 @@ +// Copyright Epic Games, Inc. All Rights Reserved + +#pragma once +#include "Engine/EngineTypes.h" +#include "Misc/StringBuilder.h" +#include "NetworkPredictionStateTypes.h" +#include "NetworkPredictionSimulation.h" +#include "NetworkPredictionTickState.h" +#include "NetworkPredictionReplicationProxy.h" +#include "BaseMovementSimulation.h" + +class UAnimInstance; + +// Very crude parameter pack for root motion parameters. The idea being each root motion source can have source-defined +// parameters. This version just works on a block of memory without safety or optimizations (NetSerialize does not quantize anything) +template +struct TMockParameterPack +{ + TArray> Data; + + void NetSerialize(const FNetSerializeParams& P) + { + npCheckf(Data.Num() <= 255, TEXT("Parameter size too big %d"), Data.Num()); + + if(P.Ar.IsSaving()) + { + uint8 Size = Data.Num(); + P.Ar << Size; + P.Ar.Serialize(Data.GetData(), Data.Num()); + } + else + { + uint8 Size = 0; + P.Ar << Size; + Data.SetNumUninitialized(Size, false); + P.Ar.Serialize(Data.GetData(), Size); + } + } + + void ToString(FAnsiStringBuilderBase& Out) const + { + Out.Appendf("ParameterPack Size: %d", Data.Num()); + } + + template + void SetByType(const T* RawData) + { + Data.SetNumUninitialized(sizeof(T), false); + FMemory::Memcpy(Data.GetData(), RawData, sizeof(T)); + } + + template + const T* GetByType() const + { + if (npEnsureMsgf(Data.Num() == sizeof(T), TEXT("Parameter size %d does not match Type size: %d"), Data.Num(), sizeof(T))) + { + return (T*)Data.GetData(); + } + return nullptr; + } + + bool operator==(const TMockParameterPack &Other) const + { + return Data.Num() == Other.Data.Num() && FMemory::Memcmp(Data.GetData(), Other.Data.GetData(), Data.Num()) == 0; + } + + bool operator!=(const TMockParameterPack &Other) const { return !(*this == Other); } +}; + + + +// This is an initial prototype of root motion in the Network Prediction system. It is meant to flesh out some ideas before +// settling on a final design for the future of root motion. In other words, we do not expect the code here in NetworkPredictionExtas +// to be used directly in shipping systems. + +// High level idea: +// -Get montage based root motion stood up +// -Expand on the idea of "Root Motion Sources" meaning any kind of motion-driving logic that can be decoupled from the "character/pawn movement system" +// -This would include simple curve based motions, programatically defined motion ("move towards actor"), or more complex, dynamic animation based motion. +// -Eventually this folds back into the "new movement system" and/or possibly becomes something that can stand on its own without being driven by the former (TBD). + +// Issues / Callouts: +// -UObject* replication inside NP sync/aux state is not supported (Without disabling WithNetSharedSerialization) +// +// + +struct FMockRootMotionInputCmd +{ + // State that is generated by the client. Strictly speaking for RootMotion, an InputCmd doesn't + // make sense - input is the concern of the higher level system that would decide to play + // RootMotions. For this mock example though, we'll make an InputCmd that can trigger an animation + // to play from the client. That way, the client can initiate an animation predictively. + // + // The real world example would be more like "InputCmd says activate an abilty, the ability says + // to play a montage". + + int32 PlaySourceID = INDEX_NONE; // Which RootMotionSourceID to trigger + int32 PlayCount = 0; // Counter - to allow back to back playing of same anim + + TMockParameterPack<> Parameters; + + void NetSerialize(const FNetSerializeParams& P) + { + P.Ar << PlaySourceID; + P.Ar << PlayCount; + + Parameters.NetSerialize(P); + } + + void ToString(FAnsiStringBuilderBase& Out) const + { + Out.Appendf("PlaySourceID: %d\n", PlaySourceID); + Out.Appendf("PlayCount: %d\n", PlayCount); + + Parameters.ToString(Out); + } +}; + +struct FMockRootMotionSyncState +{ + // Transform state. In the final version we may want to decouple this from the animation state, + // For example if a "movement simulation" was driving things, it may "own" the transform + // and feed it into the root motion system. But this is meant to be a stand alone mock example. + FVector Location; + FRotator Rotation; + + // --------------------------------------------- + // Core Root Motion state + // --------------------------------------------- + + // Maps to the actual thing driving root motion. Initially this will map to a UAnimMontage, + // but we really want this to be able to map to anything that can drive motion. + int32 RootMotionSourceID = INDEX_NONE; + + // The root motion state for this instance. This is hard coded for montages right now. + // We could instead allocate a generic block of memory for the RootMotionSourceID to + // use however it wants. This would allow different root motion sources to have different + // internal state (PlayPosition) and different parameterization (PlayRate). + + float PlayPosition = 0.f; + float PlayRate = 0.f; + + // Counter to catch new input cmds + int32 InputPlayCount = 0; + + void NetSerialize(const FNetSerializeParams& P) + { + P.Ar << Location; + P.Ar << Rotation; + + P.Ar << RootMotionSourceID; + P.Ar << PlayPosition; + P.Ar << PlayRate; + } + void ToString(FAnsiStringBuilderBase& Out) const + { + Out.Appendf("Loc: X=%.2f Y=%.2f Z=%.2f\n", Location.X, Location.Y, Location.Z); + Out.Appendf("Rot: P=%.2f Y=%.2f R=%.2f\n", Rotation.Pitch, Rotation.Yaw, Rotation.Roll); + + Out.Appendf("RootMotionSourceID: %d\n", RootMotionSourceID); + Out.Appendf("PlayPosition: %.2f\n", PlayPosition); + Out.Appendf("PlayRate: %.2f\n", PlayRate); + } + + void Interpolate(const FMockRootMotionSyncState* From, const FMockRootMotionSyncState* To, float PCT) + { + static constexpr float TeleportThreshold = 1000.f * 1000.f; + if (FVector::DistSquared(From->Location, To->Location) > TeleportThreshold) + { + *this = *To; + } + else + { + Location = FMath::Lerp(From->Location, To->Location, PCT); + Rotation = FMath::Lerp(From->Rotation, To->Rotation, PCT); + } + + // This is a case where strictly interpolating Sync/Aux state may not be enough in all situations. + // While its fine for interpolating across the same RootMotionSourceID, when interpolating between + // different sources, the Driver may want to blend between two animation poses for example + // (so rather than interpolating Sync/Aux state, we want to interpolate Driver state). + // This could be made possible by template specialization of FNetworkDriver::Interpolate + // (currently it is not supported, but we probably should do it) + + if (From->RootMotionSourceID == To->RootMotionSourceID) + { + this->RootMotionSourceID = To->RootMotionSourceID; + this->PlayPosition = FMath::Lerp(From->PlayPosition, To->PlayPosition, PCT); + this->PlayRate = FMath::Lerp(From->PlayRate, To->PlayRate, PCT); + } + else + { + *this = *To; + } + + } + + bool ShouldReconcile(const FMockRootMotionSyncState& AuthorityState) const + { + const float TransformErrorTolerance = 1.f; + + const bool bShouldReconcile = !Location.Equals(AuthorityState.Location, TransformErrorTolerance) || + RootMotionSourceID != AuthorityState.RootMotionSourceID || + !FMath::IsNearlyZero(PlayPosition - AuthorityState.PlayPosition) || + !FMath::IsNearlyZero(PlayRate - AuthorityState.PlayRate); + + return bShouldReconcile; + } +}; + +// The aux state should hold state that does not frequently change. It is otherwise the same as sync state. +// (note that optimizations for sparse aux storage are not complete yet) +struct FMockRootMotionAuxState +{ + TMockParameterPack<> Parameters; + + void NetSerialize(const FNetSerializeParams& P) + { + Parameters.NetSerialize(P); + } + + void ToString(FAnsiStringBuilderBase& Out) const + { + Parameters.ToString(Out); + } + + bool ShouldReconcile(const FMockRootMotionAuxState& AuthorityState) const + { + return this->Parameters != AuthorityState.Parameters; + } + + void Interpolate(const FMockRootMotionAuxState* From, const FMockRootMotionAuxState* To, float PCT) + { + this->Parameters = To->Parameters; + } +}; + +// This is the interface into "things that actually provide root motion" +class IMockRootMotionSourceMap +{ +public: + + // Advance the root motion state by the given TimeStep + virtual FTransform StepRootMotion(const FNetSimTimeStep& TimeStep, const FMockRootMotionSyncState* In, FMockRootMotionSyncState* Out, const FMockRootMotionAuxState* Aux) = 0; + + // Push the Sync state to the AnimInstance + // this is debatable - the simulation code doesn't need to call this, its really the concern of the driver (UMockRootMotionComponent) + // and not all potential root motion sources are going to want to set a pose. + virtual void FinalizePose(const FMockRootMotionSyncState* Sync, UAnimInstance* AnimInstance) = 0; +}; + +// This just defines the state types that the simulation uses +using MockRootMotionStateTypes = TNetworkPredictionStateTypes; + +// The actual NetworkPrediction simulation code that implements root motion movement +// (root motion evaluation itself is done via IMockRootMotionSourceMap but the actual 'how to move thing given a delta' is done here) +class FMockRootMotionSimulation : public FBaseMovementSimulation +{ +public: + + // The main tick function + void SimulationTick(const FNetSimTimeStep& TimeStep, const TNetSimInput& Input, const TNetSimOutput& Output); + + // Simulation's interface for mapping ID -> RootMotionSource + IMockRootMotionSourceMap* SourceMap = nullptr; + + // The component the root motion is relative to. This was found to be needed since, in our examples, we author root motion anims where Y is forward + // and we rotate the mesh components at the actor level so that X is forward. We need to know which component to rotate the root motion animation relative to. + // If we continue with this, this means all non anim based root motions should expect to follow the same convention. Need clarity here from animation team. + USceneComponent* RootMotionComponent = nullptr; +}; \ No newline at end of file diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockRootMotionSourceDataAsset.h b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockRootMotionSourceDataAsset.h new file mode 100644 index 000000000000..674a9250cb33 --- /dev/null +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/MockRootMotionSourceDataAsset.h @@ -0,0 +1,102 @@ +// Copyright Epic Games, Inc. All Rights Reserved + +#pragma once + +#include "Engine/EngineTypes.h" +#include "Engine/DataAsset.h" + +#include "MockRootMotionSimulation.h" +#include "MockRootMotionSourceDataAsset.generated.h" + +class UAnimMontage; +class UCurveVector; + +USTRUCT(meta=(ShowOnlyInnerProperties)) +struct FMockRootMotionSourceContainer_Montage +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = RootMotion) + TArray Montages; +}; + +USTRUCT(meta=(ShowOnlyInnerProperties)) +struct FMockRootMotionSourceContainer_Curves +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = RootMotion) + TArray Curves; +}; + +// DataAsset that implements IMockRootMotionSourceMap. Doing this as a Dataasset is not how we would want the final +// version to look but will serve to quickly stand things up. +// +// The advantages here are: +// -We can populate the data asset with all root motion montages at editor time. +// -Everyone (all clients, all actors) can agree on a RootMotionSourceID -> montage mapping +// -We want to avoid NetSerializing UAnimMontage* right now, because this limits bunch sharing between clients. +// -The layer of indirection gives us flexibility to map RootMotionSourceIDs to non montages like curves. +// +// The disadvanges are that its a data asset that we need to manually manage right now. It also won't scale well +// - we don't want to require a binary asset that has to track every root motion source in the game. +// +// But it will probably make sense to have some centralized thing/subsystem that can map the RootMotionSourceID +// to the actual root motion logic. Perhaps a global RootMotion mapping object that can hold global / anim graph +// independent root motion sources + a path into the actual anim instance where the anim graph itself can define +// root motion sources? +// +// To Consider: +// -How much of 'advance PlayPosition/PlayRate and do clamp/loop logic' can be shared between the various source types? +// -Consider parameterization: everything right now is very hard coded but we would like to be able to: +// -Statically parameterize: take a single Montage/Curve and have statically scaled versions of them +// -this would look like multiple entries in the data asse with the same backing asset, but with some new scaling proeprties +// -In this case, you would need some kind of tag/name to identify root motion sources (E.g, cant 'play montage by asset' but instead 'play montage by tag/name') +// -Dynamic parameterization: the code that is starting the root motion, whether its sim code or outside OOB code, may want to add some +// parameterization on top fo the static data. In a send PlayRate works like this. +// -Finding a uniform way of doing this across all source types, in a way that is effecient and not too boiler-platey would +// be very powerful! +// +// + +UCLASS() +class UMockRootMotionSourceDataAsset : public UDataAsset, public IMockRootMotionSourceMap +{ +public: + GENERATED_BODY() + + // --------------------------------------------------------------------- + // Montage based RootMotionSource + // --------------------------------------------------------------------- + + // All simple montage based RootMotion sources + UPROPERTY(EditAnywhere, Category = RootMotion, meta=(ShowOnlyInnerProperties)) + FMockRootMotionSourceContainer_Montage Montages; + + int32 FindRootMotionSourceID(UAnimMontage* Montage); + + // --------------------------------------------------------------------- + // Curves + // --------------------------------------------------------------------- + + UPROPERTY(EditAnywhere, Category = RootMotion, meta=(ShowOnlyInnerProperties)) + FMockRootMotionSourceContainer_Curves Curves; + + int32 FindRootMotionSourceID(UCurveVector* Curve); + + // --------------------------------------------------------------------- + // Utility + // --------------------------------------------------------------------- + bool IsValidSourceID(int32 RootMotionSourceID) const; + + // --------------------------------------------------------------------- + // IMockRootMotionSourceMap + // --------------------------------------------------------------------- + FTransform StepRootMotion(const FNetSimTimeStep& TimeStep, const FMockRootMotionSyncState* In, FMockRootMotionSyncState* Out, const FMockRootMotionAuxState* Aux) final override; + void FinalizePose(const FMockRootMotionSyncState* Sync, UAnimInstance* AnimInstance) final override; + +private: + + FTransform StepRootMotion_Curve(UCurveVector* Curve, const FNetSimTimeStep& TimeStep, const FMockRootMotionSyncState* In, FMockRootMotionSyncState* Out, const FMockRootMotionAuxState* Aux); + FTransform StepRootMotion_Montage(UAnimMontage* Montage, const FNetSimTimeStep& TimeStep, const FMockRootMotionSyncState* In, FMockRootMotionSyncState* Out, const FMockRootMotionAuxState* Aux); +}; \ No newline at end of file diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/ParametricMovement.cpp b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/ParametricMovement.cpp index 418b5993f43d..ade0db3bd6e6 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/ParametricMovement.cpp +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/ParametricMovement.cpp @@ -54,14 +54,6 @@ public: using Simulation = FParametricMovementSimulation; using Driver = UParametricMovementComponent; - /* - static void Interpolate(const TInterpolatorParameters& Params) - { - Params.Out.Sync.Position = Params.From.Sync.Position + ((Params.To.Sync.Position - Params.From.Sync.Position) * Params.InterpolationPCT); - Params.Out.Sync.PlayRate = Params.From.Sync.PlayRate + ((Params.To.Sync.PlayRate - Params.From.Sync.PlayRate) * Params.InterpolationPCT); - } - */ - static const TCHAR* GetName() { return TEXT("Parametric"); } static constexpr int32 GetSortPriority() { return (int32)ENetworkPredictionSortPriority::PreKinematicMovers + 5; } }; @@ -173,7 +165,7 @@ void UParametricMovementComponent::ProduceInput(const int32 DeltaTimeMS, FParame PendingPlayRate.Reset(); } -void UParametricMovementComponent::FinalizeFrame(const FParametricSyncState* SyncState, const FParametricAuxState* AuxState) +void UParametricMovementComponent::RestoreFrame(const FParametricSyncState* SyncState, const FParametricAuxState* AuxState) { FTransform NewTransform; ParametricMotion.MapTimeToTransform(SyncState->Position, NewTransform); @@ -184,6 +176,11 @@ void UParametricMovementComponent::FinalizeFrame(const FParametricSyncState* Syn UpdatedComponent->SetWorldTransform(NewTransform, false, nullptr, ETeleportType::TeleportPhysics); } +void UParametricMovementComponent::FinalizeFrame(const FParametricSyncState* SyncState, const FParametricAuxState* AuxState) +{ + RestoreFrame(SyncState, AuxState); +} + void UParametricMovementComponent::EnableInterpolationMode(bool bValue) { bEnableInterpolation = bValue; diff --git a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/ParametricMovement.h b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/ParametricMovement.h index c3efb7f2d8ca..c19b85548ab7 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/ParametricMovement.h +++ b/Engine/Plugins/Runtime/NetworkPredictionExtras/Source/NetworkPredictionExtras/Public/ParametricMovement.h @@ -57,7 +57,7 @@ struct FParametricInputCmd { if (PlayRate.IsSet()) { - Out.Appendf("PlatRate: %.2f\n"); + Out.Appendf("PlatRate: %.2f\n", PlayRate.GetValue()); } else { @@ -153,6 +153,7 @@ public: void InitializeSimulationState(FParametricSyncState* SyncState, FParametricAuxState* AuxState); void ProduceInput(const int32 SimTimeMS, FParametricInputCmd* Cmd); + void RestoreFrame(const FParametricSyncState* SyncState, const FParametricAuxState* AuxState); void FinalizeFrame(const FParametricSyncState* SyncState, const FParametricAuxState* AuxState); UFUNCTION(BlueprintCallable, Category="Networking") diff --git a/Engine/Plugins/Runtime/NetworkPredictionInsights/Source/NetworkPredictionInsights/Private/NetworkPredictionInsightsModule.cpp b/Engine/Plugins/Runtime/NetworkPredictionInsights/Source/NetworkPredictionInsights/Private/NetworkPredictionInsightsModule.cpp index 682885f9e97b..95a06138cdba 100644 --- a/Engine/Plugins/Runtime/NetworkPredictionInsights/Source/NetworkPredictionInsights/Private/NetworkPredictionInsightsModule.cpp +++ b/Engine/Plugins/Runtime/NetworkPredictionInsights/Source/NetworkPredictionInsights/Private/NetworkPredictionInsightsModule.cpp @@ -14,6 +14,7 @@ #include "Widgets/Docking/SDockTab.h" #include "Trace/StoreService.h" #include "Trace/StoreClient.h" +#include "Stats/Stats.h" #include "UI/SNPWindow.h" #include "UI/NetworkPredictionInsightsManager.h" @@ -44,6 +45,7 @@ void FNetworkPredictionInsightsModule::StartupModule() #if !(WITH_EDITOR) TickerHandle = FTicker::GetCoreTicker().AddTicker(TEXT("NetworkPredictionInsights"), 0.0f, [&UnrealInsightsModule](float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FNetworkPredictionInsightsModule_Tick); auto SessionPtr = UnrealInsightsModule.GetAnalysisSession(); if (SessionPtr.IsValid()) { diff --git a/Engine/Plugins/Runtime/OSCModulationMixing/Source/OSCModulationMixing/Private/OSCModulationMixingStatics.cpp b/Engine/Plugins/Runtime/OSCModulationMixing/Source/OSCModulationMixing/Private/OSCModulationMixingStatics.cpp index 35a9bca439cb..c029dd916a00 100644 --- a/Engine/Plugins/Runtime/OSCModulationMixing/Source/OSCModulationMixing/Private/OSCModulationMixingStatics.cpp +++ b/Engine/Plugins/Runtime/OSCModulationMixing/Source/OSCModulationMixing/Private/OSCModulationMixingStatics.cpp @@ -28,7 +28,7 @@ UOSCModulationMixingStatics::UOSCModulationMixingStatics(const FObjectInitialize { } -void UOSCModulationMixingStatics::CopyChannelsToOSCBundle(UObject* WorldContextObject, const FOSCAddress& InPathAddress, const TArray& InChannels, FOSCBundle& OutBundle) +void UOSCModulationMixingStatics::CopyStagesToOSCBundle(UObject* WorldContextObject, const FOSCAddress& InPathAddress, const TArray& InStages, FOSCBundle& OutBundle) { FOSCMessage RequestMessage; RequestMessage.SetAddress(OSCModulation::Addresses::MixLoad); @@ -38,22 +38,22 @@ void UOSCModulationMixingStatics::CopyChannelsToOSCBundle(UObject* WorldContextO Message.SetAddress(InPathAddress); UOSCManager::AddMessageToBundle(Message, OutBundle); - for (const FSoundControlBusMixChannel& Channel : InChannels) + for (const FSoundControlBusMixStage& Stage : InStages) { - if (Channel.Bus) + if (Stage.Bus) { - FOSCMessage ChannelMessage; - ChannelMessage.SetAddress(UOSCManager::OSCAddressFromObjectPath(Channel.Bus)); + FOSCMessage StageMessage; + StageMessage.SetAddress(UOSCManager::OSCAddressFromObjectPath(Stage.Bus)); - UOSCManager::AddFloat(ChannelMessage, Channel.Value.AttackTime); - UOSCManager::AddFloat(ChannelMessage, Channel.Value.ReleaseTime); - UOSCManager::AddFloat(ChannelMessage, Channel.Value.TargetValue); + UOSCManager::AddFloat(StageMessage, Stage.Value.AttackTime); + UOSCManager::AddFloat(StageMessage, Stage.Value.ReleaseTime); + UOSCManager::AddFloat(StageMessage, Stage.Value.TargetValue); - UClass* BusClass = Channel.Bus->GetClass(); + UClass* BusClass = Stage.Bus->GetClass(); const FString ClassName = BusClass ? BusClass->GetName() : FString(); - UOSCManager::AddString(ChannelMessage, ClassName); + UOSCManager::AddString(StageMessage, ClassName); - UOSCManager::AddMessageToBundle(ChannelMessage, OutBundle); + UOSCManager::AddMessageToBundle(StageMessage, OutBundle); } } } @@ -65,7 +65,7 @@ void UOSCModulationMixingStatics::CopyMixToOSCBundle(UObject* WorldContextObject return; } - CopyChannelsToOSCBundle(WorldContextObject, UOSCManager::OSCAddressFromObjectPath(InMix), InMix->Channels, OutBundle); + CopyStagesToOSCBundle(WorldContextObject, UOSCManager::OSCAddressFromObjectPath(InMix), InMix->MixStages, OutBundle); } FOSCAddress UOSCModulationMixingStatics::GetProfileLoadPath() @@ -115,9 +115,9 @@ void UOSCModulationMixingStatics::RequestMix(UObject* WorldContextObject, UOSCCl } } -TArray UOSCModulationMixingStatics::OSCBundleToChannelValues(UObject* WorldContextObject, const FOSCBundle& InBundle, FOSCAddress& OutMixPath, TArray& OutBusPaths, TArray& OutBusClassNames) +TArray UOSCModulationMixingStatics::OSCBundleToStageValues(UObject* WorldContextObject, const FOSCBundle& InBundle, FOSCAddress& OutMixPath, TArray& OutBusPaths, TArray& OutBusClassNames) { - TArray ChannelArray; + TArray StageArray; OutBusPaths.Reset(); const TArray Messages = UOSCManager::GetMessagesFromBundle(InBundle); @@ -129,7 +129,7 @@ TArray UOSCModulationMixingStatics::OSCBundleToChannelVal for (int32 i = 2; i < Messages.Num(); ++i) { - FSoundModulationValue Value; + FSoundModulationMixValue Value; UOSCManager::GetFloat(Messages[i], 0, Value.AttackTime); UOSCManager::GetFloat(Messages[i], 1, Value.ReleaseTime); UOSCManager::GetFloat(Messages[i], 2, Value.TargetValue); @@ -139,12 +139,12 @@ TArray UOSCModulationMixingStatics::OSCBundleToChannelVal OutBusClassNames.Add(BusClass); OutBusPaths.Add(UOSCManager::GetOSCMessageAddress(Messages[i])); - ChannelArray.Add(Value); + StageArray.Add(Value); } - return ChannelArray; + return StageArray; } } OutMixPath = FOSCAddress(); - return ChannelArray; + return StageArray; } diff --git a/Engine/Plugins/Runtime/OSCModulationMixing/Source/OSCModulationMixing/Public/OSCModulationMixingStatics.h b/Engine/Plugins/Runtime/OSCModulationMixing/Source/OSCModulationMixing/Public/OSCModulationMixingStatics.h index cc1447be07ca..fdbf7bc84580 100644 --- a/Engine/Plugins/Runtime/OSCModulationMixing/Source/OSCModulationMixing/Public/OSCModulationMixingStatics.h +++ b/Engine/Plugins/Runtime/OSCModulationMixing/Source/OSCModulationMixing/Public/OSCModulationMixingStatics.h @@ -49,9 +49,9 @@ class OSCMODULATIONMIXING_API UOSCModulationMixingStatics : public UBlueprintFun UFUNCTION(BlueprintCallable, Category = "Audio|OSC|Modulation", DisplayName = "Get Save Profile Path") static FOSCAddress GetProfileSavePath(); - /** Converts channel array to OSCBundle representation to send over network via OSC protocol */ - UFUNCTION(BlueprintCallable, Category = "Audio|OSC|Modulation", DisplayName = "Copy Mix Channels to OSC Bundle", meta = (WorldContext = "WorldContextObject")) - static void CopyChannelsToOSCBundle(UObject* WorldContextObject, const FOSCAddress& PathAddress, const TArray& Channels, UPARAM(ref) FOSCBundle& Bundle); + /** Converts stage array to OSCBundle representation to send over network via OSC protocol */ + UFUNCTION(BlueprintCallable, Category = "Audio|OSC|Modulation", DisplayName = "Copy Mix Stages to OSC Bundle", meta = (WorldContext = "WorldContextObject")) + static void CopyStagesToOSCBundle(UObject* WorldContextObject, const FOSCAddress& PathAddress, const TArray& Stages, UPARAM(ref) FOSCBundle& Bundle); /** Converts Control Bus Mix to OSCBundle representation to send over network via OSC protocol */ UFUNCTION(BlueprintCallable, Category = "Audio|OSC|Modulation", DisplayName = "Copy Mix State to OSC Bundle", meta = (WorldContext = "WorldContextObject")) @@ -66,6 +66,6 @@ class OSCMODULATIONMIXING_API UOSCModulationMixingStatics : public UBlueprintFun static void RequestMix(UObject* WorldContextObject, UOSCClient* Client, const FOSCAddress& MixPath); /** Converts OSCBundle to Control Bus Values & Mix Path from which it came */ - UFUNCTION(BlueprintCallable, Category = "Audio|OSC|Modulation", DisplayName = "OSCBundle To Channel Values", meta = (WorldContext = "WorldContextObject")) - static UPARAM(DisplayName = "Bus Values") TArray OSCBundleToChannelValues(UObject* WorldContextObject, const FOSCBundle& Bundle, FOSCAddress& MixPath, TArray& BusPaths, TArray& BusClassNames); + UFUNCTION(BlueprintCallable, Category = "Audio|OSC|Modulation", DisplayName = "OSCBundle To Stage Values", meta = (WorldContext = "WorldContextObject")) + static UPARAM(DisplayName = "Bus Values") TArray OSCBundleToStageValues(UObject* WorldContextObject, const FOSCBundle& Bundle, FOSCAddress& MixPath, TArray& BusPaths, TArray& BusClassNames); }; diff --git a/Engine/Plugins/Runtime/Oculus/OculusAudio/Source/OculusAudio/Private/OculusAudioDllManager.cpp b/Engine/Plugins/Runtime/Oculus/OculusAudio/Source/OculusAudio/Private/OculusAudioDllManager.cpp index a482c5123436..0abf9e1443f0 100644 --- a/Engine/Plugins/Runtime/Oculus/OculusAudio/Source/OculusAudio/Private/OculusAudioDllManager.cpp +++ b/Engine/Plugins/Runtime/Oculus/OculusAudio/Source/OculusAudio/Private/OculusAudioDllManager.cpp @@ -2,6 +2,7 @@ #include "OculusAudioDllManager.h" #include "Misc/Paths.h" #include "OculusAudioSettings.h" +#include "Stats/Stats.h" // forward decleration should match OAP_Globals @@ -178,6 +179,8 @@ void FOculusAudioLibraryManager::ReleaseDll() bool FOculusAudioLibraryManager::UpdatePluginContext(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FOculusAudioLibraryManager_UpdatePluginContext); + ovrAudioContext Context = GetPluginContext(); ovrResult Result = OVRA_CALL(ovrAudio_UpdateRoomModel)(Context, 1.0f); check(Result == ovrSuccess || Result == ovrError_AudioUninitialized); diff --git a/Engine/Plugins/Runtime/Oculus/OculusAudio/Source/OculusAudio/Private/OculusAudioMixer.cpp b/Engine/Plugins/Runtime/Oculus/OculusAudio/Source/OculusAudio/Private/OculusAudioMixer.cpp index f1dd11f0e6cb..943760f8e955 100644 --- a/Engine/Plugins/Runtime/Oculus/OculusAudio/Source/OculusAudio/Private/OculusAudioMixer.cpp +++ b/Engine/Plugins/Runtime/Oculus/OculusAudio/Source/OculusAudio/Private/OculusAudioMixer.cpp @@ -5,6 +5,7 @@ #include "OculusAudioSourceSettings.h" #include "OculusAudioContextManager.h" #include "IOculusAudioPlugin.h" +#include "Stats/Stats.h" float dbToLinear(float db) @@ -165,6 +166,7 @@ void OculusAudioSpatializationAudioMixer::ProcessAudio(const FAudioPluginSourceI bool OculusAudioSpatializationAudioMixer::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_OculusAudioSpatializationAudioMixer_Tick); if (ContextLock.TryLock()) { if (Context != nullptr) diff --git a/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/Public/AESGCMHandlerComponent.h b/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/Public/AESGCMHandlerComponent.h index d638c9599ba2..3a932e39103e 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/Public/AESGCMHandlerComponent.h +++ b/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/Public/AESGCMHandlerComponent.h @@ -46,8 +46,6 @@ public: virtual bool IsValid() const override; virtual void Incoming(FBitReader& Packet) override; virtual void Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits) override; - virtual void IncomingConnectionless(const TSharedPtr& Address, FBitReader& Packet) override {} - virtual void OutgoingConnectionless(const TSharedPtr& Address, FBitWriter& Packet, FOutPacketTraits& Traits) override {} virtual int32 GetReservedPacketBits() const override; virtual void CountBytes(FArchive& Ar) const override; diff --git a/Engine/Plugins/Runtime/PacketHandlers/AESHandlerComponent/Source/Public/AESHandlerComponent.h b/Engine/Plugins/Runtime/PacketHandlers/AESHandlerComponent/Source/Public/AESHandlerComponent.h index c68706a7bd95..5615b5b60baf 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/AESHandlerComponent/Source/Public/AESHandlerComponent.h +++ b/Engine/Plugins/Runtime/PacketHandlers/AESHandlerComponent/Source/Public/AESHandlerComponent.h @@ -43,8 +43,6 @@ public: virtual bool IsValid() const override; virtual void Incoming(FBitReader& Packet) override; virtual void Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits) override; - virtual void IncomingConnectionless(const TSharedPtr& Address, FBitReader& Packet) override {} - virtual void OutgoingConnectionless(const TSharedPtr& Address, FBitWriter& Packet, FOutPacketTraits& Traits) override {} virtual int32 GetReservedPacketBits() const override; virtual void CountBytes(FArchive& Ar) const override; diff --git a/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Private/OodleTrainerCommandlet.cpp b/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Private/OodleTrainerCommandlet.cpp index d2dc4c7b3eb5..a959e51eb553 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Private/OodleTrainerCommandlet.cpp +++ b/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Private/OodleTrainerCommandlet.cpp @@ -274,45 +274,50 @@ bool UOodleTrainerCommandlet::HandleMergePackets(FString OutputCapFile, const TA { UE_LOG(OodleHandlerComponentLog, Log, TEXT("Merging files into output file: %s"), *OutputCapFile); - FPacketCaptureArchive OutputFile(*OutArc); - bool bErrorAppending = false; - - OutputFile.SerializeCaptureHeader(); - - for (TMap::TConstIterator It(MergeMap); It; ++It) { - FArchive* CurReadArc = It.Key(); - FPacketCaptureArchive ReadFile(*CurReadArc); + FPacketCaptureArchive OutputFile(*OutArc); + bool bErrorAppending = false; - UE_LOG(OodleHandlerComponentLog, Log, TEXT(" Merging: %s"), *It.Value()); + OutputFile.SerializeCaptureHeader(); - OutputFile.AppendPacketFile(ReadFile); - - if (OutputFile.IsError()) + for (TMap::TConstIterator It(MergeMap); It; ++It) { - bErrorAppending = true; - break; + FArchive* CurReadArc = It.Key(); + + { + FPacketCaptureArchive ReadFile(*CurReadArc); + + UE_LOG(OodleHandlerComponentLog, Log, TEXT(" Merging: %s"), *It.Value()); + + OutputFile.AppendPacketFile(ReadFile); + } + + if (OutputFile.IsError()) + { + bErrorAppending = true; + break; + } + + delete CurReadArc; } - delete CurReadArc; - } - - MergeMap.Empty(); + MergeMap.Empty(); - if (!bErrorAppending) - { - bSuccess = true; - } - else - { - UE_LOG(OodleHandlerComponentLog, Error, TEXT("Error appending packet file.")); - } + if (!bErrorAppending) + { + bSuccess = true; + } + else + { + UE_LOG(OodleHandlerComponentLog, Error, TEXT("Error appending packet file.")); + } - if (bSuccess) - { - UE_LOG(OodleHandlerComponentLog, Log, TEXT("Merge packets success.")); + if (bSuccess) + { + UE_LOG(OodleHandlerComponentLog, Log, TEXT("Merge packets success.")); + } } @@ -388,54 +393,57 @@ bool UOodleTrainerCommandlet::HandleDebugDumpPackets(FString OutputDirectory, FS for (TMap::TConstIterator It(SourceMap); It; ++It) { FArchive* CurReadArc = It.Key(); - FPacketCaptureArchive ReadFile(*CurReadArc); - UE_LOG(OodleHandlerComponentLog, Log, TEXT(" Dumping: %s"), *It.Value()); - - FString PartialFileDir = FPaths::ChangeExtension(*(It.Value().Mid(SourceDirectory.Len())), TEXT(".bin")); - FString OutputFile = FPaths::Combine(*OutputDirectory, *PartialFileDir); - FArchive* OutputArc = IFileManager::Get().CreateFileWriter(*OutputFile); - - // Oodle example packet format: - // [4:ChannelNum][?:PacketData] - // PacketData: - // [4:PacketChannel][4:PacketSize][PacketSize:PacketData] - if (OutputArc != nullptr) { - uint32 DudChannelNum = 0; + FPacketCaptureArchive ReadFile(*CurReadArc); - OutputArc->ByteOrderSerialize(&DudChannelNum, sizeof(uint32)); + UE_LOG(OodleHandlerComponentLog, Log, TEXT(" Dumping: %s"), *It.Value()); - ReadFile.SerializeCaptureHeader(); + FString PartialFileDir = FPaths::ChangeExtension(*(It.Value().Mid(SourceDirectory.Len())), TEXT(".bin")); + FString OutputFile = FPaths::Combine(*OutputDirectory, *PartialFileDir); + FArchive* OutputArc = IFileManager::Get().CreateFileWriter(*OutputFile); - uint32 InPacketCount = ReadFile.GetPacketCount(); - - const uint32 BufferSize = 1048576; - uint8* ReadBuffer = new uint8[BufferSize]; - - for (int32 PacketIdx=0; PacketIdx<(int32)InPacketCount; PacketIdx++) + // Oodle example packet format: + // [4:ChannelNum][?:PacketData] + // PacketData: + // [4:PacketChannel][4:PacketSize][PacketSize:PacketData] + if (OutputArc != nullptr) { - uint32 InPacketSize = BufferSize; + uint32 DudChannelNum = 0; - ReadFile.SerializePacket(ReadBuffer, InPacketSize); + OutputArc->ByteOrderSerialize(&DudChannelNum, sizeof(uint32)); - uint32 DudPacketChannel = 0; + ReadFile.SerializeCaptureHeader(); - OutputArc->ByteOrderSerialize(&DudPacketChannel, sizeof(uint32)); - OutputArc->ByteOrderSerialize(&InPacketSize, sizeof(uint32)); - OutputArc->Serialize(ReadBuffer, InPacketSize); + uint32 InPacketCount = ReadFile.GetPacketCount(); + + const uint32 BufferSize = 1048576; + uint8* ReadBuffer = new uint8[BufferSize]; + + for (int32 PacketIdx=0; PacketIdx<(int32)InPacketCount; PacketIdx++) + { + uint32 InPacketSize = BufferSize; + + ReadFile.SerializePacket(ReadBuffer, InPacketSize); + + uint32 DudPacketChannel = 0; + + OutputArc->ByteOrderSerialize(&DudPacketChannel, sizeof(uint32)); + OutputArc->ByteOrderSerialize(&InPacketSize, sizeof(uint32)); + OutputArc->Serialize(ReadBuffer, InPacketSize); + } + + delete[] ReadBuffer; + ReadBuffer = nullptr; + + OutputArc->Close(); + + delete OutputArc; + + UE_LOG(OodleHandlerComponentLog, Log, TEXT("Successfully dumped packet data to file: %s"), *OutputFile); + + bSuccess = true; } - - delete[] ReadBuffer; - ReadBuffer = nullptr; - - OutputArc->Close(); - - delete OutputArc; - - UE_LOG(OodleHandlerComponentLog, Log, TEXT("Successfully dumped packet data to file: %s"), *OutputFile); - - bSuccess = true; } delete CurReadArc; @@ -731,7 +739,7 @@ bool FOodleDictionaryGenerator::ReadPackets(const TArray& InputCaptureF // Now begin training bool bSuccess = true; TArray UnboundArchives; - TArray BoundArchives; + TArray> BoundArchives; bSuccess = MergeMap.Num() > 0; @@ -741,7 +749,7 @@ bool FOodleDictionaryGenerator::ReadPackets(const TArray& InputCaptureF for (FArchive* CurArc : UnboundArchives) { - new(BoundArchives) FPacketCaptureArchive(*CurArc); + BoundArchives.Emplace(MakeUnique(*CurArc)); } } else @@ -755,13 +763,13 @@ bool FOodleDictionaryGenerator::ReadPackets(const TArray& InputCaptureF if (bSuccess) { - for (FPacketCaptureArchive& CurArc : BoundArchives) + for (TUniquePtr& CurArc : BoundArchives) { - CurArc.SerializeCaptureHeader(); + CurArc->SerializeCaptureHeader(); - if (!CurArc.IsError()) + if (!CurArc->IsError()) { - PacketCount += CurArc.GetPacketCount(); + PacketCount += CurArc->GetPacketCount(); } } @@ -847,15 +855,15 @@ bool FOodleDictionaryGenerator::ReadPackets(const TArray& InputCaptureF uint8* ReadBuffer = new uint8[BufferSize]; uint32 PacketIdx = 0; - for (FPacketCaptureArchive& CurArc : BoundArchives) + for (TUniquePtr& CurArc : BoundArchives) { - while (CurArc.Tell() < CurArc.TotalSize() && PacketIdx < (uint32)PacketCount) + while (CurArc->Tell() < CurArc->TotalSize() && PacketIdx < (uint32)PacketCount) { uint32 PacketSize = BufferSize; - CurArc.SerializePacket(ReadBuffer, PacketSize); + CurArc->SerializePacket(ReadBuffer, PacketSize); - if (CurArc.IsError()) + if (CurArc->IsError()) { UE_LOG(OodleHandlerComponentLog, Warning, TEXT("Error serializing packet from packet capture archive. Skipping.")); @@ -931,6 +939,8 @@ bool FOodleDictionaryGenerator::ReadPackets(const TArray& InputCaptureF } } + BoundArchives.Empty(); + delete[] ReadBuffer; ReadBuffer = nullptr; @@ -1000,41 +1010,43 @@ bool FOodleDictionaryGenerator::GenerateAndWriteDictionary() if (OutArc != nullptr) { - FOodleDictionaryArchive OutputFile(*OutArc); - - OutputFile.SetDictionaryHeaderValues(HashTableSize); - OutputFile.SerializeHeader(); - - bSuccess = !OutputFile.IsError(); - - bSuccess = bSuccess && OutputFile.SerializeOodleCompressData(OutputFile.Header.DictionaryData, NewDictionaryData, DictionarySize); - - bSuccess = bSuccess && OutputFile.SerializeOodleCompressData(OutputFile.Header.CompressorData, (uint8*)CompactCompressorState, - CompactCompressorStateBytes); - - - // Important warning for unusually small dictionary files - if they compress down this much, something is usually wrong. bool bDeleteFile = false; - if (bSuccess && OutputFile.TotalSize() < 65536) { - FText Msg = LOCTEXT("BadOodleDictionaryGen", - "The generated dictionary is less than 64KB. This is unusually small, indicating a problem - do NOT use for production! Delete the file?"); + FOodleDictionaryArchive OutputFile(*OutArc); - EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, Msg); + OutputFile.SetDictionaryHeaderValues(HashTableSize); + OutputFile.SerializeHeader(); - bDeleteFile = (Result == EAppReturnType::Yes); - bSuccess = false; - } + bSuccess = !OutputFile.IsError(); + + bSuccess = bSuccess && OutputFile.SerializeOodleCompressData(OutputFile.Header.DictionaryData, NewDictionaryData, DictionarySize); + + bSuccess = bSuccess && OutputFile.SerializeOodleCompressData(OutputFile.Header.CompressorData, (uint8*)CompactCompressorState, + CompactCompressorStateBytes); - if (bSuccess) - { - UE_LOG(OodleHandlerComponentLog, Log, TEXT("Successfully processed packet captures, and wrote dictionary file.")); - } - else - { - UE_LOG(OodleHandlerComponentLog, Error, TEXT("Error writing dictionary file.")); + // Important warning for unusually small dictionary files - if they compress down this much, something is usually wrong. + if (bSuccess && OutputFile.TotalSize() < 65536) + { + FText Msg = LOCTEXT("BadOodleDictionaryGen", + "The generated dictionary is less than 64KB. This is unusually small, indicating a problem - do NOT use for production! Delete the file?"); + + EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, Msg); + + bDeleteFile = (Result == EAppReturnType::Yes); + bSuccess = false; + } + + + if (bSuccess) + { + UE_LOG(OodleHandlerComponentLog, Log, TEXT("Successfully processed packet captures, and wrote dictionary file.")); + } + else + { + UE_LOG(OodleHandlerComponentLog, Error, TEXT("Error writing dictionary file.")); + } } diff --git a/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Public/OodleHandlerComponent.h b/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Public/OodleHandlerComponent.h index bdc8a9e8fdf1..f394f573faae 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Public/OodleHandlerComponent.h +++ b/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Public/OodleHandlerComponent.h @@ -373,14 +373,6 @@ public: virtual void Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits) override; - virtual void IncomingConnectionless(const TSharedPtr& Address, FBitReader& Packet) override - { - } - - virtual void OutgoingConnectionless(const TSharedPtr& Address, FBitWriter& Packet, FOutPacketTraits& Traits) override - { - } - virtual int32 GetReservedPacketBits() const override; virtual void NotifyAnalyticsProvider() override; diff --git a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/DTLSHandlerComponent.Build.cs b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/DTLSHandlerComponent.Build.cs index 0e9259eb1e65..f67b00dad834 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/DTLSHandlerComponent.Build.cs +++ b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/DTLSHandlerComponent.Build.cs @@ -12,7 +12,7 @@ public class DTLSHandlerComponent : ModuleRules { "Core", "CoreUObject", - "NetCore", + "NetCore", "PacketHandler", "Engine", "SSL", diff --git a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSCertStore.cpp b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSCertStore.cpp index a7a07d9df870..043bb24f1e11 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSCertStore.cpp +++ b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSCertStore.cpp @@ -21,10 +21,10 @@ FDTLSCertStore& FDTLSCertStore::Get() return *Instance.Get(); } -TSharedPtr FDTLSCertStore::CreateCert() +TSharedPtr FDTLSCertStore::CreateCert(const FTimespan& Lifetime) { TSharedRef Cert = MakeShared(); - if (Cert->GenerateCertificate()) + if (Cert->GenerateCertificate(Lifetime)) { return Cert; } @@ -33,9 +33,9 @@ TSharedPtr FDTLSCertStore::CreateCert() return nullptr; } -TSharedPtr FDTLSCertStore::CreateCert(const FString& Identifier) +TSharedPtr FDTLSCertStore::CreateCert(const FTimespan& Lifetime, const FString& Identifier) { - TSharedPtr Cert = CreateCert(); + TSharedPtr Cert = CreateCert(Lifetime); if (Cert.IsValid() && !Identifier.IsEmpty()) { CertMap.Emplace(Identifier, Cert); diff --git a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSCertificate.cpp b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSCertificate.cpp index 8ca939698268..181876c935f4 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSCertificate.cpp +++ b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSCertificate.cpp @@ -41,7 +41,7 @@ void FDTLSCertificate::FreeCertificate() Fingerprint.Reset(); } -bool FDTLSCertificate::GenerateCertificate() +bool FDTLSCertificate::GenerateCertificate(const FTimespan& Lifetime) { bool bSuccess = false; @@ -91,7 +91,7 @@ bool FDTLSCertificate::GenerateCertificate() ASN1_INTEGER_free(SerialNumber); X509_gmtime_adj(X509_get_notBefore(Certificate), 0); - X509_gmtime_adj(X509_get_notAfter(Certificate), 31536000L); + X509_gmtime_adj(X509_get_notAfter(Certificate), (long)Lifetime.GetTotalSeconds()); X509_set_pubkey(Certificate, PKey); diff --git a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSContext.cpp b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSContext.cpp index 7e11d4fddd71..d6bd156a4db5 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSContext.cpp +++ b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSContext.cpp @@ -15,6 +15,8 @@ namespace DTLSContext { static const char* CipherListPSK = "PSK-AES256-GCM-SHA384"; static const char* CipherListCert = "HIGH"; + + TAutoConsoleVariable CVarCertLifetime(TEXT("DTLS.CertLifetime"), 4 * 60 * 60, TEXT("Lifetime to set on generated certificates, in seconds.")); } const TCHAR* LexToString(EDTLSContextType ContextType) @@ -350,7 +352,11 @@ bool FDTLSContext::Initialize(const int32 MaxPacketSize, const FString& CertId, else { UE_LOG(LogDTLSHandler, Warning, TEXT("Empty certificate identifier")); - Cert = FDTLSCertStore::Get().CreateCert(); + + FTimespan Lifetime; + Lifetime.FromSeconds(DTLSContext::CVarCertLifetime.GetValueOnAnyThread()); + + Cert = FDTLSCertStore::Get().CreateCert(Lifetime); } if (Cert.IsValid()) diff --git a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSHandlerComponent.cpp b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSHandlerComponent.cpp index 2a22b9ab1a75..018e572cd3c2 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSHandlerComponent.cpp +++ b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Private/DTLSHandlerComponent.cpp @@ -147,10 +147,17 @@ void FDTLSHandlerComponent::Incoming(FBitReader& Packet) const int32 HandshakeBit = Packet.ReadBit(); const int32 PayloadBytes = Packet.GetBytesLeft(); - check(PayloadBytes > 0); - check(PayloadBytes <= sizeof(TempBuffer)); + if (PayloadBytes > 0 && PayloadBytes <= sizeof(TempBuffer)) + { + TempBuffer[PayloadBytes - 1] = 0; + } + else + { + UE_LOG(LogDTLSHandler, Log, TEXT("FAESHandlerComponent::Incoming: invalid payload size")); + Packet.SetError(); + return; + } - TempBuffer[PayloadBytes - 1] = 0; Packet.SerializeBits(TempBuffer, Packet.GetBitsLeft()); if (InternalState == EDTLSHandlerState::Handshaking) diff --git a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Public/DTLSCertStore.h b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Public/DTLSCertStore.h index 9e8b5b915ceb..8b5c61e398f7 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Public/DTLSCertStore.h +++ b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Public/DTLSCertStore.h @@ -21,18 +21,21 @@ public: /** * Create a new certificate * + * @Param Lifetime time in seconds until expiration of certificate + * * @return shared pointer to certificate, valid if creation succeeded */ - TSharedPtr CreateCert(); + TSharedPtr CreateCert(const FTimespan& Lifetime); /** * Create a new certificate and store internally * + * @Param Lifetime time in seconds until expiration of certificate * @Param Identifier name to use when storing certificate for later use * * @return shared pointer to certificate, valid if creation succeeded */ - TSharedPtr CreateCert(const FString& Identifier); + TSharedPtr CreateCert(const FTimespan& Lifetime, const FString& Identifier); /** * Retrieve a certificate using unique identifier diff --git a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Public/DTLSCertificate.h b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Public/DTLSCertificate.h index 08d48c627f37..b1e7e8f8f2fa 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Public/DTLSCertificate.h +++ b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Public/DTLSCertificate.h @@ -3,6 +3,7 @@ #pragma once #include "DTLSHandlerTypes.h" +#include "Misc/Timespan.h" /* * Wrapper for a fingerprint (SHA256 hash) of an X509 certificate @@ -57,9 +58,10 @@ public: /** * Generate a self-signed certificate * + * @param Lifetime number of seconds until the certificate should expire * @return true if creation succeeded */ - bool GenerateCertificate(); + bool GenerateCertificate(const FTimespan& Lifetime); private: void FreeCertificate(); diff --git a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Public/DTLSHandlerComponent.h b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Public/DTLSHandlerComponent.h index b9a74da7863f..4a7f6ebaff31 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Public/DTLSHandlerComponent.h +++ b/Engine/Plugins/Runtime/PacketHandlers/DTLSHandlerComponent/Source/Public/DTLSHandlerComponent.h @@ -34,8 +34,6 @@ public: virtual bool IsValid() const override; virtual void Incoming(FBitReader& Packet) override; virtual void Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits) override; - virtual void IncomingConnectionless(const TSharedPtr& Address, FBitReader& Packet) override {} - virtual void OutgoingConnectionless(const TSharedPtr& Address, FBitWriter& Packet, FOutPacketTraits& Traits) override {} virtual int32 GetReservedPacketBits() const override; virtual void CountBytes(FArchive& Ar) const override; diff --git a/Engine/Plugins/Runtime/PropertyAccess/PropertyAccess.uplugin b/Engine/Plugins/Runtime/PropertyAccess/PropertyAccess.uplugin new file mode 100644 index 000000000000..5b35af35ed74 --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/PropertyAccess.uplugin @@ -0,0 +1,31 @@ +{ + "FileVersion" : 3, + "Version" : 1, + "VersionName" : "1.0", + "FriendlyName" : "Property Access", + "Description" : "Support for copying properties from one object to another. Required for Animation and UMG systems to function correctly", + "Category" : "Runtime", + "CreatedBy" : "Epic Games, Inc.", + "CreatedByURL" : "http://epicgames.com", + "DocsURL" : "", + "MarketplaceURL" : "", + "SupportURL" : "", + "EnabledByDefault" : true, + "CanContainContent" : false, + "IsBetaVersion" : false, + "Installed" : false, + + "Modules" : + [ + { + "Name" : "PropertyAccess", + "Type": "Runtime" , + "LoadingPhase" : "PreDefault" + }, + { + "Name" : "PropertyAccessEditor", + "Type": "UncookedOnly" , + "LoadingPhase" : "PreDefault" + } + ] +} \ No newline at end of file diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Private/AnimBlueprintClassSubsystem_PropertyAccess.cpp b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Private/AnimBlueprintClassSubsystem_PropertyAccess.cpp new file mode 100644 index 000000000000..f1bbd2f0fa2c --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Private/AnimBlueprintClassSubsystem_PropertyAccess.cpp @@ -0,0 +1,42 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "AnimBlueprintClassSubsystem_PropertyAccess.h" +#include "PropertyAccess.h" +#include "Animation/AnimInstanceProxy.h" + +void UAnimBlueprintClassSubsystem_PropertyAccess::OnUpdateAnimation(UAnimInstance* InAnimInstance, FAnimInstanceSubsystemData& InSubsystemData, float InDeltaTime) +{ + // Process internal batched property copies + PropertyAccess::ProcessCopies(InAnimInstance, PropertyAccessLibrary, EPropertyAccessCopyBatch::ExternalBatched); +} + +void UAnimBlueprintClassSubsystem_PropertyAccess::OnParallelUpdateAnimation(FAnimInstanceProxy& InProxy, FAnimInstanceSubsystemData& InSubsystemData, float InDeltaTime) +{ + // Process internal batched property copies + PropertyAccess::ProcessCopies(InProxy.GetAnimInstanceObject(), PropertyAccessLibrary, EPropertyAccessCopyBatch::InternalBatched); +} + +void UAnimBlueprintClassSubsystem_PropertyAccess::PostLoadSubsystem() +{ + PropertyAccess::PostLoadLibrary(PropertyAccessLibrary); +} + +void UAnimBlueprintClassSubsystem_PropertyAccess::ProcessCopies(UObject* InObject, EPropertyAccessCopyBatch InBatchType) const +{ + PropertyAccess::ProcessCopies(InObject, PropertyAccessLibrary, InBatchType); +} + +void UAnimBlueprintClassSubsystem_PropertyAccess::ProcessCopy(UObject* InObject, EPropertyAccessCopyBatch InBatchType, int32 InCopyIndex, TFunctionRef InPostCopyOperation) const +{ + PropertyAccess::ProcessCopy(InObject, PropertyAccessLibrary, InBatchType, InCopyIndex, InPostCopyOperation); +} + +void UAnimBlueprintClassSubsystem_PropertyAccess::BindEvents(UObject* InObject) const +{ + PropertyAccess::BindEvents(InObject, PropertyAccessLibrary); +} + +int32 UAnimBlueprintClassSubsystem_PropertyAccess::GetEventId(const UClass* InClass, TArrayView InPath) const +{ + return PropertyAccess::GetEventId(InClass, InPath); +} diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Private/PropertyAccess.cpp b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Private/PropertyAccess.cpp new file mode 100644 index 000000000000..bfad867e088a --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Private/PropertyAccess.cpp @@ -0,0 +1,856 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "PropertyAccess.h" +#include "Misc/MemStack.h" + +#define LOCTEXT_NAMESPACE "PropertyAccess" + +struct FPropertyAccessSystem +{ + struct FResolveIndirectionsOnLoadContext + { + FResolveIndirectionsOnLoadContext(TArrayView InSegments, FPropertyAccessIndirectionChain& InAccess, FPropertyAccessLibrary& InLibrary) + : Segments(InSegments) + , Access(InAccess) + , Library(InLibrary) + , AccumulatedOffset(0) + {} + + TArrayView Segments; + TArray Indirections; + FPropertyAccessIndirectionChain& Access; + FPropertyAccessLibrary& Library; + uint32 AccumulatedOffset; + FText ErrorMessage; + }; + + static uint32 GetPropertyOffset(const FProperty* InProperty, int32 InArrayIndex = 0) + { + return (uint32)(InProperty->GetOffset_ForInternal() + InProperty->ElementSize * InArrayIndex); + } + + // Called on load to resolve all path segments to indirections + static bool ResolveIndirectionsOnLoad(FResolveIndirectionsOnLoadContext& InContext) + { + for(int32 SegmentIndex = 0; SegmentIndex < InContext.Segments.Num(); ++SegmentIndex) + { + const FPropertyAccessSegment& Segment = InContext.Segments[SegmentIndex]; + const bool bLastSegment = SegmentIndex == InContext.Segments.Num() - 1; + + if(Segment.Property.Get() == nullptr) + { + return false; + } + + if(EnumHasAllFlags((EPropertyAccessSegmentFlags)Segment.Flags, EPropertyAccessSegmentFlags::Function)) + { + if(Segment.Function == nullptr) + { + return false; + } + + FPropertyAccessIndirection& Indirection = InContext.Indirections.AddDefaulted_GetRef(); + + Indirection.Type = Segment.Function->HasAnyFunctionFlags(FUNC_Native) ? EPropertyAccessIndirectionType::NativeFunction : EPropertyAccessIndirectionType::ScriptFunction; + + switch((EPropertyAccessSegmentFlags)Segment.Flags & ~EPropertyAccessSegmentFlags::ModifierFlags) + { + case EPropertyAccessSegmentFlags::Struct: + case EPropertyAccessSegmentFlags::Leaf: + Indirection.ObjectType = EPropertyAccessObjectType::None; + break; + case EPropertyAccessSegmentFlags::Object: + Indirection.ObjectType = EPropertyAccessObjectType::Object; + break; + case EPropertyAccessSegmentFlags::WeakObject: + Indirection.ObjectType = EPropertyAccessObjectType::WeakObject; + break; + case EPropertyAccessSegmentFlags::SoftObject: + Indirection.ObjectType = EPropertyAccessObjectType::SoftObject; + break; + default: + check(false); + break; + } + + Indirection.Function = Segment.Function; + Indirection.ReturnBufferSize = Segment.Property.Get()->GetSize(); + Indirection.ReturnBufferAlignment = Segment.Property.Get()->GetMinAlignment(); + } + else + { + const int32 ArrayIndex = Segment.ArrayIndex == INDEX_NONE ? 0 : Segment.ArrayIndex; + const EPropertyAccessSegmentFlags UnmodifiedFlags = (EPropertyAccessSegmentFlags)Segment.Flags & ~EPropertyAccessSegmentFlags::ModifierFlags; + switch(UnmodifiedFlags) + { + case EPropertyAccessSegmentFlags::Struct: + case EPropertyAccessSegmentFlags::Leaf: + { + FPropertyAccessIndirection& Indirection = InContext.Indirections.AddDefaulted_GetRef(); + + Indirection.Offset = GetPropertyOffset(Segment.Property.Get(), ArrayIndex); + Indirection.Type = EPropertyAccessIndirectionType::Offset; + break; + } + case EPropertyAccessSegmentFlags::Object: + { + FPropertyAccessIndirection& Indirection = InContext.Indirections.AddDefaulted_GetRef(); + + Indirection.Offset = GetPropertyOffset(Segment.Property.Get(), ArrayIndex); + Indirection.Type = EPropertyAccessIndirectionType::Object; + Indirection.ObjectType = EPropertyAccessObjectType::Object; + break; + } + case EPropertyAccessSegmentFlags::WeakObject: + { + FPropertyAccessIndirection& Indirection = InContext.Indirections.AddDefaulted_GetRef(); + + Indirection.Offset = GetPropertyOffset(Segment.Property.Get(), ArrayIndex); + Indirection.Type = EPropertyAccessIndirectionType::Object; + Indirection.ObjectType = EPropertyAccessObjectType::WeakObject; + break; + } + case EPropertyAccessSegmentFlags::SoftObject: + { + FPropertyAccessIndirection& Indirection = InContext.Indirections.AddDefaulted_GetRef(); + + Indirection.Offset = GetPropertyOffset(Segment.Property.Get(), ArrayIndex); + Indirection.Type = EPropertyAccessIndirectionType::Object; + Indirection.ObjectType = EPropertyAccessObjectType::SoftObject; + break; + } + case EPropertyAccessSegmentFlags::Array: + case EPropertyAccessSegmentFlags::ArrayOfStructs: + case EPropertyAccessSegmentFlags::ArrayOfObjects: + { + FPropertyAccessIndirection& Indirection = InContext.Indirections.AddDefaulted_GetRef(); + + Indirection.Offset = GetPropertyOffset(Segment.Property.Get()); + Indirection.Type = EPropertyAccessIndirectionType::Array; + Indirection.ArrayProperty = CastFieldChecked(Segment.Property.Get()); + Indirection.ArrayIndex = ArrayIndex; + if(UnmodifiedFlags == EPropertyAccessSegmentFlags::ArrayOfObjects) + { + if(!bLastSegment) + { + // Object arrays need an object dereference adding if non-leaf + FPropertyAccessIndirection& ExtraIndirection = InContext.Indirections.AddDefaulted_GetRef(); + + ExtraIndirection.Offset = 0; + ExtraIndirection.Type = EPropertyAccessIndirectionType::Object; + + FProperty* InnerProperty = Indirection.ArrayProperty.Get()->Inner; + if(FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + ExtraIndirection.ObjectType = EPropertyAccessObjectType::Object; + } + else if(FWeakObjectProperty* WeakObjectProperty = CastField(InnerProperty)) + { + ExtraIndirection.ObjectType = EPropertyAccessObjectType::WeakObject; + } + else if(FSoftObjectProperty* SoftObjectProperty = CastField(InnerProperty)) + { + ExtraIndirection.ObjectType = EPropertyAccessObjectType::SoftObject; + } + } + } + break; + } + default: + check(false); + break; + } + } + } + + // Collapse adjacent offset indirections + for(int32 IndirectionIndex = 0; IndirectionIndex < InContext.Indirections.Num(); ++IndirectionIndex) + { + FPropertyAccessIndirection& StartIndirection = InContext.Indirections[IndirectionIndex]; + if(StartIndirection.Type == EPropertyAccessIndirectionType::Offset) + { + for(int32 NextIndirectionIndex = IndirectionIndex + 1; NextIndirectionIndex < InContext.Indirections.Num(); ++NextIndirectionIndex) + { + FPropertyAccessIndirection& RunIndirection = InContext.Indirections[NextIndirectionIndex]; + if(RunIndirection.Type == EPropertyAccessIndirectionType::Offset) + { + StartIndirection.Offset += RunIndirection.Offset; + InContext.Indirections.RemoveAt(NextIndirectionIndex); + } + else + { + // No run, exit + break; + } + } + } + } + + // Concatenate indirections into the library and update the access + InContext.Access.IndirectionStartIndex = InContext.Library.Indirections.Num(); + InContext.Library.Indirections.Append(InContext.Indirections); + InContext.Access.IndirectionEndIndex = InContext.Library.Indirections.Num(); + + // Copy leaf property to access + FPropertyAccessSegment& LastSegment = InContext.Segments.Last(); + switch((EPropertyAccessSegmentFlags)LastSegment.Flags & ~EPropertyAccessSegmentFlags::ModifierFlags) + { + case EPropertyAccessSegmentFlags::Struct: + case EPropertyAccessSegmentFlags::Leaf: + case EPropertyAccessSegmentFlags::Object: + case EPropertyAccessSegmentFlags::WeakObject: + case EPropertyAccessSegmentFlags::SoftObject: + InContext.Access.Property = LastSegment.Property.Get(); + break; + case EPropertyAccessSegmentFlags::Array: + case EPropertyAccessSegmentFlags::ArrayOfStructs: + case EPropertyAccessSegmentFlags::ArrayOfObjects: + InContext.Access.Property = CastFieldChecked(LastSegment.Property.Get())->Inner; + break; + } + + return true; + } + + static void PostLoadLibrary(FPropertyAccessLibrary& InLibrary) + { + InLibrary.Indirections.Reset(); + + const int32 SrcCount = InLibrary.SrcPaths.Num(); + InLibrary.SrcAccesses.Reset(); + InLibrary.SrcAccesses.SetNum(SrcCount); + const int32 DestCount = InLibrary.DestPaths.Num(); + InLibrary.DestAccesses.Reset(); + InLibrary.DestAccesses.SetNum(DestCount); + + // @TODO: ParallelFor this if required + + for(int32 SrcIndex = 0; SrcIndex < SrcCount; ++SrcIndex) + { + TArrayView Segments(&InLibrary.PathSegments[InLibrary.SrcPaths[SrcIndex].PathSegmentStartIndex], InLibrary.SrcPaths[SrcIndex].PathSegmentCount); + FResolveIndirectionsOnLoadContext Context(Segments, InLibrary.SrcAccesses[SrcIndex], InLibrary); + if(!ResolveIndirectionsOnLoad(Context)) + { + Context.Indirections.Empty(); + Context.Access.IndirectionStartIndex = Context.Access.IndirectionEndIndex = INDEX_NONE; + } + } + + for(int32 DestIndex = 0; DestIndex < DestCount; ++DestIndex) + { + TArrayView Segments(&InLibrary.PathSegments[InLibrary.DestPaths[DestIndex].PathSegmentStartIndex], InLibrary.DestPaths[DestIndex].PathSegmentCount); + FResolveIndirectionsOnLoadContext Context(Segments, InLibrary.DestAccesses[DestIndex], InLibrary); + if(!ResolveIndirectionsOnLoad(Context)) + { + Context.Indirections.Empty(); + Context.Access.IndirectionStartIndex = Context.Access.IndirectionEndIndex = INDEX_NONE; + } + } + + InLibrary.bHasBeenPostLoaded = true; + } + + static void PerformCopy(const FPropertyAccessCopy& Copy, const FProperty* InSrcProperty, const void* InSrcAddr, const FProperty* InDestProperty, void* InDestAddr, TFunctionRef InPostCopyOperation) + { + switch(Copy.Type) + { + case EPropertyAccessCopyType::Plain: + checkSlow(InSrcProperty->PropertyFlags & CPF_IsPlainOldData); + checkSlow(InDestProperty->PropertyFlags & CPF_IsPlainOldData); + FMemory::Memcpy(InDestAddr, InSrcAddr, InSrcProperty->ElementSize); + break; + case EPropertyAccessCopyType::Complex: + InSrcProperty->CopyCompleteValue(InDestAddr, InSrcAddr); + break; + case EPropertyAccessCopyType::Bool: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->SetPropertyValue(InDestAddr, static_cast(InSrcProperty)->GetPropertyValue(InSrcAddr)); + break; + case EPropertyAccessCopyType::Struct: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->Struct->CopyScriptStruct(InDestAddr, InSrcAddr); + break; + case EPropertyAccessCopyType::Object: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->SetObjectPropertyValue(InDestAddr, static_cast(InSrcProperty)->GetObjectPropertyValue(InSrcAddr)); + break; + case EPropertyAccessCopyType::Name: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->SetPropertyValue(InDestAddr, static_cast(InSrcProperty)->GetPropertyValue(InSrcAddr)); + break; + case EPropertyAccessCopyType::Array: + { + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + const FArrayProperty* SrcArrayProperty = static_cast(InSrcProperty); + const FArrayProperty* DestArrayProperty = static_cast(InDestProperty); + FScriptArrayHelper SourceArrayHelper(SrcArrayProperty, InSrcAddr); + FScriptArrayHelper DestArrayHelper(DestArrayProperty, InDestAddr); + + // Copy the minimum number of elements to the destination array without resizing + const int32 MinSize = FMath::Min(SourceArrayHelper.Num(), DestArrayHelper.Num()); + for(int32 ElementIndex = 0; ElementIndex < MinSize; ++ElementIndex) + { + SrcArrayProperty->Inner->CopySingleValue(DestArrayHelper.GetRawPtr(ElementIndex), SourceArrayHelper.GetRawPtr(ElementIndex)); + } + break; + } + case EPropertyAccessCopyType::PromoteBoolToByte: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->SetPropertyValue(InDestAddr, (uint8)static_cast(InSrcProperty)->GetPropertyValue(InSrcAddr)); + break; + case EPropertyAccessCopyType::PromoteBoolToInt32: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->SetPropertyValue(InDestAddr, (int32)static_cast(InSrcProperty)->GetPropertyValue(InSrcAddr)); + break; + case EPropertyAccessCopyType::PromoteBoolToInt64: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->SetPropertyValue(InDestAddr, (int64)static_cast(InSrcProperty)->GetPropertyValue(InSrcAddr)); + break; + case EPropertyAccessCopyType::PromoteBoolToFloat: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->SetPropertyValue(InDestAddr, (float)static_cast(InSrcProperty)->GetPropertyValue(InSrcAddr)); + break; + case EPropertyAccessCopyType::PromoteByteToInt32: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->SetPropertyValue(InDestAddr, (int32)static_cast(InSrcProperty)->GetPropertyValue(InSrcAddr)); + break; + case EPropertyAccessCopyType::PromoteByteToInt64: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->SetPropertyValue(InDestAddr, (int64)static_cast(InSrcProperty)->GetPropertyValue(InSrcAddr)); + break; + case EPropertyAccessCopyType::PromoteByteToFloat: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->SetPropertyValue(InDestAddr, (float)static_cast(InSrcProperty)->GetPropertyValue(InSrcAddr)); + break; + case EPropertyAccessCopyType::PromoteInt32ToInt64: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->SetPropertyValue(InDestAddr, (int64)static_cast(InSrcProperty)->GetPropertyValue(InSrcAddr)); + break; + case EPropertyAccessCopyType::PromoteInt32ToFloat: + checkSlow(InSrcProperty->IsA()); + checkSlow(InDestProperty->IsA()); + static_cast(InDestProperty)->SetPropertyValue(InDestAddr, (float)static_cast(InSrcProperty)->GetPropertyValue(InSrcAddr)); + break; + default: + check(false); + break; + } + + InPostCopyOperation(InDestProperty, InDestAddr); + } + + static void CallNativeAccessor(UObject* InObject, UFunction* InFunction, void* OutRetValue) + { + // Func must be local + check((InObject->GetFunctionCallspace(InFunction, nullptr) & FunctionCallspace::Local) != 0); + + // Function must be native + check(InFunction->HasAnyFunctionFlags(FUNC_Native)); + + // Function must have a return property + check(InFunction->GetReturnProperty() != nullptr); + + // Function must only have one param - its return value + check(InFunction->NumParms == 1); + + FFrame Stack(InObject, InFunction, nullptr, nullptr, InFunction->ChildProperties); + InFunction->Invoke(InObject, Stack, OutRetValue); + } + + static void IterateAccess(void* InContainer, const FPropertyAccessLibrary& InLibrary, const FPropertyAccessIndirectionChain& InAccess, TFunctionRef InAddressFunction, TFunctionRef InPerObjectFunction) + { + void* Address = InContainer; + + for(int32 IndirectionIndex = InAccess.IndirectionStartIndex; Address != nullptr && IndirectionIndex < InAccess.IndirectionEndIndex; ++IndirectionIndex) + { + const FPropertyAccessIndirection& Indirection = InLibrary.Indirections[IndirectionIndex]; + + switch(Indirection.Type) + { + case EPropertyAccessIndirectionType::Offset: + Address = static_cast(static_cast(Address) + Indirection.Offset); + break; + case EPropertyAccessIndirectionType::Object: + { + switch(Indirection.ObjectType) + { + case EPropertyAccessObjectType::Object: + { + UObject* Object = *reinterpret_cast(static_cast(Address) + Indirection.Offset); + Address = static_cast(Object); + if(Object != nullptr) + { + InPerObjectFunction(Object); + } + break; + } + case EPropertyAccessObjectType::WeakObject: + { + TWeakObjectPtr& WeakObjectPtr = *reinterpret_cast*>(static_cast(Address) + Indirection.Offset); + UObject* Object = WeakObjectPtr.Get(); + Address = static_cast(Object); + if(Object != nullptr) + { + InPerObjectFunction(Object); + } + break; + } + case EPropertyAccessObjectType::SoftObject: + { + FSoftObjectPtr& SoftObjectPtr = *reinterpret_cast(static_cast(Address) + Indirection.Offset); + UObject* Object = SoftObjectPtr.Get(); + Address = static_cast(Object); + if(Object != nullptr) + { + InPerObjectFunction(Object); + } + break; + } + default: + check(false); + } + break; + } + case EPropertyAccessIndirectionType::Array: + { + if(FArrayProperty* ArrayProperty = Indirection.ArrayProperty.Get()) + { + FScriptArrayHelper Helper(ArrayProperty, static_cast(Address) + Indirection.Offset); + if(Helper.IsValidIndex(Indirection.ArrayIndex)) + { + Address = static_cast(Helper.GetRawPtr(Indirection.ArrayIndex)); + } + else + { + Address = nullptr; + } + } + else + { + Address = nullptr; + } + break; + } + case EPropertyAccessIndirectionType::ScriptFunction: + case EPropertyAccessIndirectionType::NativeFunction: + { + if(Indirection.Function != nullptr) + { + UObject* CalleeObject = static_cast(Address); + + // Allocate an aligned buffer for the return value + Address = (uint8*)FMemStack::Get().Alloc(Indirection.ReturnBufferSize, Indirection.ReturnBufferAlignment); + + // Init value + check(Indirection.Function->GetReturnProperty()); + Indirection.Function->GetReturnProperty()->InitializeValue(Address); + + if(Indirection.Type == EPropertyAccessIndirectionType::NativeFunction) + { + CallNativeAccessor(CalleeObject, Indirection.Function, Address); + } + else + { + CalleeObject->ProcessEvent(Indirection.Function, Address); + } + + // Function access may return an object, so we need to follow that ptr + switch(Indirection.ObjectType) + { + case EPropertyAccessObjectType::Object: + { + UObject* Object = *static_cast(Address); + Address = static_cast(Object); + if(Object != nullptr) + { + InPerObjectFunction(Object); + } + break; + } + case EPropertyAccessObjectType::WeakObject: + { + TWeakObjectPtr& WeakObjectPtr = *static_cast*>(Address); + UObject* Object = WeakObjectPtr.Get(); + Address = static_cast(Object); + if(Object != nullptr) + { + InPerObjectFunction(Object); + } + break; + } + case EPropertyAccessObjectType::SoftObject: + { + FSoftObjectPtr& SoftObjectPtr = *reinterpret_cast(Address); + UObject* Object = SoftObjectPtr.Get(); + Address = static_cast(Object); + if(Object != nullptr) + { + InPerObjectFunction(Object); + } + break; + } + default: + break; + } + break; + } + else + { + Address = nullptr; + } + } + default: + check(false); + } + } + + if(Address != nullptr) + { + InAddressFunction(Address); + } + } + + // Iterates all the objects in an access path/indirection graph. + static void ForEachObjectInAccess(void* InContainer, const FPropertyAccessLibrary& InLibrary, const FPropertyAccessIndirectionChain& InAccess, TFunctionRef InPerObjectFunction) + { + IterateAccess(InContainer, InLibrary, InAccess, [](void*){}, InPerObjectFunction); + } + + // Gets the address that corresponds to a property access. + // Forwards the address onto the passed-in function. This callback-style approach is used because in some cases + // (e.g. functions), the address may be memory allocated on the stack. + static void GetAccessAddress(void* InContainer, const FPropertyAccessLibrary& InLibrary, const FPropertyAccessIndirectionChain& InAccess, TFunctionRef InAddressFunction) + { + IterateAccess(InContainer, InLibrary, InAccess, InAddressFunction, [](UObject*){}); + } + + // Process a single copy + static void ProcessCopy(UStruct* InStruct, void* InContainer, const FPropertyAccessLibrary& InLibrary, int32 InCopyIndex, EPropertyAccessCopyBatch InBatchType, TFunctionRef InPostCopyOperation) + { + if(InLibrary.bHasBeenPostLoaded) + { + const FPropertyAccessCopy& Copy = InLibrary.CopyBatches[(__underlying_type(EPropertyAccessCopyBatch))InBatchType].Copies[InCopyIndex]; + const FPropertyAccessIndirectionChain& SrcAccess = InLibrary.SrcAccesses[Copy.AccessIndex]; + if(SrcAccess.Property.Get()) + { + GetAccessAddress(InContainer, InLibrary, SrcAccess, [InContainer, &InLibrary, &Copy, &SrcAccess, &InPostCopyOperation](void* InSrcAddress) + { + for(int32 DestAccessIndex = Copy.DestAccessStartIndex; DestAccessIndex < Copy.DestAccessEndIndex; ++DestAccessIndex) + { + const FPropertyAccessIndirectionChain& DestAccess = InLibrary.DestAccesses[DestAccessIndex]; + if(DestAccess.Property.Get()) + { + GetAccessAddress(InContainer, InLibrary, DestAccess, [&InSrcAddress, &Copy, &SrcAccess, &DestAccess, &InPostCopyOperation](void* InDestAddress) + { + PerformCopy(Copy, SrcAccess.Property.Get(), InSrcAddress, DestAccess.Property.Get(), InDestAddress, InPostCopyOperation); + }); + } + } + }); + } + } + } + + static void ProcessCopies(UStruct* InStruct, void* InContainer, const FPropertyAccessLibrary& InLibrary, EPropertyAccessCopyBatch InBatchType) + { + if(InLibrary.bHasBeenPostLoaded) + { + FMemMark Mark(FMemStack::Get()); + + // Copy all valid properties + // Parallelization opportunity: ParallelFor all the property copies we need to make + const int32 NumCopies = InLibrary.CopyBatches[(__underlying_type(EPropertyAccessCopyBatch))InBatchType].Copies.Num(); + for(int32 CopyIndex = 0; CopyIndex < NumCopies; ++CopyIndex) + { + ProcessCopy(InStruct, InContainer, InLibrary, CopyIndex, InBatchType, [](const FProperty*, void*){}); + } + } + } + + /** A node in a class property tree */ + struct FPropertyNode + { + FPropertyNode(TArray& InPropertyTree, const FProperty* InProperty, FName InName, int32 InId, int32 InParentId) + : Property(InProperty) + , Name(InName) + , Id(InId) + , ParentId(InParentId) + { + if(InParentId != INDEX_NONE) + { + if(InPropertyTree[InParentId].FirstChildId == INDEX_NONE) + { + InPropertyTree[InParentId].FirstChildId = InId; + InPropertyTree[InParentId].LastChildId = InId; + } + InPropertyTree[InParentId].LastChildId++; + } + } + + FPropertyNode(TArray& InPropertyTree, const FProperty* InProperty, int32 InId, int32 InParentId) + : FPropertyNode(InPropertyTree, InProperty, InProperty->GetFName(), InId, InParentId) + { + } + + FPropertyNode(TArray& InPropertyTree, int32 InId) + : FPropertyNode(InPropertyTree, nullptr, NAME_None, InId, INDEX_NONE) + { + } + + const FProperty* Property = nullptr; + + FName Name = NAME_None; + + int32 Id = INDEX_NONE; + + int32 ParentId = INDEX_NONE; + + int32 FirstChildId = INDEX_NONE; + + int32 LastChildId = INDEX_NONE; + }; + + /** Map of classes to a tree of property nodes, for faster specialized iteration */ + static TMap>> ClassPropertyTrees; + + /** Finds the event Id associated with a property node identified by InPath */ + static int32 FindEventId(const TArray& InPropertyTree, TArrayView InPath) + { + if(InPropertyTree.Num() > 0) + { + int32 NodeIndex = 0; + int32 PathIndex = 0; + + for(int32 ChildIndex = InPropertyTree[NodeIndex].FirstChildId; ChildIndex < InPropertyTree[NodeIndex].LastChildId; ++ChildIndex) + { + if(InPropertyTree[ChildIndex].Name == InPath[PathIndex]) + { + if(PathIndex == InPath.Num() - 1) + { + // Found the leaf property of the path + check(InPropertyTree[ChildIndex].Id == ChildIndex); + return ChildIndex; + } + else + { + // Recurse into the next level of the tree/path + PathIndex++; + NodeIndex = ChildIndex; + ChildIndex = InPropertyTree[ChildIndex].FirstChildId; + } + } + } + } + + return INDEX_NONE; + } + + /** Makes a property tree for the specified struct */ + static void MakeClassPropertyTree(const UStruct* InStruct, TArray& OutPropertyTree, int32 InParentId = INDEX_NONE) + { + const int32 StartIndex = OutPropertyTree.Num(); + int32 Index = OutPropertyTree.Num(); + + // Add root + if(InParentId == INDEX_NONE) + { + check(OutPropertyTree.Num() == 0); + OutPropertyTree.Emplace(OutPropertyTree, Index++); + InParentId = 0; + } + + // Make sure that all children are contiguous indices + for (TFieldIterator It(InStruct, EFieldIteratorFlags::ExcludeSuper); It; ++It) + { + const FProperty* Property = *It; + + // Skip editor-only properties, as the runtime cant respond to them and we want editor and non-editor IDs to match + if((Property->GetFlags() & CPF_EditorOnly) == 0) + { + OutPropertyTree.Emplace(OutPropertyTree, Property, Index++, InParentId); + } + } + + // Reset index to repeat iteration for recursion into sub-structs + Index = StartIndex; + for (TFieldIterator It(InStruct, EFieldIteratorFlags::ExcludeSuper); It; ++It, ++Index) + { + const FProperty* Property = *It; + + if((Property->GetFlags() & CPF_EditorOnly) == 0) + { + if(const FStructProperty* StructProperty = CastField(Property)) + { + MakeClassPropertyTree(StructProperty->Struct, OutPropertyTree, Index); + } + else if(const FArrayProperty* ArrayProperty = CastField(Property)) + { + if(const FStructProperty* InnerStructProperty = CastField(ArrayProperty->Inner)) + { + MakeClassPropertyTree(InnerStructProperty->Struct, OutPropertyTree, Index); + } + } + } + } + } + + static const TArray& GetClassPropertyTree(const UClass* InClass) + { + TArray* PropertyTree = nullptr; + + if(TUniquePtr>* ExistingPropertyTreePtr = ClassPropertyTrees.Find(InClass)) + { + PropertyTree = ExistingPropertyTreePtr->Get(); + } + else + { + PropertyTree = ClassPropertyTrees.Emplace(InClass, MakeUnique>()).Get(); + MakeClassPropertyTree(InClass, *PropertyTree); + } + + return *PropertyTree; + } + + static int32 GetEventId(const UClass* InClass, TArrayView InPath) + { + return FindEventId(GetClassPropertyTree(InClass), InPath); + } + + // Mapping from a broadcaster's IDs to subscribers IDs + struct FSubscriberMap + { + TArray Mapping; + }; + + // Mapping from a broadcaster to various subscribers, the integer indexes SubscriberMappings + struct FBroadcasterMap + { + TMap, int32> SubscriberIndices; + }; + + // Map for looking up a broadcaster/subscriber + static TMap, FBroadcasterMap> BroadcasterMaps; + + // Array for stable indices of mappings + static TArray SubscriberMappings; + + /** Creates a mapping between a broadcaster classes IDs and a subscriber classes accesses */ + static int32 CreateBroadcasterSubscriberMapping(const UClass* InBroadcasterClass, const UClass* InSubscriberClass, const FPropertyAccessLibrary& InSubscriberLibrary) + { + const TArray& BroadcasterPropertyTree = GetClassPropertyTree(InBroadcasterClass); + + int32 MappingIndex = SubscriberMappings.Num(); + FBroadcasterMap& BroadcasterMap = BroadcasterMaps.FindOrAdd(InBroadcasterClass); + BroadcasterMap.SubscriberIndices.Add(InSubscriberClass, MappingIndex); + FSubscriberMap& Mapping = SubscriberMappings.AddDefaulted_GetRef(); + + Mapping.Mapping.SetNum(BroadcasterPropertyTree.Num()); + + for(int32& Index : Mapping.Mapping) + { + Index = INDEX_NONE; + } + + for(int32 AccessIndex = 0; AccessIndex < InSubscriberLibrary.SrcAccesses.Num(); ++AccessIndex) + { + const FPropertyAccessIndirectionChain& Access = InSubscriberLibrary.SrcAccesses[AccessIndex]; + Mapping.Mapping[Access.EventId] = AccessIndex; + } + + return MappingIndex; + } + + static void BindEvents(UObject* InSubscriberObject, UClass* InSubscriberClass, const FPropertyAccessLibrary& InSubscriberLibrary) + { + IPropertyEventSubscriber* SubscriberObject = CastChecked(InSubscriberObject); + + TSet Broadcasters; + + for(int32 EventAccessIndex : InSubscriberLibrary.EventAccessIndices) + { + const FPropertyAccessIndirectionChain& Access = InSubscriberLibrary.SrcAccesses[EventAccessIndex]; + check(Access.EventId != INDEX_NONE); + + // Find all unique broadcasters + ForEachObjectInAccess(InSubscriberObject, InSubscriberLibrary, Access, [&Broadcasters](UObject* InBroadcasterObject) + { + if(IPropertyEventBroadcaster* Broadcaster = Cast(InBroadcasterObject)) + { + Broadcasters.Add(InBroadcasterObject); + } + }); + + for(UObject* BroadcasterObject : Broadcasters) + { + // lookup or create a mapping + int32 MappingIndex = INDEX_NONE; + const UClass* BroadcasterClass = BroadcasterObject->GetClass(); + + if(FBroadcasterMap* BroadcasterMapPtr = BroadcasterMaps.Find(BroadcasterClass)) + { + if(int32* SubscriberMappingIndexPtr = BroadcasterMapPtr->SubscriberIndices.Find(InSubscriberClass)) + { + MappingIndex = *SubscriberMappingIndexPtr; + } + } + + if(MappingIndex == INDEX_NONE) + { + MappingIndex = CreateBroadcasterSubscriberMapping(BroadcasterClass, InSubscriberClass, InSubscriberLibrary); + } + + IPropertyEventBroadcaster* Broadcaster = CastChecked(BroadcasterObject); + Broadcaster->RegisterSubscriber(SubscriberObject, MappingIndex); + } + } + } +}; + +TMap>> FPropertyAccessSystem::ClassPropertyTrees; +TMap, FPropertyAccessSystem::FBroadcasterMap> FPropertyAccessSystem::BroadcasterMaps; +TArray FPropertyAccessSystem::SubscriberMappings; + +namespace PropertyAccess +{ + void PostLoadLibrary(FPropertyAccessLibrary& InLibrary) + { + ::FPropertyAccessSystem::PostLoadLibrary(InLibrary); + } + + void ProcessCopies(UObject* InObject, const FPropertyAccessLibrary& InLibrary, EPropertyAccessCopyBatch InBatchType) + { + QUICK_SCOPE_CYCLE_COUNTER(PropertyAccess_ProcessCopies); + + ::FPropertyAccessSystem::ProcessCopies(InObject->GetClass(), InObject, InLibrary, InBatchType); + } + + void ProcessCopy(UObject* InObject, const FPropertyAccessLibrary& InLibrary, EPropertyAccessCopyBatch InBatchType, int32 InCopyIndex, TFunctionRef InPostCopyOperation) + { + ::FPropertyAccessSystem::ProcessCopy(InObject->GetClass(), InObject, InLibrary, InCopyIndex, InBatchType, InPostCopyOperation); + } + + void BindEvents(UObject* InObject, const FPropertyAccessLibrary& InLibrary) + { + ::FPropertyAccessSystem::BindEvents(InObject, InObject->GetClass(), InLibrary); + } + + int32 GetEventId(const UClass* InClass, TArrayView InPath) + { + return ::FPropertyAccessSystem::GetEventId(InClass, InPath); + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MovieRenderPipelineEncoders.cpp b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Private/PropertyAccessModule.cpp similarity index 53% rename from Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MovieRenderPipelineEncoders.cpp rename to Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Private/PropertyAccessModule.cpp index 803dfd318590..1f5d2f01d65f 100644 --- a/Engine/Plugins/MovieScene/MovieRenderPipelineEncoders/Source/MovieRenderPipelineEncoders/Private/MovieRenderPipelineEncoders.cpp +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Private/PropertyAccessModule.cpp @@ -1,5 +1,6 @@ // Copyright Epic Games, Inc. All Rights Reserved. +#include "CoreMinimal.h" #include "Modules/ModuleManager.h" -IMPLEMENT_MODULE(FDefaultModuleImpl, MovieRenderPipelineEncoders) \ No newline at end of file +IMPLEMENT_MODULE(FDefaultModuleImpl, PropertyAccess) \ No newline at end of file diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/PropertyAccess.Build.cs b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/PropertyAccess.Build.cs new file mode 100644 index 000000000000..90ce36b32d46 --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/PropertyAccess.Build.cs @@ -0,0 +1,17 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class PropertyAccess : ModuleRules +{ + public PropertyAccess(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.AddRange(new string[] + { + "Core", + "CoreUObject", + "PropertyPath", + "Engine", + }); + } +} diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Public/AnimBlueprintClassSubsystem_PropertyAccess.h b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Public/AnimBlueprintClassSubsystem_PropertyAccess.h new file mode 100644 index 000000000000..097f954f6172 --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Public/AnimBlueprintClassSubsystem_PropertyAccess.h @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Animation/AnimBlueprintClassSubsystem.h" +#include "IPropertyAccess.h" +#include "PropertyAccess.h" + +#include "AnimBlueprintClassSubsystem_PropertyAccess.generated.h" + +UCLASS() +class PROPERTYACCESS_API UAnimBlueprintClassSubsystem_PropertyAccess : public UAnimBlueprintClassSubsystem, public IPropertyAccess +{ + GENERATED_BODY() + +public: + // UAnimBlueprintClassSubsystem interface + virtual void OnUpdateAnimation(UAnimInstance* InAnimInstance, FAnimInstanceSubsystemData& InSubsystemData, float InDeltaTime) override; + virtual void OnParallelUpdateAnimation(FAnimInstanceProxy& InProxy, FAnimInstanceSubsystemData& InSubsystemData, float InDeltaTime) override; + virtual void PostLoadSubsystem() override; + + // IPropertyAccess interface + virtual void ProcessCopies(UObject* InObject, EPropertyAccessCopyBatch InBatchType) const override; + virtual void ProcessCopy(UObject* InObject, EPropertyAccessCopyBatch InBatchType, int32 InCopyIndex, TFunctionRef InPostCopyOperation) const override; + virtual void BindEvents(UObject* InObject) const override; + virtual int32 GetEventId(const UClass* InClass, TArrayView InPath) const override; + + // Get the library that holds property access data + FPropertyAccessLibrary& GetLibrary() { return PropertyAccessLibrary; } + +private: + // The library holding the property access data + UPROPERTY() + FPropertyAccessLibrary PropertyAccessLibrary; +}; \ No newline at end of file diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Public/PropertyAccess.h b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Public/PropertyAccess.h new file mode 100644 index 000000000000..dc7c96ee6f9c --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Public/PropertyAccess.h @@ -0,0 +1,418 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Containers/ArrayView.h" +#include "UObject/WeakObjectPtrTemplates.h" +#include "UObject/Object.h" +#include "PropertyEventInterfaces.h" +#include "IPropertyAccess.h" + +#include "PropertyAccess.generated.h" + +struct FPropertyAccessSystem; +enum class EPropertyAccessType : uint8; + +namespace PropertyAccess +{ + /** + * Called to patch up library after it is loaded. + * This converts all FName-based paths into node-based paths that provide an optimized way of accessing properties. + */ + PROPERTYACCESS_API extern void PostLoadLibrary(FPropertyAccessLibrary& InLibrary); + + /** + * Process a 'tick' of a property access instance. + * Note internally allocates via FMemStack and pushes its own FMemMark + */ + PROPERTYACCESS_API extern void ProcessCopies(UObject* InObject, const FPropertyAccessLibrary& InLibrary, EPropertyAccessCopyBatch InBatchType); + + /** + * Process a single copy + * Note that this can potentially allocate via FMemStack, so inserting FMemMark before a number of these calls is recommended + */ + PROPERTYACCESS_API extern void ProcessCopy(UObject* InObject, const FPropertyAccessLibrary& InLibrary, EPropertyAccessCopyBatch InBatchType, int32 InCopyIndex, TFunctionRef InPostCopyOperation); + + /** Bind all event-type accesses to their respective objects */ + PROPERTYACCESS_API extern void BindEvents(UObject* InObject, const FPropertyAccessLibrary& InLibrary); + + /** Resolve a path to an event Id for the specified class */ + PROPERTYACCESS_API extern int32 GetEventId(const UClass* InClass, TArrayView InPath); +} + +// The type of an indirection +UENUM() +enum class EPropertyAccessIndirectionType : uint8 +{ + // Access node is a simple basePtr + offset + Offset, + + // Access node needs to dereference an object at its current address + Object, + + // Access node indexes a dynamic array + Array, + + // Access node calls a script function to get a value + ScriptFunction, + + // Access node calls a native function to get a value + NativeFunction, +}; + +// For object nodes, we need to know what type of object we are looking at so we can cast appropriately +UENUM() +enum class EPropertyAccessObjectType : uint8 +{ + // Access is not an object + None, + + // Access is an object + Object, + + // Access is a weak object + WeakObject, + + // Access is a soft object + SoftObject, +}; + +// Runtime-generated access node. +// Represents: +// - An offset within an object +// - An indirection to follow (object, array, function) +USTRUCT() +struct FPropertyAccessIndirection +{ + GENERATED_BODY() + + FPropertyAccessIndirection() = default; + +private: + friend struct ::FPropertyAccessSystem; + + // Array property if this is an array indirection + UPROPERTY() + TFieldPath ArrayProperty; + + // Function if this is a script of native function indirection + UPROPERTY() + UFunction* Function = nullptr; + + // Return buffer size if this is a script of native function indirection + UPROPERTY() + int32 ReturnBufferSize = 0; + + // Return buffer alignment if this is a script of native function indirection + UPROPERTY() + int32 ReturnBufferAlignment = 0; + + // Array index if this is an array indirection + UPROPERTY() + int32 ArrayIndex = INDEX_NONE; + + // Offset of this indirection within its containing object + UPROPERTY() + uint32 Offset = 0; + + // Object type if this is an object indirection + UPROPERTY() + EPropertyAccessObjectType ObjectType = EPropertyAccessObjectType::None; + + // The type of this indirection + UPROPERTY() + EPropertyAccessIndirectionType Type = EPropertyAccessIndirectionType::Offset; +}; + +// A single property access list. This is a list of FPropertyAccessIndirection +USTRUCT() +struct FPropertyAccessIndirectionChain +{ + GENERATED_BODY() + + FPropertyAccessIndirectionChain() = default; + +private: + friend struct ::FPropertyAccessSystem; + + // Leaf property + UPROPERTY() + TFieldPath Property = nullptr; + + // Index of the first indirection of a property access + UPROPERTY() + int32 IndirectionStartIndex = INDEX_NONE; + + // Index of the last indirection of a property access + UPROPERTY() + int32 IndirectionEndIndex = INDEX_NONE; + + // If this access is an event, then this will be the event Id of the property + UPROPERTY() + int32 EventId = INDEX_NONE; +}; + +// Flags for a segment of a property access path +// Note: NOT an UENUM as we dont support mixing flags and values properly in UENUMs, e.g. for serialization. +enum class EPropertyAccessSegmentFlags : uint16 +{ + // Segment has not been resolved yet, we don't know anything about it + Unresolved = 0, + + // Segment is a struct property + Struct, + + // Segment is a leaf property + Leaf, + + // Segment is an object + Object, + + // Segment is a weak object + WeakObject, + + // Segment is a soft object + SoftObject, + + // Segment is a dynamic array. If the index is INDEX_NONE, then the entire array is referenced. + Array, + + // Segment is a dynamic array of structs. If the index is INDEX_NONE, then the entire array is referenced. + ArrayOfStructs, + + // Segment is a dynamic array of objects. If the index is INDEX_NONE, then the entire array is referenced. + ArrayOfObjects, + + // Entries before this are exclusive values + LastExclusiveValue = ArrayOfObjects, + + // Segment is an object key for an event (object) + Event = (1 << 14), + + // Segment is a function + Function = (1 << 15), + + // All modifier flags + ModifierFlags = (Event | Function), +}; + +ENUM_CLASS_FLAGS(EPropertyAccessSegmentFlags); + +// A segment of a 'property path' used to access an object's properties from another location +USTRUCT() +struct FPropertyAccessSegment +{ + GENERATED_BODY() + + FPropertyAccessSegment() = default; + +private: + friend struct FPropertyAccessSystem; + friend struct FPropertyAccessEditorSystem; + + /** The sub-component of the property path, a single value between .'s of the path */ + UPROPERTY() + FName Name = NAME_None; + + /** The Class or ScriptStruct that was used last to resolve Name to a property. */ + UPROPERTY() + UStruct* Struct = nullptr; + + /** The cached property on the Struct that this Name resolved to at compile time. If this is a Function segment, then this is the return property of the function. */ + UPROPERTY() + TFieldPath Property; + + /** If this segment is a function, EPropertyAccessSegmentFlags::Function flag will be present and this value will be valid */ + UPROPERTY() + UFunction* Function = nullptr; + + /** The optional array index. */ + UPROPERTY() + int32 ArrayIndex = INDEX_NONE; + + /** @see EPropertyAccessSegmentFlags */ + UPROPERTY() + uint16 Flags = (uint16)EPropertyAccessSegmentFlags::Unresolved; +}; + +// A property access path. References a string of property access segments. +// These are resolved at load time to create corresponding FPropertyAccess entries +USTRUCT() +struct FPropertyAccessPath +{ + GENERATED_BODY() + + FPropertyAccessPath() = default; + +private: + friend struct FPropertyAccessSystem; + friend struct FPropertyAccessEditorSystem; + + // Index into the library's path segments. Used to provide a starting point for a path resolve + UPROPERTY() + int32 PathSegmentStartIndex = INDEX_NONE; + + // The count of the path segments. + UPROPERTY() + int32 PathSegmentCount = INDEX_NONE; + + // Whether this access has events in its path + UPROPERTY() + uint8 bHasEvents : 1; +}; + +UENUM() +enum class EPropertyAccessCopyType : uint8 +{ + // No copying + None, + + // For plain old data types, we do a simple memcpy. + Plain, + + // For more complex data types, we need to call the properties copy function + Complex, + + // Read and write properties using bool property helpers, as source/dest could be bitfield or boolean + Bool, + + // Use struct copy operation, as this needs to correctly handle CPP struct ops + Struct, + + // Read and write properties using object property helpers, as source/dest could be regular/weak/soft etc. + Object, + + // FName needs special case because its size changes between editor/compiler and runtime. + Name, + + // Array needs special handling for fixed size arrays + Array, + + // Promote the type during the copy + // Bool promotions + PromoteBoolToByte, + PromoteBoolToInt32, + PromoteBoolToInt64, + PromoteBoolToFloat, + + // Byte promotions + PromoteByteToInt32, + PromoteByteToInt64, + PromoteByteToFloat, + + // Int32 promotions + PromoteInt32ToInt64, + PromoteInt32ToFloat, // This is strictly sketchy because of potential data loss, but it is usually OK in the general case +}; + +// A property copy, represents a one-to-many copy operation +USTRUCT() +struct FPropertyAccessCopy +{ + GENERATED_BODY() + + FPropertyAccessCopy() = default; + +private: + friend struct FPropertyAccessSystem; + friend struct FPropertyAccessEditorSystem; + + // Index into the library's Accesses + UPROPERTY() + int32 AccessIndex = INDEX_NONE; + + // Index of the first of the library's DescAccesses + UPROPERTY() + int32 DestAccessStartIndex = INDEX_NONE; + + // Index of the last of the library's DescAccesses + UPROPERTY() + int32 DestAccessEndIndex = INDEX_NONE; + + UPROPERTY() + EPropertyAccessCopyType Type = EPropertyAccessCopyType::Plain; +}; + +USTRUCT() +struct FPropertyAccessCopyBatch +{ + GENERATED_BODY() + + FPropertyAccessCopyBatch() = default; + +private: + friend struct FPropertyAccessSystem; + friend struct FPropertyAccessEditorSystem; + + UPROPERTY() + TArray Copies; +}; + +/** A library of property paths used within a specific context (e.g. a class) */ +USTRUCT() +struct FPropertyAccessLibrary +{ + GENERATED_BODY() + + FPropertyAccessLibrary() = default; + +private: + friend struct FPropertyAccessSystem; + friend struct FPropertyAccessEditorSystem; + + // All path segments in this library. + UPROPERTY() + TArray PathSegments; + + // All source paths + UPROPERTY() + TArray SrcPaths; + + // All destination paths + UPROPERTY() + TArray DestPaths; + + // All copy operations + UPROPERTY() + FPropertyAccessCopyBatch CopyBatches[(uint8)EPropertyAccessCopyBatch::Count]; + + // All source property accesses + UPROPERTY(Transient) + TArray SrcAccesses; + + // All destination accesses (that are copied to our instances). + UPROPERTY(Transient) + TArray DestAccesses; + + // Indirections + UPROPERTY(Transient) + TArray Indirections; + + // Indexes into the SrcAccesses array to allow faster iteration of all event accesses + UPROPERTY() + TArray EventAccessIndices; + + // Whether this library has been post-loaded + bool bHasBeenPostLoaded = false; + + // A per-class mapping + struct FEventMapping + { + // The class that this mapping refers to + TWeakObjectPtr Class; + + // Mapping from class event Id to SrcAccesses index in this library + TArray Mapping; + }; + + // Per-class event ID mappings. Built dynamically at runtime. Maps class event IDs to SrcAccesses index. + TArray EventMappings; +}; + +// Broadcasts a property changed event. +// Arguments are of the form of a comma-separated list of property names, e.g. +// BROADCAST_PROPERTY_CHANGED("MyStructProperty", "MySubProperty"); +#define BROADCAST_PROPERTY_CHANGED(...) \ + static int32 EventId_##__LINE__ = PropertyAccess::GetEventId(GetClass(), { __VA_ARGS__ }); \ + static_cast(this)->BroadcastPropertyChanged(EventId_##__LINE__); \ No newline at end of file diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Public/PropertyEventInterfaces.h b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Public/PropertyEventInterfaces.h new file mode 100644 index 000000000000..5b39bceab04a --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccess/Public/PropertyEventInterfaces.h @@ -0,0 +1,52 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "PropertyEventInterfaces.generated.h" + +namespace PropertyAccess +{ + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPropertyChanged, UObject* /*InObject*/, int32 /*InBroadcastId*/); +} + +UINTERFACE(MinimalAPI) +class UPropertyEventBroadcaster : public UInterface +{ + GENERATED_BODY() +}; + +class IPropertyEventSubscriber; + +/** Interface used to broadcast property changed events. Inherit from this and call BROADCAST_PROPERTY_CHANGED */ +class IPropertyEventBroadcaster +{ + GENERATED_BODY() +public: + + /** Broadcast a property changing */ + virtual void BroadcastPropertyChanged(int32 InBroadcastId) const = 0; + + /** Register a subscriber to listen for property changed events */ + virtual void RegisterSubscriber(IPropertyEventSubscriber* InSubscriber, int32 InMappingId) = 0; + + /** Bind a delegate to the property changed event */ + virtual void UnregisterSubscriber(IPropertyEventSubscriber* InSubscriber) = 0; +}; + +UINTERFACE(MinimalAPI) +class UPropertyEventSubscriber : public UInterface +{ + GENERATED_BODY() +}; + +class IPropertyEventSubscriber +{ + GENERATED_BODY() +public: + + /** Handle a property changing */ + virtual void OnPropertyChanged(IPropertyEventBroadcaster* InObject, int32 InBroadcastId) const = 0; +}; diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/AnimBlueprintCompilerSubsystem_PropertyAccess.cpp b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/AnimBlueprintCompilerSubsystem_PropertyAccess.cpp new file mode 100644 index 000000000000..23915e7b73fd --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/AnimBlueprintCompilerSubsystem_PropertyAccess.cpp @@ -0,0 +1,59 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "AnimBlueprintCompilerSubsystem_PropertyAccess.h" +#include "AnimBlueprintClassSubsystem_PropertyAccess.h" +#include "Animation/AnimBlueprintGeneratedClass.h" + +void FPropertyAccessLibraryCompiler_AnimBlueprint::BeginCompilation(UClass* InClass) +{ + Class = InClass; + + UAnimBlueprintClassSubsystem_PropertyAccess* PropertyAccessSubsystem = Cast(CastChecked(InClass)->GetSubsystem(UAnimBlueprintClassSubsystem_PropertyAccess::StaticClass())); + if(PropertyAccessSubsystem) + { + Library = &PropertyAccessSubsystem->GetLibrary(); + } +} + +int32 UAnimBlueprintCompilerSubsystem_PropertyAccess::AddCopy(TArrayView InSourcePath, TArrayView InDestPath, EPropertyAccessBatchType InBatchType, UObject* InObject) +{ + return PropertyAccessLibraryCompiler.AddCopy(InSourcePath, InDestPath, InBatchType, InObject); +} + +void UAnimBlueprintCompilerSubsystem_PropertyAccess::StartCompilingClass(UClass* InClass) +{ + PropertyAccessLibraryCompiler.BeginCompilation(InClass); +} + +void UAnimBlueprintCompilerSubsystem_PropertyAccess::FinishCompilingClass(UClass* InClass) +{ + OnPreLibraryCompiledDelegate.Broadcast(); + + if(!PropertyAccessLibraryCompiler.FinishCompilation()) + { + PropertyAccessLibraryCompiler.IterateErrors([this](const FText& InErrorText, UObject* InObject) + { + // Output any property access errors as warnings + if(InObject) + { + GetMessageLog().Warning(*InErrorText.ToString(), InObject); + } + else + { + GetMessageLog().Warning(*InErrorText.ToString()); + } + }); + } + + OnPostLibraryCompiledDelegate.Broadcast(); +} + +void UAnimBlueprintCompilerSubsystem_PropertyAccess::GetRequiredClassSubsystems(TArray>& OutSubsystemClasses) const +{ + OutSubsystemClasses.Add(UAnimBlueprintClassSubsystem_PropertyAccess::StaticClass()); +} + +int32 UAnimBlueprintCompilerSubsystem_PropertyAccess::MapCopyIndex(int32 InIndex) const +{ + return PropertyAccessLibraryCompiler.MapCopyIndex(InIndex); +} \ No newline at end of file diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/AnimBlueprintCompilerSubsystem_PropertyAccess.h b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/AnimBlueprintCompilerSubsystem_PropertyAccess.h new file mode 100644 index 000000000000..013aa83173f9 --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/AnimBlueprintCompilerSubsystem_PropertyAccess.h @@ -0,0 +1,46 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AnimBlueprintCompilerSubsystem.h" +#include "IPropertyAccessCompilerSubsystem.h" +#include "PropertyAccessEditor.h" + +#include "AnimBlueprintCompilerSubsystem_PropertyAccess.generated.h" + +class FPropertyAccessLibraryCompiler_AnimBlueprint : public FPropertyAccessLibraryCompiler +{ +public: + // IPropertyAccessLibraryCompiler + virtual void BeginCompilation(UClass* InClass) override; +}; + +UCLASS() +class UAnimBlueprintCompilerSubsystem_PropertyAccess : public UAnimBlueprintCompilerSubsystem, public IPropertyAccessCompilerSubsystem +{ + GENERATED_BODY() + +public: + // IAnimBlueprintCompilerSubsystem_PropertyAccess interface + virtual int32 AddCopy(TArrayView InSourcePath, TArrayView InDestPath, EPropertyAccessBatchType InBatchType, UObject* InObject = nullptr) override; + virtual FSimpleMulticastDelegate& OnPreLibraryCompiled() override { return OnPreLibraryCompiledDelegate; } + virtual FSimpleMulticastDelegate& OnPostLibraryCompiled() override { return OnPostLibraryCompiledDelegate; } + virtual int32 MapCopyIndex(int32 InIndex) const override; + +private: + // UAnimBlueprintCompilerSubsystem interface + virtual void StartCompilingClass(UClass* InClass) override; + virtual void FinishCompilingClass(UClass* InClass) override; + virtual void GetRequiredClassSubsystems(TArray>& OutSubsystemClasses) const override; + +private: + // Property access library compiler + FPropertyAccessLibraryCompiler_AnimBlueprint PropertyAccessLibraryCompiler; + + // Delegate called before the library is compiled + FSimpleMulticastDelegate OnPreLibraryCompiledDelegate; + + // Delegate called when the library is compiled (whether successfully or not) + FSimpleMulticastDelegate OnPostLibraryCompiledDelegate; +}; \ No newline at end of file diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/PropertyAccessEditor.cpp b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/PropertyAccessEditor.cpp new file mode 100644 index 000000000000..46bf23eaaa2f --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/PropertyAccessEditor.cpp @@ -0,0 +1,635 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "PropertyAccessEditor.h" +#include "PropertyAccess.h" +#include "PropertyPathHelpers.h" +#include "Algo/Transform.h" +#include "IPropertyAccessEditor.h" +#include "EdGraph/EdGraphPin.h" +#include "EdGraphSchema_K2.h" + +#define LOCTEXT_NAMESPACE "PropertyAccessEditor" + +struct FPropertyAccessEditorSystem +{ + struct FResolveSegmentsContext + { + FResolveSegmentsContext(UStruct* InStruct, TArrayView InPath, FPropertyAccessPath& InAccessPath) + : Struct(InStruct) + , CurrentStruct(InStruct) + , Path(InPath) + , AccessPath(InAccessPath) + {} + + // Starting struct + UStruct* Struct; + + // Starting struct + UStruct* CurrentStruct; + + // Path as FStrings with optional array markup + TArrayView Path; + + // The access path we are building + FPropertyAccessPath& AccessPath; + + // Output segments + TArray Segments; + + // The last error message produced + FText ErrorMessage; + + // The current segment index (or that at which the last error occurred) + int32 SegmentIndex; + + // Whether this is the final segment + bool bFinalSegment; + }; + + // The result of a segment resolve operation + enum class ESegmentResolveResult + { + Skipped, + + Failed, + + SucceededInternal, + + SucceededExternal, + }; + + static ESegmentResolveResult ResolveSegments_CheckProperty(FPropertyAccessSegment& InSegment, FProperty* InProperty, FResolveSegmentsContext& InContext) + { + ESegmentResolveResult Result = ESegmentResolveResult::SucceededInternal; + + InSegment.Property = InProperty; + + // Check to see if it is an array first, as arrays get handled the same for 'leaf' and 'branch' nodes + FArrayProperty* ArrayProperty = CastField(InProperty); + if(ArrayProperty != nullptr && InSegment.ArrayIndex != INDEX_NONE) + { + // It is an array, now check to see if this is an array of structures + if(FStructProperty* ArrayOfStructsProperty = CastField(ArrayProperty->Inner)) + { + InSegment.Flags |= (uint16)EPropertyAccessSegmentFlags::ArrayOfStructs; + InSegment.Struct = ArrayOfStructsProperty->Struct; + } + // if it's not an array of structs, maybe it's an array of objects + else if(FObjectPropertyBase* ArrayOfObjectsProperty = CastField(ArrayProperty->Inner)) + { + InSegment.Flags |= (uint16)EPropertyAccessSegmentFlags::ArrayOfObjects; + InSegment.Struct = ArrayOfObjectsProperty->PropertyClass; + if(!InContext.bFinalSegment) + { + Result = ESegmentResolveResult::SucceededExternal; + } + } + else + { + InSegment.Flags |= (uint16)EPropertyAccessSegmentFlags::Array; + } + } + // Leaf segments all get treated the same, plain, struct or object + else if(InContext.bFinalSegment) + { + InSegment.Flags |= (uint16)EPropertyAccessSegmentFlags::Leaf; + InSegment.Struct = nullptr; + } + // Check to see if this is a simple structure (eg. not an array of structures) + else if(FStructProperty* StructProperty = CastField(InProperty)) + { + InSegment.Flags |= (uint16)EPropertyAccessSegmentFlags::Struct; + InSegment.Struct = StructProperty->Struct; + } + // Check to see if this is a simple object (eg. not an array of objects) + else if(FObjectProperty* ObjectProperty = CastField(InProperty)) + { + InSegment.Flags |= (uint16)EPropertyAccessSegmentFlags::Object; + InSegment.Struct = ObjectProperty->PropertyClass; + if(!InContext.bFinalSegment) + { + Result = ESegmentResolveResult::SucceededExternal; + } + } + // Check to see if this is a simple weak object property (eg. not an array of weak objects). + else if(FWeakObjectProperty* WeakObjectProperty = CastField(InProperty)) + { + InSegment.Flags |= (uint16)EPropertyAccessSegmentFlags::WeakObject; + InSegment.Struct = WeakObjectProperty->PropertyClass; + Result = ESegmentResolveResult::SucceededExternal; + if(!InContext.bFinalSegment) + { + Result = ESegmentResolveResult::SucceededExternal; + } + } + // Check to see if this is a simple soft object property (eg. not an array of soft objects). + else if(FSoftObjectProperty* SoftObjectProperty = CastField(InProperty)) + { + InSegment.Flags |= (uint16)EPropertyAccessSegmentFlags::SoftObject; + InSegment.Struct = SoftObjectProperty->PropertyClass; + Result = ESegmentResolveResult::SucceededExternal; + if(!InContext.bFinalSegment) + { + Result = ESegmentResolveResult::SucceededExternal; + } + } + else + { + return ESegmentResolveResult::Failed; + } + + static const FName PropertyEventMetadata("PropertyEvent"); + if(InProperty->HasMetaData(PropertyEventMetadata)) + { + InSegment.Flags |= (uint16)EPropertyAccessSegmentFlags::Event; + } + + return Result; + } + + static ESegmentResolveResult ResolveSegments_CheckFunction(FPropertyAccessSegment& InSegment, UFunction* InFunction, FResolveSegmentsContext& InContext) + { + InSegment.Function = InFunction; + InSegment.Flags |= (uint16)EPropertyAccessSegmentFlags::Function; + + // Functions are always 'getters', so we need to verify their form + if(InFunction->NumParms != 1) + { + InContext.ErrorMessage = FText::Format(LOCTEXT("FunctionHasTooManyParameters", "Function '{0}' has too many parameters in property path for @@"), FText::FromName(InFunction->GetFName())); + return ESegmentResolveResult::Failed; + } + + FProperty* ReturnProperty = InFunction->GetReturnProperty(); + if(ReturnProperty == nullptr) + { + InContext.ErrorMessage = FText::Format(LOCTEXT("FunctionHasNoReturnValue", "Function '{0}' has no return value in property path for @@"), FText::FromName(InFunction->GetFName())); + return ESegmentResolveResult::Failed; + } + + // Treat the function's return value as the struct/class we want to use for the next segment + return ResolveSegments_CheckProperty(InSegment, ReturnProperty, InContext); + } + + // Called at compile time to build out a segments array + static EPropertyAccessResolveResult ResolveSegments(FResolveSegmentsContext& InContext) + { + check(InContext.Struct); + + if(InContext.Path.Num() > 0) + { + EPropertyAccessResolveResult Result = EPropertyAccessResolveResult::SucceededInternal; + + for(int32 SegmentIndex = 0; SegmentIndex < InContext.Path.Num(); ++SegmentIndex) + { + const FString& SegmentString = InContext.Path[SegmentIndex]; + + FPropertyAccessSegment& Segment = InContext.Segments.AddDefaulted_GetRef(); + const TCHAR* PropertyNamePtr = nullptr; + int32 PropertyNameLength = 0; + int32 ArrayIndex = INDEX_NONE; + PropertyPathHelpers::FindFieldNameAndArrayIndex(SegmentString.Len(), *SegmentString, PropertyNameLength, &PropertyNamePtr, ArrayIndex); + ensure(PropertyNamePtr != nullptr); + FString PropertyNameString(PropertyNameLength, PropertyNamePtr); + Segment.Name = FName(*PropertyNameString, FNAME_Find); + Segment.ArrayIndex = ArrayIndex; + + InContext.SegmentIndex = SegmentIndex; + InContext.bFinalSegment = SegmentIndex == InContext.Path.Num() - 1; + + if(InContext.CurrentStruct == nullptr) + { + InContext.ErrorMessage = LOCTEXT("MalformedPath", "Malformed property path for @@"); + return EPropertyAccessResolveResult::Failed; + } + + // Obtain the property/function from the given structure definition + FFieldVariant Field = FindUFieldOrFProperty(InContext.CurrentStruct, Segment.Name); + if(!Field.IsValid()) + { + InContext.ErrorMessage = FText::Format(LOCTEXT("InvalidField", "Invalid field '{0}' found in property path for @@"), FText::FromName(Segment.Name)); + return EPropertyAccessResolveResult::Failed; + } + + if(FProperty* Property = Field.Get()) + { + ESegmentResolveResult PropertyResult = ResolveSegments_CheckProperty(Segment, Property, InContext); + if(PropertyResult == ESegmentResolveResult::SucceededExternal) + { + Result = EPropertyAccessResolveResult::SucceededExternal; + } + else if(PropertyResult == ESegmentResolveResult::Failed) + { + return EPropertyAccessResolveResult::Failed; + } + } + else if(UFunction* Function = Field.Get()) + { + ESegmentResolveResult FunctionResult = ResolveSegments_CheckFunction(Segment, Function, InContext); + if(FunctionResult == ESegmentResolveResult::SucceededExternal) + { + Result = EPropertyAccessResolveResult::SucceededExternal; + } + else if(FunctionResult == ESegmentResolveResult::Failed) + { + return EPropertyAccessResolveResult::Failed; + } + } + + InContext.CurrentStruct = Segment.Struct; + } + + return InContext.Segments.Num() > 0 ? Result : EPropertyAccessResolveResult::Failed; + } + else + { + InContext.ErrorMessage = LOCTEXT("InvalidPath", "Invalid path found for @@"); + return EPropertyAccessResolveResult::Failed; + } + } + + static EPropertyAccessResolveResult ResolveLeafProperty(UStruct* InStruct, TArrayView InPath, FProperty*& OutProperty, int32& OutArrayIndex) + { + FPropertyAccessPath AccessPath; + FResolveSegmentsContext Context(InStruct, InPath, AccessPath); + EPropertyAccessResolveResult Result = ResolveSegments(Context); + if(Result != EPropertyAccessResolveResult::Failed) + { + const FPropertyAccessSegment& LeafSegment = Context.Segments.Last(); + OutProperty = LeafSegment.Property.Get(); + OutArrayIndex = LeafSegment.ArrayIndex; + return Result; + } + + return Result; + } + + static EPropertyAccessCopyType GetCopyType(const FPropertyAccessSegment& InSrcSegment, const FPropertyAccessSegment& InDestSegment) + { + FProperty* SrcProperty = InSrcSegment.Property.Get(); + check(SrcProperty); + + if(FArrayProperty* SrcArrayProperty = CastField(SrcProperty)) + { + // use the array's inner property if we are not trying to copy the whole array + if(InSrcSegment.ArrayIndex != INDEX_NONE) + { + SrcProperty = SrcArrayProperty->Inner; + } + } + + FProperty* DestProperty = InDestSegment.Property.Get(); + check(DestProperty); + + if(FArrayProperty* DestArrayProperty = CastField(DestProperty)) + { + // use the array's inner property if we are not trying to copy the whole array + if(InDestSegment.ArrayIndex != INDEX_NONE) + { + DestProperty = DestArrayProperty->Inner; + } + } + + EPropertyAccessCompatibility Compatibility = PropertyAccess::GetPropertyCompatibility(SrcProperty, DestProperty); + if(Compatibility == EPropertyAccessCompatibility::Compatible) + { + if (CastField(DestProperty)) + { + return EPropertyAccessCopyType::Name; + } + else if (CastField(DestProperty)) + { + return EPropertyAccessCopyType::Bool; + } + else if (CastField(DestProperty)) + { + return EPropertyAccessCopyType::Struct; + } + else if (CastField(DestProperty)) + { + return EPropertyAccessCopyType::Object; + } + else if (CastField(DestProperty) && DestProperty->HasAnyPropertyFlags(CPF_EditFixedSize)) + { + // only apply array copying rules if the destination array is fixed size, otherwise it will be 'complex' + return EPropertyAccessCopyType::Array; + } + else if(DestProperty->PropertyFlags & CPF_IsPlainOldData) + { + return EPropertyAccessCopyType::Plain; + } + else + { + return EPropertyAccessCopyType::Complex; + } + } + else if(Compatibility == EPropertyAccessCompatibility::Promotable) + { + if(SrcProperty->IsA()) + { + if(DestProperty->IsA()) + { + return EPropertyAccessCopyType::PromoteBoolToByte; + } + else if(DestProperty->IsA()) + { + return EPropertyAccessCopyType::PromoteBoolToInt32; + } + else if(DestProperty->IsA()) + { + return EPropertyAccessCopyType::PromoteBoolToInt64; + } + else if(DestProperty->IsA()) + { + return EPropertyAccessCopyType::PromoteBoolToFloat; + } + } + else if(SrcProperty->IsA()) + { + if(DestProperty->IsA()) + { + return EPropertyAccessCopyType::PromoteByteToInt32; + } + else if(DestProperty->IsA()) + { + return EPropertyAccessCopyType::PromoteByteToInt64; + } + else if(DestProperty->IsA()) + { + return EPropertyAccessCopyType::PromoteByteToFloat; + } + } + else if(SrcProperty->IsA()) + { + if(DestProperty->IsA()) + { + return EPropertyAccessCopyType::PromoteInt32ToInt64; + } + else if(DestProperty->IsA()) + { + return EPropertyAccessCopyType::PromoteInt32ToFloat; + } + } + } + + checkf(false, TEXT("Couldnt determine property copy type (%s -> %s)"), *SrcProperty->GetName(), *DestProperty->GetName()); + + return EPropertyAccessCopyType::None; + } + + static bool CompileCopy(UStruct* InStruct, FPropertyAccessLibrary& InLibrary, FPropertyAccessLibraryCompiler::FQueuedCopy& OutCopy) + { + FPropertyAccessPath SrcAccessPath; + FPropertyAccessPath DestAccessPath; + + FResolveSegmentsContext SrcContext(InStruct, OutCopy.SourcePath, SrcAccessPath); + FResolveSegmentsContext DestContext(InStruct, OutCopy.DestPath, DestAccessPath); + + OutCopy.SourceResult = ResolveSegments(SrcContext); + OutCopy.SourceErrorText = SrcContext.ErrorMessage; + OutCopy.DestResult = ResolveSegments(DestContext); + OutCopy.DestErrorText = SrcContext.ErrorMessage; + + if(OutCopy.SourceResult != EPropertyAccessResolveResult::Failed && OutCopy.DestResult != EPropertyAccessResolveResult::Failed) + { + const bool bExternal = OutCopy.SourceResult == EPropertyAccessResolveResult::SucceededExternal || OutCopy.DestResult == EPropertyAccessResolveResult::SucceededExternal; + + // Decide on batch type + EPropertyAccessCopyBatch CopyBatch; + if(bExternal) + { + CopyBatch = OutCopy.BatchType == EPropertyAccessBatchType::Unbatched ? EPropertyAccessCopyBatch::ExternalUnbatched : EPropertyAccessCopyBatch::ExternalBatched; + } + else + { + CopyBatch = OutCopy.BatchType == EPropertyAccessBatchType::Unbatched ? EPropertyAccessCopyBatch::InternalUnbatched : EPropertyAccessCopyBatch::InternalBatched; + } + + OutCopy.BatchIndex = InLibrary.CopyBatches[(__underlying_type(EPropertyAccessCopyBatch))CopyBatch].Copies.Num(); + FPropertyAccessCopy& Copy = InLibrary.CopyBatches[(__underlying_type(EPropertyAccessCopyBatch))CopyBatch].Copies.AddDefaulted_GetRef(); + Copy.AccessIndex = InLibrary.SrcPaths.Num(); + Copy.DestAccessStartIndex = InLibrary.DestPaths.Num(); + Copy.DestAccessEndIndex = InLibrary.DestPaths.Num() + 1; + Copy.Type = GetCopyType(SrcContext.Segments.Last(), DestContext.Segments.Last()); + + SrcAccessPath.PathSegmentStartIndex = InLibrary.PathSegments.Num(); + SrcAccessPath.PathSegmentCount = SrcContext.Segments.Num(); + InLibrary.SrcPaths.Add(SrcAccessPath); + InLibrary.PathSegments.Append(SrcContext.Segments); + + DestAccessPath.PathSegmentStartIndex = InLibrary.PathSegments.Num(); + DestAccessPath.PathSegmentCount = DestContext.Segments.Num(); + InLibrary.DestPaths.Add(DestAccessPath); + InLibrary.PathSegments.Append(DestContext.Segments); + + return true; + } + + return false; + } +}; + +namespace PropertyAccess +{ + EPropertyAccessResolveResult ResolveLeafProperty(UStruct* InStruct, TArrayView InPath, FProperty*& OutProperty, int32& OutArrayIndex) + { + return ::FPropertyAccessEditorSystem::ResolveLeafProperty(InStruct, InPath, OutProperty, OutArrayIndex); + } + + EPropertyAccessCompatibility GetPropertyCompatibility(const FProperty* InPropertyA, const FProperty* InPropertyB) + { + if(InPropertyA == InPropertyB) + { + return EPropertyAccessCompatibility::Compatible; + } + + if(InPropertyA == nullptr || InPropertyB == nullptr) + { + return EPropertyAccessCompatibility::Incompatible; + } + + // Special case for object properties + if(InPropertyA->IsA() && InPropertyB->IsA()) + { + return EPropertyAccessCompatibility::Compatible; + } + + // Extract underlying types for enums + if(const FEnumProperty* EnumPropertyA = CastField(InPropertyA)) + { + InPropertyA = EnumPropertyA->GetUnderlyingProperty(); + } + + if(const FEnumProperty* EnumPropertyB = CastField(InPropertyB)) + { + InPropertyB = EnumPropertyB->GetUnderlyingProperty(); + } + + if(InPropertyA->SameType(InPropertyB)) + { + return EPropertyAccessCompatibility::Compatible; + } + else + { + // Not directly compatible, check for promotions + if(InPropertyA->IsA()) + { + if(InPropertyB->IsA() || InPropertyB->IsA() || InPropertyB->IsA() || InPropertyB->IsA()) + { + return EPropertyAccessCompatibility::Promotable; + } + } + else if(InPropertyA->IsA()) + { + if(InPropertyB->IsA() || InPropertyB->IsA() || InPropertyB->IsA()) + { + return EPropertyAccessCompatibility::Promotable; + } + } + else if(InPropertyA->IsA()) + { + if(InPropertyB->IsA() || InPropertyB->IsA()) + { + return EPropertyAccessCompatibility::Promotable; + } + } + } + + return EPropertyAccessCompatibility::Incompatible; + } + + EPropertyAccessCompatibility GetPinTypeCompatibility(const FEdGraphPinType& InPinTypeA, const FEdGraphPinType& InPinTypeB) + { + const UEdGraphSchema_K2* Schema = GetDefault(); + if(Schema->ArePinTypesCompatible(InPinTypeA, InPinTypeB)) + { + return EPropertyAccessCompatibility::Compatible; + } + else + { + // Not directly compatible, check for promotions + if(InPinTypeA.PinCategory == UEdGraphSchema_K2::PC_Boolean) + { + if(InPinTypeB.PinCategory == UEdGraphSchema_K2::PC_Byte || InPinTypeB.PinCategory == UEdGraphSchema_K2::PC_Int || InPinTypeB.PinCategory == UEdGraphSchema_K2::PC_Int64 || InPinTypeB.PinCategory == UEdGraphSchema_K2::PC_Float) + { + return EPropertyAccessCompatibility::Promotable; + } + } + else if(InPinTypeA.PinCategory == UEdGraphSchema_K2::PC_Byte) + { + if(InPinTypeB.PinCategory == UEdGraphSchema_K2::PC_Int || InPinTypeB.PinCategory == UEdGraphSchema_K2::PC_Int64 || InPinTypeB.PinCategory == UEdGraphSchema_K2::PC_Float) + { + return EPropertyAccessCompatibility::Promotable; + } + } + else if(InPinTypeA.PinCategory == UEdGraphSchema_K2::PC_Int) + { + if(InPinTypeB.PinCategory == UEdGraphSchema_K2::PC_Int64 || InPinTypeB.PinCategory == UEdGraphSchema_K2::PC_Float) + { + return EPropertyAccessCompatibility::Promotable; + } + } + } + + return EPropertyAccessCompatibility::Incompatible; + } + + void MakeStringPath(const TArray& InBindingChain, TArray& OutStringPath) + { + Algo::Transform(InBindingChain, OutStringPath, [](const FBindingChainElement& InElement) + { + if(FProperty* Property = InElement.Field.Get()) + { + if(InElement.ArrayIndex == INDEX_NONE) + { + return Property->GetName(); + } + else + { + return FString::Printf(TEXT("%s[%d]"), *Property->GetName(), InElement.ArrayIndex); + } + } + else if(UFunction* Function = InElement.Field.Get()) + { + return Function->GetName(); + } + else + { + check(false); + return FString(); + } + }); + } +} + +FPropertyAccessLibraryCompiler::FPropertyAccessLibraryCompiler() + : Library(nullptr) + , Class(nullptr) +{ +} + +int32 FPropertyAccessLibraryCompiler::AddCopy(TArrayView InSourcePath, TArrayView InDestPath, EPropertyAccessBatchType InBatchType, UObject* InAssociatedObject) +{ + FQueuedCopy QueuedCopy; + QueuedCopy.SourcePath = InSourcePath; + QueuedCopy.DestPath = InDestPath; + QueuedCopy.BatchType = InBatchType; + QueuedCopy.AssociatedObject = InAssociatedObject; + + QueuedCopies.Add(MoveTemp(QueuedCopy)); + + return QueuedCopies.Num() - 1; +} + +bool FPropertyAccessLibraryCompiler::FinishCompilation() +{ + if(Class && Library) + { + bool bResult = true; + for(int32 CopyIndex = 0; CopyIndex < QueuedCopies.Num(); ++CopyIndex) + { + FQueuedCopy& Copy = QueuedCopies[CopyIndex]; + bResult &= ::FPropertyAccessEditorSystem::CompileCopy(Class, *Library, Copy); + CopyMap.Add(CopyIndex, Copy.BatchIndex); + } + + if(bResult) + { + PropertyAccess::PostLoadLibrary(*Library); + } + + return bResult; + } + + return false; +} + +void FPropertyAccessLibraryCompiler::IterateErrors(TFunctionRef InFunction) const +{ + if(Class && Library) + { + for(const FQueuedCopy& Copy : QueuedCopies) + { + if(Copy.SourceResult == EPropertyAccessResolveResult::Failed) + { + InFunction(Copy.SourceErrorText, Copy.AssociatedObject); + } + + if(Copy.DestResult == EPropertyAccessResolveResult::Failed) + { + InFunction(Copy.DestErrorText, Copy.AssociatedObject); + } + } + } +} + +int32 FPropertyAccessLibraryCompiler::MapCopyIndex(int32 InIndex) const +{ + if(const int32* FoundIndex = CopyMap.Find(InIndex)) + { + return *FoundIndex; + } + + return INDEX_NONE; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/PropertyAccessEditor.h b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/PropertyAccessEditor.h new file mode 100644 index 000000000000..e8ca46391e9a --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/PropertyAccessEditor.h @@ -0,0 +1,68 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Containers/ArrayView.h" +#include "UObject/Object.h" +#include "IPropertyAccessEditor.h" +#include "IPropertyAccessCompiler.h" + +struct FEdGraphPinType; + +namespace PropertyAccess +{ + /** Resolve a property path to a structure, returning the leaf property and array index if any. @return true if resolution succeeded */ + extern EPropertyAccessResolveResult ResolveLeafProperty(UStruct* InStruct, TArrayView InPath, FProperty*& OutProperty, int32& OutArrayIndex); + + // Get the compatibility of the two supplied properties. Ordering matters for promotion (A->B). + extern EPropertyAccessCompatibility GetPropertyCompatibility(const FProperty* InPropertyA, const FProperty* InPropertyB); + + // Get the compatibility of the two supplied pins. Ordering matters for promotion (A->B). + extern EPropertyAccessCompatibility GetPinTypeCompatibility(const FEdGraphPinType& InPinTypeA, const FEdGraphPinType& InPinTypeB); + + // Makes a string path from a binding chain + extern void MakeStringPath(const TArray& InBindingChain, TArray& OutStringPath); +} + +// A helper structure used to compile a property access library +class FPropertyAccessLibraryCompiler : public IPropertyAccessLibraryCompiler +{ +public: + FPropertyAccessLibraryCompiler(); + + // IPropertyAccessLibraryCompiler interface + virtual int32 AddCopy(TArrayView InSourcePath, TArrayView InDestPath, EPropertyAccessBatchType InBatchType, UObject* InAssociatedObject = nullptr) override; + virtual bool FinishCompilation() override; + virtual void IterateErrors(TFunctionRef InFunction) const override; + virtual int32 MapCopyIndex(int32 InIndex) const override; + + // Stored copy info for processing in FinishCompilation() + struct FQueuedCopy + { + TArray SourcePath; + TArray DestPath; + EPropertyAccessBatchType BatchType; + FText SourceErrorText; + FText DestErrorText; + EPropertyAccessResolveResult SourceResult = EPropertyAccessResolveResult::Failed; + EPropertyAccessResolveResult DestResult = EPropertyAccessResolveResult::Failed; + UObject* AssociatedObject = nullptr; + int32 BatchIndex = INDEX_NONE; + }; + +protected: + friend struct FPropertyAccessEditorSystem; + + // The library we are compiling + FPropertyAccessLibrary* Library; + + // The class we are compiling the library for + UClass* Class; + + // All copies to process in FinishCompilation() + TArray QueuedCopies; + + // Copy map + TMap CopyMap; +}; diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/PropertyAccessEditorModule.cpp b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/PropertyAccessEditorModule.cpp new file mode 100644 index 000000000000..f3cdaf4054a3 --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/PropertyAccessEditorModule.cpp @@ -0,0 +1,54 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "CoreMinimal.h" +#include "IPropertyAccessEditor.h" +#include "Modules/ModuleManager.h" +#include "SPropertyBinding.h" +#include "EdGraphUtilities.h" +#include "PropertyAccessEditor.h" +#include "Modules/ModuleInterface.h" +#include "Features/IModularFeatures.h" + +class FPropertyAccessEditorModule : public IPropertyAccessEditor, public IModuleInterface +{ +public: + // IModuleInterface interface + virtual void StartupModule() override + { + IModularFeatures::Get().RegisterModularFeature("PropertyAccessEditor", this); + } + + virtual void ShutdownModule() override + { + IModularFeatures::Get().UnregisterModularFeature("PropertyAccessEditor", this); + } + + // IPropertyAccessEditor interface + virtual TSharedRef MakePropertyBindingWidget(UBlueprint* InBlueprint, const FPropertyBindingWidgetArgs& InArgs) const override + { + return SNew(SPropertyBinding, InBlueprint) + .Args(InArgs); + } + + virtual EPropertyAccessResolveResult ResolveLeafProperty(UStruct* InStruct, TArrayView InPath, FProperty*& OutProperty, int32& OutArrayIndex) const override + { + return PropertyAccess::ResolveLeafProperty(InStruct, InPath, OutProperty, OutArrayIndex); + } + + virtual EPropertyAccessCompatibility GetPropertyCompatibility(const FProperty* InPropertyA, const FProperty* InPropertyB) const override + { + return PropertyAccess::GetPropertyCompatibility(InPropertyA, InPropertyB); + } + + virtual EPropertyAccessCompatibility GetPinTypeCompatibility(const FEdGraphPinType& InPinTypeA, const FEdGraphPinType& InPinTypeB) const override + { + return PropertyAccess::GetPinTypeCompatibility(InPinTypeA, InPinTypeB); + } + + virtual void MakeStringPath(const TArray& InBindingChain, TArray& OutStringPath) const override + { + PropertyAccess::MakeStringPath(InBindingChain, OutStringPath); + } +}; + +IMPLEMENT_MODULE(FPropertyAccessEditorModule, PropertyAccessEditor) \ No newline at end of file diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/SPropertyBinding.cpp b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/SPropertyBinding.cpp new file mode 100644 index 000000000000..0a671808a3b0 --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/SPropertyBinding.cpp @@ -0,0 +1,698 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SPropertyBinding.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Text/STextBlock.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SComboButton.h" + +#if WITH_EDITOR +#include "Components/PrimitiveComponent.h" +#include "Components/StaticMeshComponent.h" +#include "Engine/BlueprintGeneratedClass.h" +#endif // WITH_EDITOR + +#include "EdGraph/EdGraph.h" +#include "EdGraphSchema_K2.h" + +#include "DetailLayoutBuilder.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "ScopedTransaction.h" +#include "Components/WidgetComponent.h" +#include "Binding/PropertyBinding.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Algo/Transform.h" +#include "Widgets/Layout/SSpacer.h" + +#define LOCTEXT_NAMESPACE "PropertyBinding" + +///////////////////////////////////////////////////// +// SPropertyBinding + +void SPropertyBinding::Construct(const FArguments& InArgs, UBlueprint* InBlueprint) +{ + Blueprint = InBlueprint; + + Args = InArgs._Args; + PropertyName = Args.Property != nullptr ? Args.Property->GetFName() : NAME_None; + + ChildSlot + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SBox) + .MaxDesiredWidth(200.0f) + [ + SNew(SComboButton) + .ToolTipText(this, &SPropertyBinding::GetCurrentBindingText) + .OnGetMenuContent(this, &SPropertyBinding::OnGenerateDelegateMenu) + .ContentPadding(1) + .ButtonContent() + [ + SNew(SHorizontalBox) + .Clipping(EWidgetClipping::ClipToBounds) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SBox) + .HeightOverride(16.0f) + [ + SNew(SImage) + .Image(this, &SPropertyBinding::GetCurrentBindingImage) + .ColorAndOpacity(this, &SPropertyBinding::GetCurrentBindingColor) + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4, 0, 0, 0) + [ + SNew(STextBlock) + .Text(this, &SPropertyBinding::GetCurrentBindingText) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ] + ] + + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") + .Visibility(this, &SPropertyBinding::GetGotoBindingVisibility) + .OnClicked(this, &SPropertyBinding::HandleGotoBindingClicked) + .VAlign(VAlign_Center) + .ToolTipText(LOCTEXT("GotoFunction", "Goto Function")) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.Button_Browse")) + ] + ] + ]; +} + +bool SPropertyBinding::IsClassBlackListed(UClass* InClass) const +{ + if(Args.OnCanBindToClass.IsBound() && Args.OnCanBindToClass.Execute(InClass)) + { + return false; + } + + return true; +} + +bool SPropertyBinding::IsFieldFromBlackListedClass(FFieldVariant Field) const +{ + return IsClassBlackListed(Field.GetOwnerClass()); +} + +template +void SPropertyBinding::ForEachBindableFunction(UClass* FromClass, Predicate Pred) const +{ + if(Args.OnCanBindFunction.IsBound()) + { + // Walk up class hierarchy for native functions and properties + for ( TFieldIterator FuncIt(FromClass, EFieldIteratorFlags::IncludeSuper); FuncIt; ++FuncIt ) + { + UFunction* Function = *FuncIt; + + // Stop processing functions after reaching a base class that it doesn't make sense to go beyond. + if ( IsFieldFromBlackListedClass(Function) ) + { + break; + } + + // Only allow binding pure functions if we're limited to pure function bindings. + if ( Args.bGeneratePureBindings && !Function->HasAnyFunctionFlags(FUNC_Const | FUNC_BlueprintPure) ) + { + continue; + } + + // Only bind to functions that are callable from blueprints + if ( !UEdGraphSchema_K2::CanUserKismetCallFunction(Function) ) + { + continue; + } + + bool bValidObjectFunction = false; + if(Args.bAllowUObjectFunctions) + { + FObjectPropertyBase* ObjectPropertyBase = CastField(Function->GetReturnProperty()); + if(ObjectPropertyBase != nullptr && Function->NumParms == 1) + { + bValidObjectFunction = true; + } + } + + if(bValidObjectFunction || Args.OnCanBindFunction.Execute(Function)) + { + Pred(FFunctionInfo(Function)); + } + } + } +} + +template +void SPropertyBinding::ForEachBindableProperty(UStruct* InStruct, Predicate Pred) const +{ + if(Args.OnCanBindProperty.IsBound()) + { + UBlueprintGeneratedClass* SkeletonClass = Cast(Blueprint->SkeletonGeneratedClass); + + for ( TFieldIterator PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt ) + { + FProperty* Property = *PropIt; + + // Stop processing properties after reaching the stopped base class + if ( IsFieldFromBlackListedClass(Property) ) + { + break; + } + + if ( !UEdGraphSchema_K2::CanUserKismetAccessVariable(Property, SkeletonClass, UEdGraphSchema_K2::CannotBeDelegate) ) + { + continue; + } + + // Also ignore advanced properties + if ( Property->HasAnyPropertyFlags(CPF_AdvancedDisplay | CPF_EditorOnly) ) + { + continue; + } + + Pred(Property); + } + } +} + +TSharedRef SPropertyBinding::OnGenerateDelegateMenu() +{ + const bool bInShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder(bInShouldCloseWindowAfterMenuSelection, nullptr, Args.MenuExtender); + + MenuBuilder.BeginSection("BindingActions", LOCTEXT("Bindings", "Bindings")); + + if ( CanRemoveBinding() ) + { + MenuBuilder.AddMenuEntry( + LOCTEXT("RemoveBinding", "Remove Binding"), + LOCTEXT("RemoveBindingToolTip", "Removes the current binding"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Cross"), + FUIAction(FExecuteAction::CreateSP(this, &SPropertyBinding::HandleRemoveBinding)) + ); + } + + if(Args.bAllowNewBindings && Args.BindableSignature != nullptr) + { + MenuBuilder.AddMenuEntry( + LOCTEXT("CreateBinding", "Create Binding"), + LOCTEXT("CreateBindingToolTip", "Creates a new function on the widget blueprint that will return the binding data for this property."), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Plus"), + FUIAction(FExecuteAction::CreateSP(this, &SPropertyBinding::HandleCreateAndAddBinding)) + ); + } + + MenuBuilder.EndSection(); //CreateBinding + + // Properties + { + // Get the current skeleton class, think header for the blueprint. + UBlueprintGeneratedClass* SkeletonClass = Cast(Blueprint->SkeletonGeneratedClass); + + TArray> BindingChain; + FillPropertyMenu(MenuBuilder, SkeletonClass, BindingChain); + } + + FDisplayMetrics DisplayMetrics; + FSlateApplication::Get().GetCachedDisplayMetrics(DisplayMetrics); + + return + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .MaxHeight(DisplayMetrics.PrimaryDisplayHeight * 0.5) + [ + MenuBuilder.MakeWidget() + ]; +} + +void SPropertyBinding::FillPropertyMenu(FMenuBuilder& MenuBuilder, UStruct* InOwnerStruct, TArray> InBindingChain) +{ + auto MakeArrayElementPropertyWidget = [this](FProperty* InProperty, const TArray>& InNewBindingChain) + { + const UEdGraphSchema_K2* Schema = GetDefault(); + + FEdGraphPinType PinType; + Schema->ConvertPropertyToPinType(InProperty, PinType); + + TSharedPtr LeafElement = InNewBindingChain.Last(); + + return SNew(SHorizontalBox) + .ToolTipText(InProperty->GetToolTipText()) + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SSpacer) + .Size(FVector2D(18.0f, 0.0f)) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(1.0f, 0.0f) + [ + SNew(SImage) + .Image(FBlueprintEditorUtils::GetIconFromPin(PinType, true)) + .ColorAndOpacity(Schema->GetPinTypeColor(PinType)) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromName(InProperty->GetFName())) + ] + +SHorizontalBox::Slot() + .FillWidth(1.0f) + .HAlign(HAlign_Right) + .Padding(2.0f, 0.0f) + [ + SNew(SBox) + .MinDesiredWidth(24.0f) + [ + SNew(SNumericEntryBox) + .ToolTipText(LOCTEXT("ArrayIndex", "Array Index")) + .Value_Lambda([LeafElement](){ return LeafElement->ArrayIndex == INDEX_NONE ? TOptional() : TOptional(LeafElement->ArrayIndex); }) + .OnValueCommitted(this, &SPropertyBinding::HandleSetBindingArrayIndex, InProperty, InNewBindingChain) + ] + ]; + }; + + auto MakeArrayElementEntry = [this, &MenuBuilder, &MakeArrayElementPropertyWidget](FProperty* InProperty, const TArray>& InNewBindingChain) + { + MenuBuilder.AddMenuEntry( + FUIAction(FExecuteAction::CreateSP(this, &SPropertyBinding::HandleAddBinding, InNewBindingChain)), + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + MakeArrayElementPropertyWidget(InProperty, InNewBindingChain) + ] + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SSpacer) + .Size(FVector2D(15.0f, 0.0f)) + ]); + }; + + auto MakePropertyWidget = [this](FProperty* InProperty) + { + static FName PropertyIcon(TEXT("Kismet.VariableList.TypeIcon")); + + const UEdGraphSchema_K2* Schema = GetDefault(); + + FEdGraphPinType PinType; + Schema->ConvertPropertyToPinType(InProperty, PinType); + + return SNew(SHorizontalBox) + .ToolTipText(InProperty->GetToolTipText()) + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SSpacer) + .Size(FVector2D(18.0f, 0.0f)) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(1.0f, 0.0f) + [ + SNew(SImage) + .Image(FBlueprintEditorUtils::GetIconFromPin(PinType, true)) + .ColorAndOpacity(Schema->GetPinTypeColor(PinType)) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromName(InProperty->GetFName())) + ]; + }; + + auto MakePropertyEntry = [this, &MenuBuilder, &MakePropertyWidget](FProperty* InProperty, const TArray>& InNewBindingChain) + { + MenuBuilder.AddMenuEntry( + FUIAction(FExecuteAction::CreateSP(this, &SPropertyBinding::HandleAddBinding, InNewBindingChain)), + MakePropertyWidget(InProperty)); + }; + + auto MakeFunctionWidget = [this](const FFunctionInfo& Info) + { + static FName FunctionIcon(TEXT("GraphEditor.Function_16x")); + + const UEdGraphSchema_K2* Schema = GetDefault(); + + FEdGraphPinType PinType; + if(FProperty* ReturnProperty = Info.Function->GetReturnProperty()) + { + Schema->ConvertPropertyToPinType(ReturnProperty, PinType); + } + + return SNew(SHorizontalBox) + .ToolTipText(FText::FromString(Info.Tooltip)) + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SSpacer) + .Size(FVector2D(18.0f, 0.0f)) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(1.0f, 0.0f) + [ + SNew(SImage) + .Image(FEditorStyle::Get().GetBrush(FunctionIcon)) + .ColorAndOpacity(Schema->GetPinTypeColor(PinType)) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4.0f, 0.0f) + [ + SNew(STextBlock) + .Text(Info.DisplayName) + ]; + }; + + //--------------------------------------- + // Function Bindings + + if ( UClass* OwnerClass = Cast(InOwnerStruct) ) + { + static FName FunctionIcon(TEXT("GraphEditor.Function_16x")); + + MenuBuilder.BeginSection("Functions", LOCTEXT("Functions", "Functions")); + { + ForEachBindableFunction(OwnerClass, [this, &InBindingChain, &MenuBuilder, &MakeFunctionWidget] (const FFunctionInfo& Info) + { + TArray> NewBindingChain(InBindingChain); + NewBindingChain.Emplace(MakeShared(Info.Function)); + + FProperty* ReturnProperty = Info.Function->GetReturnProperty(); + // We can get here if we accept non-leaf UObject functions, so if so we need to check the return value for compatibility + if(!Args.bAllowUObjectFunctions || Args.OnCanBindProperty.Execute(ReturnProperty)) + { + MenuBuilder.AddMenuEntry( + FUIAction(FExecuteAction::CreateSP(this, &SPropertyBinding::HandleAddBinding, NewBindingChain)), + MakeFunctionWidget(Info)); + } + + // Only show bindable subobjects and variables if we're generating pure bindings. + if(Args.bGeneratePureBindings) + { + if(FObjectPropertyBase* ObjectPropertyBase = CastField(ReturnProperty)) + { + MenuBuilder.AddSubMenu( + MakeFunctionWidget(Info), + FNewMenuDelegate::CreateSP(this, &SPropertyBinding::FillPropertyMenu, static_cast(ObjectPropertyBase->PropertyClass), NewBindingChain)); + } + } + }); + } + MenuBuilder.EndSection(); //Functions + } + + //--------------------------------------- + // Property Bindings + + // Get the current skeleton class, think header for the blueprint. + UBlueprintGeneratedClass* SkeletonClass = Cast(Blueprint->SkeletonGeneratedClass); + + // Only show bindable subobjects and variables if we're generating pure bindings. + if ( Args.bGeneratePureBindings ) + { + FProperty* BindingProperty = nullptr; + if(Args.BindableSignature != nullptr) + { + BindingProperty = Args.BindableSignature->GetReturnProperty(); + } + else + { + BindingProperty = Args.Property; + } + + // Find the binder that can handle the delegate return type, don't bother allowing people + // to look for bindings that we don't support + if ( Args.OnCanBindProperty.IsBound() && Args.OnCanBindProperty.Execute(BindingProperty) ) + { + MenuBuilder.BeginSection("Properties", LOCTEXT("Properties", "Properties")); + { + ForEachBindableProperty(InOwnerStruct, [this, &InBindingChain, &MenuBuilder, &MakeArrayElementPropertyWidget, &MakePropertyWidget, &MakePropertyEntry, &MakeArrayElementEntry] (FProperty* Property) + { + TArray> NewBindingChain(InBindingChain); + NewBindingChain.Emplace(MakeShared(Property)); + + if(Args.OnCanBindProperty.Execute(Property)) + { + MakePropertyEntry(Property, NewBindingChain); + } + + FArrayProperty* ArrayProperty = CastField(Property); + + if(Args.bAllowArrayElementBindings && ArrayProperty != nullptr && Args.OnCanBindProperty.Execute(ArrayProperty->Inner)) + { + TArray> NewArrayElementBindingChain(InBindingChain); + NewArrayElementBindingChain.Emplace(MakeShared(Property)); + NewArrayElementBindingChain.Last()->ArrayIndex = 0; + + MakeArrayElementEntry(ArrayProperty->Inner, NewArrayElementBindingChain); + } + + FProperty* InnerProperty = Property; + if(Args.bAllowArrayElementBindings && ArrayProperty != nullptr) + { + InnerProperty = ArrayProperty->Inner; + } + + FObjectPropertyBase* ObjectProperty = CastField(InnerProperty); + FStructProperty* StructProperty = CastField(InnerProperty); + + UStruct* Struct = nullptr; + UClass* Class = nullptr; + + if ( ObjectProperty ) + { + Struct = Class = ObjectProperty->PropertyClass; + } + else if ( StructProperty ) + { + Struct = StructProperty->Struct; + } + + if ( Struct ) + { + if ( Class ) + { + // Ignore any subobject properties that are not bindable. + // Also ignore any class that is explicitly on the black list. + if ( IsClassBlackListed(Class) || (Args.OnCanBindToSubObjectClass.IsBound() && Args.OnCanBindToSubObjectClass.Execute(Class))) + { + return; + } + } + + if(Args.bAllowArrayElementBindings && ArrayProperty != nullptr) + { + TArray> NewArrayElementBindingChain(InBindingChain); + NewArrayElementBindingChain.Emplace(MakeShared(Property)); + NewArrayElementBindingChain.Last()->ArrayIndex = 0; + + MenuBuilder.AddSubMenu( + MakeArrayElementPropertyWidget(ArrayProperty->Inner, NewArrayElementBindingChain), + FNewMenuDelegate::CreateSP(this, &SPropertyBinding::FillPropertyMenu, Struct, NewArrayElementBindingChain) + ); + } + else + { + MenuBuilder.AddSubMenu( + MakePropertyWidget(Property), + FNewMenuDelegate::CreateSP(this, &SPropertyBinding::FillPropertyMenu, Struct, NewBindingChain)); + } + } + }); + } + MenuBuilder.EndSection(); //Properties + } + } + + // Add 'none' entry only if we just have the search block in the builder + if ( MenuBuilder.GetMultiBox()->GetBlocks().Num() == 1 ) + { + MenuBuilder.BeginSection("None", InOwnerStruct->GetDisplayNameText()); + MenuBuilder.AddWidget(SNew(STextBlock).Text(LOCTEXT("None", "None")), FText::GetEmpty()); + MenuBuilder.EndSection(); //None + } +} + +const FSlateBrush* SPropertyBinding::GetCurrentBindingImage() const +{ + if(Args.CurrentBindingImage.IsSet()) + { + return Args.CurrentBindingImage.Get(); + } + + return nullptr; +} + +FText SPropertyBinding::GetCurrentBindingText() const +{ + if(Args.CurrentBindingText.IsSet()) + { + return Args.CurrentBindingText.Get(); + } + + return LOCTEXT("Bind", "Bind"); +} + +FSlateColor SPropertyBinding::GetCurrentBindingColor() const +{ + if(Args.CurrentBindingColor.IsSet()) + { + return Args.CurrentBindingColor.Get(); + } + + return FLinearColor(0.25f, 0.25f, 0.25f); +} + +bool SPropertyBinding::CanRemoveBinding() +{ + if(Args.OnCanRemoveBinding.IsBound()) + { + return Args.OnCanRemoveBinding.Execute(PropertyName); + } + + return false; +} + +void SPropertyBinding::HandleRemoveBinding() +{ + if(Args.OnRemoveBinding.IsBound()) + { + const FScopedTransaction Transaction(LOCTEXT("UnbindDelegate", "Remove Binding")); + + Args.OnRemoveBinding.Execute(PropertyName); + } +} + +void SPropertyBinding::HandleAddBinding(TArray> InBindingChain) +{ + if(Args.OnAddBinding.IsBound()) + { + const FScopedTransaction Transaction(LOCTEXT("BindDelegate", "Set Binding")); + + TArray BindingChain; + Algo::Transform(InBindingChain, BindingChain, [](TSharedPtr InElement) + { + return *InElement.Get(); + }); + Args.OnAddBinding.Execute(PropertyName, BindingChain); + } +} + +void SPropertyBinding::HandleSetBindingArrayIndex(int32 InArrayIndex, ETextCommit::Type InCommitType, FProperty* InProperty, TArray> InBindingChain) +{ + InBindingChain.Last()->ArrayIndex = InArrayIndex; + + // If the user hit enter on a compatible property, assume they want to accept + if(Args.OnCanBindProperty.Execute(InProperty) && InCommitType == ETextCommit::OnEnter) + { + HandleAddBinding(InBindingChain); + } +} + +void SPropertyBinding::HandleCreateAndAddBinding() +{ + const FScopedTransaction Transaction(LOCTEXT("CreateDelegate", "Create Binding")); + + Blueprint->Modify(); + + FString Pre = Args.bGeneratePureBindings ? FString(TEXT("Get")) : FString(TEXT("On")); + + FString WidgetName; + if(Args.OnGenerateBindingName.IsBound()) + { + WidgetName = Args.OnGenerateBindingName.Execute(); + } + + FString Post = PropertyName != NAME_None ? PropertyName.ToString() : TEXT(""); + Post.RemoveFromStart(TEXT("On")); + Post.RemoveFromEnd(TEXT("Event")); + + // Create the function graph. + FString FunctionName = Pre + WidgetName + Post; + UEdGraph* FunctionGraph = FBlueprintEditorUtils::CreateNewGraph( + Blueprint, + FBlueprintEditorUtils::FindUniqueKismetName(Blueprint, FunctionName), + UEdGraph::StaticClass(), + UEdGraphSchema_K2::StaticClass()); + + const bool bUserCreated = true; + FBlueprintEditorUtils::AddFunctionGraph(Blueprint, FunctionGraph, bUserCreated, Args.BindableSignature); + + UFunction* Function = Blueprint->SkeletonGeneratedClass->FindFunctionByName(FunctionGraph->GetFName()); + check(Function); + + // Add the binding to the blueprint + TArray> BindingPath; + BindingPath.Emplace(MakeShared(Function)); + + HandleAddBinding(BindingPath); + + // Only mark bindings as pure that need to be. + if ( Args.bGeneratePureBindings ) + { + const UEdGraphSchema_K2* Schema_K2 = Cast(FunctionGraph->GetSchema()); + Schema_K2->AddExtraFunctionFlags(FunctionGraph, FUNC_BlueprintPure); + } + + HandleGotoBindingClicked(); +} + +EVisibility SPropertyBinding::GetGotoBindingVisibility() const +{ + if(Args.OnCanGotoBinding.IsBound()) + { + if(Args.OnCanGotoBinding.Execute(PropertyName)) + { + return EVisibility::Visible; + } + } + + return EVisibility::Collapsed; +} + +FReply SPropertyBinding::HandleGotoBindingClicked() +{ + if(Args.OnGotoBinding.IsBound()) + { + if(Args.OnGotoBinding.Execute(PropertyName)) + { + return FReply::Handled(); + } + } + + return FReply::Unhandled(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/SPropertyBinding.h b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/SPropertyBinding.h new file mode 100644 index 000000000000..bb66ba467c58 --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/Private/SPropertyBinding.h @@ -0,0 +1,85 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Layout/Visibility.h" +#include "Input/Reply.h" +#include "Widgets/SWidget.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "PropertyHandle.h" +#include "IPropertyAccessEditor.h" + +class FMenuBuilder; +class UEdGraph; +class UBlueprint; +struct FEditorPropertyPath; + +class SPropertyBinding : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SPropertyBinding){} + + SLATE_ARGUMENT(FPropertyBindingWidgetArgs, Args) + + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs, UBlueprint* InBlueprint); + +protected: + struct FFunctionInfo + { + FFunctionInfo() + : Function(nullptr) + { + } + + FFunctionInfo(UFunction* InFunction) + : DisplayName(FText::FromName(InFunction->GetFName())) + , Tooltip(InFunction->GetMetaData("Tooltip")) + , FuncName(InFunction->GetFName()) + , Function(InFunction) + {} + + FText DisplayName; + FString Tooltip; + + FName FuncName; + UFunction* Function; + }; + + TSharedRef OnGenerateDelegateMenu(); + void FillPropertyMenu(FMenuBuilder& MenuBuilder, UStruct* InOwnerStruct, TArray> InBindingChain); + + const FSlateBrush* GetCurrentBindingImage() const; + FText GetCurrentBindingText() const; + FSlateColor GetCurrentBindingColor() const; + + bool CanRemoveBinding(); + void HandleRemoveBinding(); + + void HandleAddBinding(TArray> InBindingChain); + void HandleSetBindingArrayIndex(int32 InArrayIndex, ETextCommit::Type InCommitType, FProperty* InProperty, TArray> InBindingChain); + + void HandleCreateAndAddBinding(); + + EVisibility GetGotoBindingVisibility() const; + + FReply HandleGotoBindingClicked(); + +private: + bool IsClassBlackListed(UClass* OwnerClass) const; + bool IsFieldFromBlackListedClass(FFieldVariant Field) const; + + template + void ForEachBindableProperty(UStruct* InStruct, Predicate Pred) const; + + template + void ForEachBindableFunction(UClass* FromClass, Predicate Pred) const; + + UBlueprint* Blueprint; + FPropertyBindingWidgetArgs Args; + FName PropertyName; +}; diff --git a/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/PropertyAccessEditor.Build.cs b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/PropertyAccessEditor.Build.cs new file mode 100644 index 000000000000..d79c68fa1d49 --- /dev/null +++ b/Engine/Plugins/Runtime/PropertyAccess/Source/PropertyAccessEditor/PropertyAccessEditor.Build.cs @@ -0,0 +1,35 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class PropertyAccessEditor : ModuleRules +{ + public PropertyAccessEditor(ReadOnlyTargetRules Target) : base(Target) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Slate", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "PropertyAccess", + "GraphEditor", + "BlueprintGraph", + "EditorStyle", + "Engine", + "UnrealEd", + "SlateCore", + "InputCore", + "KismetWidgets", + "PropertyPath", + "AnimGraph", + } + ); + } +} diff --git a/Engine/Plugins/Runtime/ReplicationGraph/Source/Private/ReplicationGraph.cpp b/Engine/Plugins/Runtime/ReplicationGraph/Source/Private/ReplicationGraph.cpp index 91c8e216685f..c0c03ac019b1 100644 --- a/Engine/Plugins/Runtime/ReplicationGraph/Source/Private/ReplicationGraph.cpp +++ b/Engine/Plugins/Runtime/ReplicationGraph/Source/Private/ReplicationGraph.cpp @@ -47,8 +47,6 @@ #include "Engine/ActorChannel.h" #include "Engine/NetworkObjectList.h" #include "Net/RepLayout.h" -#include "GameFramework/SpectatorPawn.h" -#include "GameFramework/SpectatorPawnMovement.h" #include "Net/UnrealNetwork.h" #include "Net/NetworkProfiler.h" #include "HAL/LowLevelMemTracker.h" @@ -422,7 +420,11 @@ UNetReplicationGraphConnection* UReplicationGraph::CreateClientConnectionManager UNetReplicationGraphConnection* NewConnectionManager = NewObject(this, ReplicationConnectionManagerClass.Get()); // Give it an ID - NewConnectionManager->ConnectionId = Connections.Num() + PendingConnections.Num(); + const int32 NewConnectionNum = Connections.Num() + PendingConnections.Num(); + NewConnectionManager->ConnectionOrderNum = NewConnectionNum; + PRAGMA_DISABLE_DEPRECATION_WARNINGS + NewConnectionManager->ConnectionId = NewConnectionNum; + PRAGMA_ENABLE_DEPRECATION_WARNINGS // Initialize it with us NewConnectionManager->InitForGraph(this); @@ -436,11 +438,40 @@ UNetReplicationGraphConnection* UReplicationGraph::CreateClientConnectionManager return NewConnectionManager; } +UNetReplicationGraphConnection* UReplicationGraph::FixGraphConnectionList(TArray& OutList, int32& ConnectionNum, UNetConnection* RemovedNetConnection) +{ + UNetReplicationGraphConnection* RemovedGraphConnection(nullptr); + + for (int32 Index = 0; Index < OutList.Num(); ++Index) + { + UNetReplicationGraphConnection* CurrentGraphConnection = OutList[Index]; + if (CurrentGraphConnection->NetConnection != RemovedNetConnection) + { + // Fix the ConnectionOrderNum + const int32 NewConnectionNum = ConnectionNum++; + CurrentGraphConnection->ConnectionOrderNum = NewConnectionNum; + PRAGMA_DISABLE_DEPRECATION_WARNINGS + CurrentGraphConnection->ConnectionId = NewConnectionNum; + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } + else + { + // Found the connection to remove + ensureMsgf(RemovedGraphConnection==nullptr, TEXT("Found multiple GraphConnections for the same NetConnection: %s. PreviousGraphConnection(%i): %s | CurrentGraphConnection(%i): %s"), + *RemovedNetConnection->Describe(), + RemovedGraphConnection->ConnectionOrderNum, *RemovedGraphConnection->GetName(), + CurrentGraphConnection->ConnectionOrderNum, *CurrentGraphConnection->GetName()); + RemovedGraphConnection = CurrentGraphConnection; + + OutList.RemoveAtSwap(Index--, 1, false); + } + } + + return RemovedGraphConnection; +} + void UReplicationGraph::RemoveClientConnection(UNetConnection* NetConnection) { - int32 ConnectionId = 0; - bool bFound = false; - // Children do not have a connection manager, do not attempt to remove it here. // Default behavior never calls this function with child connections anyways, so this is really only here for protection. if (NetConnection->GetUChildConnection() != nullptr) @@ -449,32 +480,24 @@ void UReplicationGraph::RemoveClientConnection(UNetConnection* NetConnection) return; } - // Remove the RepGraphConnection associated with this NetConnection. Also update ConnectionIds to stay compact. - auto UpdateList = [&](TArray List) + int32 ConnectionNum = 0; + + UNetReplicationGraphConnection* ActiveGraphConnectionRemoved = FixGraphConnectionList(Connections, ConnectionNum, NetConnection); + UNetReplicationGraphConnection* PendingGraphConnectionRemoved = FixGraphConnectionList(PendingConnections, ConnectionNum, NetConnection); + + if (ActiveGraphConnectionRemoved) { - for (int32 idx=0; idx < Connections.Num(); ++idx) - { - UNetReplicationGraphConnection* ConnectionManager = Connections[idx]; - repCheck(ConnectionManager); + ActiveGraphConnectionRemoved->TearDown(); + ensure(PendingGraphConnectionRemoved == nullptr); + } - if (ConnectionManager->NetConnection == NetConnection) - { - ensure(!bFound); - ConnectionManager->TearDown(); - Connections.RemoveAtSwap(idx, 1, false); - bFound = true; - } - else - { - ConnectionManager->ConnectionId = ConnectionId++; - } - } - }; + if (PendingGraphConnectionRemoved) + { + PendingGraphConnectionRemoved->TearDown(); + ensure(ActiveGraphConnectionRemoved == nullptr); + } - UpdateList(Connections); - UpdateList(PendingConnections); - - if (!bFound) + if (!ActiveGraphConnectionRemoved && !PendingGraphConnectionRemoved) { // At least one list should have found the connection UE_LOG(LogReplicationGraph, Warning, TEXT("UReplicationGraph::RemoveClientConnection could not find connection in Connection (%d) or PendingConnections (%d) lists"), *GetNameSafe(NetConnection), Connections.Num(), PendingConnections.Num()); @@ -741,8 +764,8 @@ void UReplicationGraph::NotifyActorDormancyChange(AActor* Actor, ENetDormancy Ol { QUICK_SCOPE_CYCLE_COUNTER(UReplicationGraph_NotifyActorDormancyChange); - FGlobalActorReplicationInfo* GlobalInfo = GlobalActorReplicationInfoMap.Find(Actor); - if (!GlobalInfo) + FGlobalActorReplicationInfo* ActorRepInfo = GlobalActorReplicationInfoMap.Find(Actor); + if (!ActorRepInfo) { UE_CLOG(CVar_RepGraph_LogNetDormancyDetails > 0, LogReplicationGraph, Display, TEXT("UReplicationGraph::NotifyActorDormancyChange %s. Ignoring change since actor is not registered yet."), *Actor->GetPathName()); return; @@ -756,13 +779,13 @@ void UReplicationGraph::NotifyActorDormancyChange(AActor* Actor, ENetDormancy Ol ENetDormancy CurrentDormancy = Actor->NetDormancy; - UE_CLOG(CVar_RepGraph_LogNetDormancyDetails > 0, LogReplicationGraph, Display, TEXT("UReplicationGraph::NotifyActorDormancyChange %s. Old WantsToBeDormant: %d. New WantsToBeDormant: %d"), *Actor->GetPathName(), GlobalInfo->bWantsToBeDormant, CurrentDormancy > DORM_Awake ? 1 : 0); + UE_CLOG(CVar_RepGraph_LogNetDormancyDetails > 0, LogReplicationGraph, Display, TEXT("UReplicationGraph::NotifyActorDormancyChange %s. Old WantsToBeDormant: %d. New WantsToBeDormant: %d"), *Actor->GetPathName(), ActorRepInfo->bWantsToBeDormant, CurrentDormancy > DORM_Awake ? 1 : 0); const bool bOldWantsToBeDormant = OldDormancyState > DORM_Awake; const bool bNewWantsToBeDormant = CurrentDormancy > DORM_Awake; - GlobalInfo->bWantsToBeDormant = bNewWantsToBeDormant; - GlobalInfo->Events.DormancyChange.Broadcast(Actor, *GlobalInfo, CurrentDormancy, OldDormancyState); + ActorRepInfo->bWantsToBeDormant = bNewWantsToBeDormant; + ActorRepInfo->Events.DormancyChange.Broadcast(Actor, *ActorRepInfo, CurrentDormancy, OldDormancyState); // Is the actor coming out of dormancy via changing its dormancy state? if (!bNewWantsToBeDormant && bOldWantsToBeDormant) @@ -895,8 +918,18 @@ int32 UReplicationGraph::ServerReplicateActors(float DeltaSeconds) FPerConnectionActorInfoMap& ConnectionActorInfoMap = ConnectionManager->ActorInfoMap; repCheckf(NetConnection->GetReplicationConnectionDriver() == ConnectionManager, TEXT("NetConnection %s mismatch rep driver. %s vs %s"), *GetNameSafe(NetConnection), *GetNameSafe(NetConnection->GetReplicationConnectionDriver()), *GetNameSafe(ConnectionManager)); - - new(ConnectionViewers) FNetViewer(NetConnection, 0.f); + + const bool bReplayConnection = NetConnection->IsReplay(); + + if (bReplayConnection && !NetConnection->IsReplayReady()) + { + // replay isn't ready to record right now + continue; + } + + CSV_SCOPED_TIMING_STAT_EXCLUSIVE_CONDITIONAL(ReplayNetConnection, bReplayConnection); + + ConnectionViewers.Emplace(NetConnection, 0.f); // Send ClientAdjustments (movement RPCs) do this first and never let bandwidth saturation suppress these. if (PC) @@ -911,12 +944,27 @@ int32 UReplicationGraph::ServerReplicateActors(float DeltaSeconds) if (ChildConnection && ChildConnection->PlayerController && ChildConnection->ViewTarget) { ChildConnection->PlayerController->SendClientAdjustment(); - new(ConnectionViewers) FNetViewer(ChildConnection, 0.f); + + ConnectionViewers.Emplace(ChildConnection, 0.f); } } NumChildrenConnectionsProcessed += NetConnection->Children.Num(); + // treat all other non-replay connections as viewers + if (bReplayConnection) + { + for (UNetReplicationGraphConnection* RepGraphConn : Connections) + { + if ((RepGraphConn->NetConnection != NetConnection) && !RepGraphConn->NetConnection->IsReplay() && RepGraphConn->PrepareForReplication()) + { + ConnectionViewers.Emplace(RepGraphConn->NetConnection, 0.f); + } + } + + AddReplayViewers(NetConnection, ConnectionViewers); + } + ON_SCOPE_EXIT { NetConnection->TrackReplicationForAnalytics(bWasConnectionSaturated); @@ -952,11 +1000,6 @@ int32 UReplicationGraph::ServerReplicateActors(float DeltaSeconds) ConnectionManager->UpdateGatherLocationsForConnection(ConnectionViewers, DestructionSettings); - // Do this so we don't break anyone. - PRAGMA_DISABLE_DEPRECATION_WARNINGS - Parameters.ConnectionManager.LastGatherLocation = Parameters.Viewer.ViewLocation; - PRAGMA_ENABLE_DEPRECATION_WARNINGS - if (GatheredReplicationListsForConnection.NumLists() == 0) { // No lists were returned, kind of weird but not fatal. Early out because code below assumes at least 1 list @@ -1131,7 +1174,6 @@ void UReplicationGraph::ReplicateActorListsForConnections_Default(UNetReplicatio const float MaxDistanceScaling = PrioritizationConstants.MaxDistanceScaling; const uint32 MaxFramesSinceLastRep = PrioritizationConstants.MaxFramesSinceLastRep; - const int32 TotalNumOfConnections = 1 + NetConnection->Children.Num(); for (FActorRepListRawView& List : GatheredReplicationListsForConnection.GetLists(EActorRepListTypeFlags::Default)) { @@ -1195,7 +1237,7 @@ void UReplicationGraph::ReplicateActorListsForConnections_Default(UNetReplicatio if (GlobalData.Settings.DistancePriorityScale > 0.f) { float SmallestDistanceSq = TNumericLimits::Max(); - int32 ConnectionsThatSkipActor = 0; + int32 ViewersThatSkipActor = 0; for (const FNetViewer& CurViewer : Viewers) { @@ -1205,13 +1247,13 @@ void UReplicationGraph::ReplicateActorListsForConnections_Default(UNetReplicatio // Figure out if we should be skipping this actor if (bDoDistanceCull && ConnectionData.GetCullDistanceSquared() > 0.f && DistSq > ConnectionData.GetCullDistanceSquared()) { - ++ConnectionsThatSkipActor; + ++ViewersThatSkipActor; continue; } } // If no one is near this actor, skip it. - if (ConnectionsThatSkipActor >= TotalNumOfConnections) + if (ViewersThatSkipActor >= Viewers.Num()) { DO_REPGRAPH_DETAILS(PrioritizedReplicationList.GetNextSkippedDebugDetails(Actor)->DistanceCulled = FMath::Sqrt(SmallestDistanceSq)); @@ -1371,22 +1413,6 @@ void UReplicationGraph::ReplicateActorListsForConnections_Default(UNetReplicatio } } -PRAGMA_DISABLE_DEPRECATION_WARNINGS -void UReplicationGraph::ReplicateActorListsForConnection_Default(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists& GatheredReplicationListsForConnection, FNetViewer& Viewer) -{ - FNetViewerArray ViewersToConsider; - ViewersToConsider.Add(Viewer); - - // Create viewers for all the children related to this connection - for (int32 ChildIdx = 0; ChildIdx < Viewer.Connection->Children.Num(); ++ChildIdx) - { - new(ViewersToConsider)FNetViewer(Viewer.Connection->Children[ChildIdx], 0); - } - - ReplicateActorListsForConnections_Default(ConnectionManager, GatheredReplicationListsForConnection, ViewersToConsider); -} -PRAGMA_ENABLE_DEPRECATION_WARNINGS - struct FScopedQueuedBits { FScopedQueuedBits(int32& InQueuedBits, int32& InTotalBits) : QueuedBits(InQueuedBits), TotalBits(InTotalBits) { } @@ -1554,23 +1580,6 @@ void UReplicationGraph::ReplicateActorListsForConnections_FastShared(UNetReplica } } -PRAGMA_DISABLE_DEPRECATION_WARNINGS -void UReplicationGraph::ReplicateActorListsForConnection_FastShared(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists& GatheredReplicationListsForConnection, FNetViewer& Viewer) -{ - FNetViewerArray ViewersToConsider; - ViewersToConsider.Add(Viewer); - - // Create viewers for all the children related to this connection - for (int32 ChildIdx = 0; ChildIdx < Viewer.Connection->Children.Num(); ++ChildIdx) - { - new(ViewersToConsider)FNetViewer(Viewer.Connection->Children[ChildIdx], 0); - } - - ReplicateActorListsForConnections_FastShared(ConnectionManager, GatheredReplicationListsForConnection, ViewersToConsider); -} -PRAGMA_ENABLE_DEPRECATION_WARNINGS - - REPGRAPH_DEVCVAR_SHIPCONST(int32, "Net.RepGraph.FastShared.ForceFull", CVar_RepGraph_FastShared_ForceFull, 0, "Redirects calls to ReplicateSingleActor_FastShared to ReplicateSingleActor"); int64 UReplicationGraph::ReplicateSingleActor_FastShared(AActor* Actor, FConnectionReplicationActorInfo& ConnectionData, FGlobalActorReplicationInfo& GlobalActorInfo, UNetReplicationGraphConnection& ConnectionManager, const uint32 FrameNum) @@ -1585,16 +1594,17 @@ int64 UReplicationGraph::ReplicateSingleActor_FastShared(AActor* Actor, FConnect ConnectionData.FastPath_LastRepFrameNum = FrameNum; ConnectionData.FastPath_NextReplicationFrameNum = FrameNum + ConnectionData.FastPath_ReplicationPeriodFrame; }; - + TOptional SwapGuard; if (GlobalActorInfo.bSwapRolesOnReplicate) { SwapGuard = FScopedActorRoleSwap(Actor); } - if (CVar_RepGraph_FastShared_ForceFull > 0) + // always replicate full pawns to the replay + if (NetConnection->IsReplay() || CVar_RepGraph_FastShared_ForceFull > 0) { - return ReplicateSingleActor(Actor, ConnectionData, GlobalActorInfo, FindOrAddConnectionManager(NetConnection)->ActorInfoMap, ConnectionManager, FrameNum); + return ReplicateSingleActor(Actor, ConnectionData, GlobalActorInfo, ConnectionManager.ActorInfoMap, ConnectionManager, FrameNum); } int32 BitsWritten = 0; @@ -2056,13 +2066,13 @@ bool UReplicationGraph::ProcessRemoteFunction(class AActor* Actor, UFunction* Fu } FNetViewerArray ViewsToConsider; - new(ViewsToConsider)FNetViewer(NetConnection, 0.f); + ViewsToConsider.Emplace(NetConnection, 0.f); for (int32 ChildIdx = 0; ChildIdx < NetConnection->Children.Num(); ++ChildIdx) { if (NetConnection->Children[ChildIdx]->ViewTarget != nullptr) { - new(ViewsToConsider)FNetViewer(NetConnection->Children[ChildIdx], 0.f); + ViewsToConsider.Emplace(NetConnection->Children[ChildIdx], 0.f); } } @@ -2457,8 +2467,6 @@ void UNetReplicationGraphConnection::NotifyAddDestructionInfo(FActorDestructionI return; } - - #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // Should not be happening but lets check in non shipping builds. int32 ExistingIdx = PendingDestructInfoList.IndexOfByKey(DestructInfo); @@ -2580,7 +2588,6 @@ void UNetReplicationGraphConnection::NotifyClientVisibleLevelNamesAdd(FName Leve { MapDelegate->Broadcast(LevelName, StreamingWorld); } - } int64 UNetReplicationGraphConnection::ReplicateDestructionInfos(const FNetViewerArray& Viewers, const float DestructInfoMaxDistanceSquared) @@ -2662,15 +2669,18 @@ void UNetReplicationGraphConnection::UpdateGatherLocationsForConnection(const FN { for (const FNetViewer& CurViewer : ConnectionViewers) { - FLastLocationGatherInfo* LastInfoForViewer = LastGatherLocations.FindByKey(CurViewer.Connection); - if (LastInfoForViewer != nullptr) + if (CurViewer.Connection != nullptr) { - OnUpdateViewerLocation(LastInfoForViewer, CurViewer, DestructionSettings); - } - else - { - // We need to add this viewer to the last gather locations - LastGatherLocations.Add(FLastLocationGatherInfo(CurViewer.Connection, CurViewer.ViewLocation)); + FLastLocationGatherInfo* LastInfoForViewer = LastGatherLocations.FindByKey(CurViewer.Connection); + if (LastInfoForViewer != nullptr) + { + OnUpdateViewerLocation(LastInfoForViewer, CurViewer, DestructionSettings); + } + else + { + // We need to add this viewer to the last gather locations + LastGatherLocations.Emplace(CurViewer.Connection, CurViewer.ViewLocation); + } } } @@ -2785,13 +2795,14 @@ void FStreamingLevelActorListCollection::AddActor(const FNewReplicatedActorInfo& FStreamingLevelActors* Item = StreamingLevelLists.FindByKey(ActorInfo.StreamingLevelName); if (!Item) { - Item = new (StreamingLevelLists) FStreamingLevelActors(ActorInfo.StreamingLevelName); + Item = &StreamingLevelLists.Emplace_GetRef(ActorInfo.StreamingLevelName); } if (CVar_RepGraph_Verify) { ensureMsgf(Item->ReplicationActorList.Contains(ActorInfo.Actor) == false, TEXT("%s being added to %s twice! Streaming level: %s"), *GetActorRepListTypeDebugString(ActorInfo.Actor), *ActorInfo.StreamingLevelName.ToString() ); } + Item->ReplicationActorList.Add(ActorInfo.Actor); } @@ -2818,6 +2829,20 @@ bool FStreamingLevelActorListCollection::RemoveActor(const FNewReplicatedActorIn return bRemovedSomething; } +bool FStreamingLevelActorListCollection::RemoveActorFast(const FNewReplicatedActorInfo& ActorInfo, UReplicationGraphNode* Outer) +{ + bool bRemovedSomething = false; + for (FStreamingLevelActors& StreamingList : StreamingLevelLists) + { + if (StreamingList.StreamingLevelName == ActorInfo.StreamingLevelName) + { + bRemovedSomething = StreamingList.ReplicationActorList.RemoveFast(ActorInfo.Actor); + break; + } + } + return bRemovedSomething; +} + void FStreamingLevelActorListCollection::Reset() { for (FStreamingLevelActors& StreamingList : StreamingLevelLists) @@ -2848,9 +2873,9 @@ void FStreamingLevelActorListCollection::DeepCopyFrom(const FStreamingLevelActor { if (StreamingLevel.ReplicationActorList.Num() > 0) { - FStreamingLevelActors* NewStreamingLevel = new (StreamingLevelLists)FStreamingLevelActors(StreamingLevel.StreamingLevelName); - NewStreamingLevel->ReplicationActorList.CopyContentsFrom(StreamingLevel.ReplicationActorList); - ensure(NewStreamingLevel->ReplicationActorList.Num() == StreamingLevel.ReplicationActorList.Num()); + FStreamingLevelActors& NewStreamingLevel = StreamingLevelLists.Emplace_GetRef(StreamingLevel.StreamingLevelName); + NewStreamingLevel.ReplicationActorList.CopyContentsFrom(StreamingLevel.ReplicationActorList); + ensure(NewStreamingLevel.ReplicationActorList.Num() == StreamingLevel.ReplicationActorList.Num()); } } } @@ -2916,6 +2941,19 @@ bool UReplicationGraphNode_ActorList::NotifyRemoveNetworkActor(const FNewReplica return bRemovedSomething; } + +/** Removes the actor very quickly but breaks the list order */ +bool UReplicationGraphNode_ActorList::RemoveNetworkActorFast(const FNewReplicatedActorInfo& ActorInfo) +{ + if (ActorInfo.StreamingLevelName == NAME_None) + { + return ReplicationActorList.RemoveFast(ActorInfo.Actor); + } + else + { + return StreamingLevelCollection.RemoveActorFast(ActorInfo, this); + } +} void UReplicationGraphNode_ActorList::NotifyResetAllNetworkActors() { @@ -3290,7 +3328,7 @@ void UReplicationGraphNode_DynamicSpatialFrequency::GatherActorListsForConnectio // -------------------------------------------------------------------------------------------------------- // Two passes: filter list down to MaxNearestActors actors based on distance. Then calc freq and resort // -------------------------------------------------------------------------------------------------------- - if (MaxNearestActors >= 0) + if (MaxNearestActors >= 0 && !NetConnection->IsReplay()) { int32 PossibleNumActors = ReplicationActorList.Num();; @@ -3662,47 +3700,33 @@ void UReplicationGraphNode_DynamicSpatialFrequency::CalcFrequencyForActor(AActor RemoveExistingItem(); } -PRAGMA_DISABLE_DEPRECATION_WARNINGS -FORCEINLINE void UReplicationGraphNode_DynamicSpatialFrequency::CalcFrequencyForActor(AActor* Actor, UReplicationGraph* RepGraph, UNetConnection* NetConnection, FGlobalActorReplicationInfo& GlobalInfo, FConnectionReplicationActorInfo& ConnectionInfo, FSettings& MySettings, const FVector& ConnectionViewLocation, const FVector& ConnectionViewDir, const uint32 FrameNum, int32 ExistingItemIndex) -{ - FNetViewerArray ViewersArray; - FNetViewer SoloViewer(NetConnection, 0.f); - SoloViewer.ViewLocation = ConnectionViewLocation; - SoloViewer.ViewDir = ConnectionViewDir; - ViewersArray.Add(SoloViewer); - - // Add the child viewers - for (UNetConnection* Child : NetConnection->Children) - { - new(ViewersArray) FNetViewer(Child, 0.f); - } - - CalcFrequencyForActor(Actor, RepGraph, NetConnection, GlobalInfo, ConnectionInfo, MySettings, ViewersArray, FrameNum, ExistingItemIndex); -} -PRAGMA_ENABLE_DEPRECATION_WARNINGS - void UReplicationGraphNode_DynamicSpatialFrequency::GatherActors(const FActorRepListRefView& RepList, FGlobalActorReplicationInfoMap& GlobalMap, FPerConnectionActorInfoMap& ConnectionMap, const FConnectionGatherActorListParameters& Params, UNetConnection* NetConnection) { UReplicationGraph* RepGraph = GraphGlobals->ReplicationGraph; FSettings& MySettings = GetSettings(); const uint32 FrameNum = Params.ReplicationFrameNum; + const bool bReplay = NetConnection->IsReplay(); + for (AActor* Actor : RepList) { - bool bShouldSkipActor = false; - // Don't replicate the connection view target like this. It will be done through a connection specific node - for (const FNetViewer& CurViewer : Params.Viewers) + if (!bReplay) { - if (UNLIKELY(Actor == CurViewer.ViewTarget)) + bool bShouldSkipActor = false; + // Don't replicate the connection view target like this. It will be done through a connection specific node + for (const FNetViewer& CurViewer : Params.Viewers) { - bShouldSkipActor = true; - break; + if (UNLIKELY(Actor == CurViewer.ViewTarget)) + { + bShouldSkipActor = true; + break; + } } - } - if (bShouldSkipActor) - { - continue; + if (bShouldSkipActor) + { + continue; + } } FGlobalActorReplicationInfo& GlobalInfo = GlobalMap.Get(Actor); @@ -3762,7 +3786,7 @@ void UReplicationGraphNode_ConnectionDormancyNode::GatherActorListsForConnection FStreamingLevelActorListCollection::FStreamingLevelActors* RemoveList = RemovedStreamingLevelActorListCollection.StreamingLevelLists.FindByKey(StreamingList.StreamingLevelName); if (!RemoveList) { - RemoveList = new (RemovedStreamingLevelActorListCollection.StreamingLevelLists) FStreamingLevelActorListCollection::FStreamingLevelActors(StreamingList.StreamingLevelName); + RemoveList = &RemovedStreamingLevelActorListCollection.StreamingLevelLists.Emplace_GetRef(StreamingList.StreamingLevelName); Params.ConnectionManager.OnClientVisibleLevelNameAddMap.FindOrAdd(StreamingList.StreamingLevelName).AddUObject(this, &UReplicationGraphNode_ConnectionDormancyNode::OnClientVisibleLevelNameAdd); } @@ -3830,7 +3854,9 @@ bool ContainsReverse(const FActorRepListRefView& List, FActorRepListType Actor) for (int32 idx=List.Num()-1; idx >= 0; --idx) { if (List[idx] == Actor) + { return true; + } } return false; @@ -3838,6 +3864,8 @@ bool ContainsReverse(const FActorRepListRefView& List, FActorRepListType Actor) void UReplicationGraphNode_ConnectionDormancyNode::NotifyActorDormancyFlush(FActorRepListType Actor) { + QUICK_SCOPE_CYCLE_COUNTER(ConnectionDormancyNode_NotifyActorDormancyFlush); + FNewReplicatedActorInfo ActorInfo(Actor); // Dormancy is flushed so we need to make sure this actor is on this connection specific node. @@ -3856,10 +3884,11 @@ void UReplicationGraphNode_ConnectionDormancyNode::NotifyActorDormancyFlush(FAct FStreamingLevelActorListCollection::FStreamingLevelActors* Item = StreamingLevelCollection.StreamingLevelLists.FindByKey(ActorInfo.StreamingLevelName); if (!Item) { - Item = new (StreamingLevelCollection.StreamingLevelLists) FStreamingLevelActorListCollection::FStreamingLevelActors(ActorInfo.StreamingLevelName); + Item = &StreamingLevelCollection.StreamingLevelLists.Emplace_GetRef(ActorInfo.StreamingLevelName); Item->ReplicationActorList.Add(ActorInfo.Actor); - } else if(!ContainsReverse(Item->ReplicationActorList, Actor)) + } + else if(!ContainsReverse(Item->ReplicationActorList, Actor)) { Item->ReplicationActorList.Add(ActorInfo.Actor); } @@ -3869,7 +3898,7 @@ void UReplicationGraphNode_ConnectionDormancyNode::NotifyActorDormancyFlush(FAct if (RemoveList) { RemoveList->ReplicationActorList.PrepareForWrite(); - RemoveList->ReplicationActorList.Remove(Actor); + RemoveList->ReplicationActorList.RemoveFast(Actor); } } } @@ -3886,7 +3915,7 @@ void UReplicationGraphNode_ConnectionDormancyNode::OnClientVisibleLevelNameAdd(F FStreamingLevelActorListCollection::FStreamingLevelActors* AddList = StreamingLevelCollection.StreamingLevelLists.FindByKey(LevelName); if (!AddList) { - AddList = new (StreamingLevelCollection.StreamingLevelLists) FStreamingLevelActorListCollection::FStreamingLevelActors(LevelName); + AddList = &StreamingLevelCollection.StreamingLevelLists.Emplace_GetRef(LevelName); } UE_CLOG(CVar_RepGraph_LogNetDormancyDetails, LogReplicationGraph, Display, TEXT("::OnClientVisibleLevelNameadd %s. LevelName: %s."), *GetPathName(), *LevelName.ToString()); @@ -3902,14 +3931,16 @@ void UReplicationGraphNode_ConnectionDormancyNode::OnClientVisibleLevelNameAdd(F bool UReplicationGraphNode_ConnectionDormancyNode::NotifyRemoveNetworkActor(const FNewReplicatedActorInfo& ActorInfo, bool WarnIfNotFound) { + QUICK_SCOPE_CYCLE_COUNTER(ConnectionDormancyNode_NotifyRemoveNetworkActor); + // Remove from active list by calling super - if (Super::NotifyRemoveNetworkActor(ActorInfo, false)) + if (Super::RemoveNetworkActorFast(ActorInfo)) { return true; } // Not found in active list. We must check out RemovedActorList - return RemovedStreamingLevelActorListCollection.RemoveActor(ActorInfo, WarnIfNotFound, this); + return RemovedStreamingLevelActorListCollection.RemoveActorFast(ActorInfo, this); } void UReplicationGraphNode_ConnectionDormancyNode::NotifyResetAllNetworkActors() @@ -3949,12 +3980,15 @@ void UReplicationGraphNode_DormancyNode::NotifyResetAllNetworkActors() void UReplicationGraphNode_DormancyNode::AddDormantActor(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& GlobalInfo) { - Super::NotifyAddNetworkActor(ActorInfo); + QUICK_SCOPE_CYCLE_COUNTER(DormancyNode_AddDormantActor); + Super::NotifyAddNetworkActor(ActorInfo); + UE_CLOG(CVar_RepGraph_LogNetDormancyDetails > 0 && ConnectionNodes.Num() > 0, LogReplicationGraph, Display, TEXT("GRAPH_DORMANCY: AddDormantActor %s on %s. Adding to %d connection nodes."), *ActorInfo.Actor->GetPathName(), *GetName(), ConnectionNodes.Num()); for (auto& MapIt : ConnectionNodes) { + QUICK_SCOPE_CYCLE_COUNTER(ConnectionDormancyNode_NotifyAddNetworkActor); UReplicationGraphNode_ConnectionDormancyNode* Node = MapIt.Value; Node->NotifyAddNetworkActor(ActorInfo); } @@ -3965,12 +3999,14 @@ void UReplicationGraphNode_DormancyNode::AddDormantActor(const FNewReplicatedAct void UReplicationGraphNode_DormancyNode::RemoveDormantActor(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo) { + QUICK_SCOPE_CYCLE_COUNTER(DormancyNode_RemoveDormantActor); + UE_CLOG(CVar_RepGraph_LogActorRemove>0, LogReplicationGraph, Display, TEXT("UReplicationGraphNode_DormancyNode::RemoveDormantActor %s on %s. (%d connection nodes). ChildNodes: %d"), *GetNameSafe(ActorInfo.Actor), *GetPathName(), ConnectionNodes.Num(), AllChildNodes.Num()); - Super::NotifyRemoveNetworkActor(ActorInfo); - + Super::RemoveNetworkActorFast(ActorInfo); + ActorRepInfo.Events.DormancyFlush.RemoveAll(this); - + // Update any connection specific nodes for (auto& MapIt : ConnectionNodes) { @@ -4031,7 +4067,7 @@ UReplicationGraphNode_ConnectionDormancyNode* UReplicationGraphNode_DormancyNode void UReplicationGraphNode_DormancyNode::OnActorDormancyFlush(FActorRepListType Actor, FGlobalActorReplicationInfo& GlobalInfo) { - QUICK_SCOPE_CYCLE_COUNTER(UReplicationGraphNode_DormancyNode_OnActorDormancyFlush); + QUICK_SCOPE_CYCLE_COUNTER(DormancyNode_OnActorDormancyFlush); if (CVar_RepGraph_Verify) { @@ -4099,12 +4135,12 @@ void UReplicationGraphNode_DormancyNode::ConditionalGatherDormantDynamicActors(F // -------------------------------------------------------------------------------------------------------------------------------------------- -void UReplicationGraphNode_GridCell::AddStaticActor(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& GlobalInfo, bool bParentNodeHandlesDormancyChange) +void UReplicationGraphNode_GridCell::AddStaticActor(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo, bool bParentNodeHandlesDormancyChange) { - if (GlobalInfo.bWantsToBeDormant) + if (ActorRepInfo.bWantsToBeDormant) { // Pass to dormancy node - GetDormancyNode()->AddDormantActor(ActorInfo, GlobalInfo); + GetDormancyNode()->AddDormantActor(ActorInfo, ActorRepInfo); } else { @@ -4115,7 +4151,7 @@ void UReplicationGraphNode_GridCell::AddStaticActor(const FNewReplicatedActorInf // We need to be told if this actor changes dormancy so we can move him between nodes. Unless our parent is going to do it. if (!bParentNodeHandlesDormancyChange) { - GlobalInfo.Events.DormancyChange.AddUObject(this, &UReplicationGraphNode_GridCell::OnStaticActorNetDormancyChange); + ActorRepInfo.Events.DormancyChange.AddUObject(this, &UReplicationGraphNode_GridCell::OnStaticActorNetDormancyChange); } } @@ -5028,12 +5064,11 @@ void UReplicationGraphNode_GridSpatialization2D::PrepareForReplication() // information regarding current players for a connection when working with grids struct FPlayerGridCellInformation { - FPlayerGridCellInformation(UNetConnection* InConnection, FIntPoint InCurLocation) : - Connection(InConnection), CurLocation(InCurLocation), PrevLocation(FIntPoint::ZeroValue) + FPlayerGridCellInformation(FIntPoint InCurLocation) : + CurLocation(InCurLocation), PrevLocation(FIntPoint::ZeroValue) { } - UNetConnection* Connection; FIntPoint CurLocation; FIntPoint PrevLocation; }; @@ -5049,7 +5084,7 @@ void UReplicationGraphNode_GridSpatialization2D::GatherActorListsForConnection(c TArray ActiveGridCells; for (const FNetViewer& CurViewer : Params.Viewers) { - if (CurViewer.ViewLocation.Z > ConnectionMaxZ || CurViewer.Connection == nullptr) + if (CurViewer.ViewLocation.Z > ConnectionMaxZ) { continue; } @@ -5070,28 +5105,36 @@ void UReplicationGraphNode_GridSpatialization2D::GatherActorListsForConnection(c int32 CellX = (ClampedViewLoc.X - SpatialBias.X) / CellSize; if (CellX < 0) { - UE_LOG(LogReplicationGraph, Verbose, TEXT("Net view location.X %s is less than the spatial bias %s for %s"), *ClampedViewLoc.ToString(), *SpatialBias.ToString(), *CurViewer.Connection->Describe()); + UE_LOG(LogReplicationGraph, Verbose, TEXT("Net view location.X %s is less than the spatial bias %s for %s"), *ClampedViewLoc.ToString(), *SpatialBias.ToString(), CurViewer.Connection ? *CurViewer.Connection->Describe() : TEXT("NONE")); CellX = 0; } int32 CellY = (ClampedViewLoc.Y - SpatialBias.Y) / CellSize; if (CellY < 0) { - UE_LOG(LogReplicationGraph, Verbose, TEXT("Net view location.Y %s is less than the spatial bias %s for %s"), *ClampedViewLoc.ToString(), *SpatialBias.ToString(), *CurViewer.Connection->Describe()); + UE_LOG(LogReplicationGraph, Verbose, TEXT("Net view location.Y %s is less than the spatial bias %s for %s"), *ClampedViewLoc.ToString(), *SpatialBias.ToString(), CurViewer.Connection ? *CurViewer.Connection->Describe() : TEXT("NONE")); CellY = 0; } - // Save this information out for later. - FPlayerGridCellInformation NewPlayerCell(CurViewer.Connection, FIntPoint(CellX, CellY)); - FLastLocationGatherInfo* GatherInfoForConnection = LastLocationArray.FindByKey(CurViewer.Connection); + FPlayerGridCellInformation NewPlayerCell(FIntPoint(CellX, CellY)); - // Add any missing last location information that we don't have - if (GatherInfoForConnection == nullptr) + FLastLocationGatherInfo* GatherInfoForConnection = nullptr; + + // Save this information out for later. + if (CurViewer.Connection != nullptr) { - GatherInfoForConnection = &LastLocationArray[LastLocationArray.Add(FLastLocationGatherInfo(CurViewer.Connection, FVector(ForceInitToZero)))]; + GatherInfoForConnection = LastLocationArray.FindByKey(CurViewer.Connection); + + // Add any missing last location information that we don't have + if (GatherInfoForConnection == nullptr) + { + GatherInfoForConnection = &LastLocationArray[LastLocationArray.Emplace(CurViewer.Connection, FVector(ForceInitToZero))]; + } } - FVector LastLocationForConnection = GatherInfoForConnection->LastLocation; + FVector LastLocationForConnection = GatherInfoForConnection ? GatherInfoForConnection->LastLocation : ClampedViewLoc; + + //@todo: if this is clamp view loc this is now redundant... if (GridBounds.IsValid) { // Clean up the location data for this connection to be grid bound @@ -5339,7 +5382,7 @@ void UReplicationGraphNode_AlwaysRelevant::GatherActorListsForConnection(const F } // ------------------------------------------------------- - + void UReplicationGraphNode_TearOff_ForConnection::GatherActorListsForConnection(const FConnectionGatherActorListParameters& Params) { if (TearOffActors.Num() > 0) @@ -5437,6 +5480,12 @@ void UReplicationGraphNode_AlwaysRelevant_ForConnection::GatherActorListsForConn continue; } + // Ignore other connection view targets when this is a replay connection + if (Params.ConnectionManager.NetConnection->IsReplay() && (CurViewer.Connection != Params.ConnectionManager.NetConnection)) + { + continue; + } + FAlwaysRelevantActorInfo* LastData = PastRelevantActors.FindByKey(CurViewer.Connection); // We've not seen this actor before, go ahead and add them. @@ -5464,7 +5513,6 @@ void UReplicationGraphNode_AlwaysRelevant_ForConnection::GatherActorListsForConn } } - // ------------------------------------------------------- FAutoConsoleCommandWithWorldAndArgs NetRepGraphPrintChannelCounters(TEXT("Net.RepGraph.PrintActorChannelCounters"),TEXT(""), diff --git a/Engine/Plugins/Runtime/ReplicationGraph/Source/Private/ReplicationGraphTypes.cpp b/Engine/Plugins/Runtime/ReplicationGraph/Source/Private/ReplicationGraphTypes.cpp index 94dbf68723ac..3c6e49fa397f 100644 --- a/Engine/Plugins/Runtime/ReplicationGraph/Source/Private/ReplicationGraphTypes.cpp +++ b/Engine/Plugins/Runtime/ReplicationGraph/Source/Private/ReplicationGraphTypes.cpp @@ -227,6 +227,7 @@ void FActorRepList::Release() void FActorRepListRefView::RequestNewList(int32 NewSize, bool CopyExistingContent) { + QUICK_SCOPE_CYCLE_COUNTER(RepList_RequestNewList); FActorRepList* NewList = &GActorListAllocator.RequestList(NewSize > 0 ? NewSize : InitialListSize); if (CopyExistingContent) { diff --git a/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraph.h b/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraph.h index a2932558dbdd..65c6ed9934f2 100644 --- a/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraph.h +++ b/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraph.h @@ -149,6 +149,7 @@ struct FStreamingLevelActorListCollection { void AddActor(const FNewReplicatedActorInfo& ActorInfo); bool RemoveActor(const FNewReplicatedActorInfo& ActorInfo, bool bWarnIfNotFound, UReplicationGraphNode* Outer); + bool RemoveActorFast(const FNewReplicatedActorInfo& ActorInfo, UReplicationGraphNode* Outer); void Reset(); void Gather(const FConnectionGatherActorListParameters& Params); void DeepCopyFrom(const FStreamingLevelActorListCollection& Source); @@ -199,6 +200,9 @@ public: virtual void GetAllActorsInNode_Debugging(TArray& OutArray) const; + /** Removes the actor very quickly but breaks the list order */ + bool RemoveNetworkActorFast(const FNewReplicatedActorInfo& ActorInfo); + /** Copies the contents of Source into this node. Note this does not copy child nodes, just the ReplicationActorList/StreamingLevelCollection lists on this node. */ void DeepCopyActorListsFrom(const UReplicationGraphNode_ActorList* Source); @@ -383,8 +387,6 @@ protected: virtual void GatherActors_DistanceOnly(const FActorRepListRefView& RepList, FGlobalActorReplicationInfoMap& GlobalMap, FPerConnectionActorInfoMap& ConnectionMap, const FConnectionGatherActorListParameters& Params); void CalcFrequencyForActor(AActor* Actor, UReplicationGraph* RepGraph, UNetConnection* NetConnection, FGlobalActorReplicationInfo& GlobalInfo, FConnectionReplicationActorInfo& ConnectionInfo, FSettings& MySettings, const FNetViewerArray& Viewers, const uint32 FrameNum, int32 ExistingItemIndex); - UE_DEPRECATED(4.23, "Use the other function to allow for multiple viewers") - void CalcFrequencyForActor(AActor* Actor, UReplicationGraph* RepGraph, UNetConnection* NetConnection, FGlobalActorReplicationInfo& GlobalInfo, FConnectionReplicationActorInfo& ConnectionInfo, FSettings& MySettings, const FVector& ConnectionViewLocation, const FVector& ConnectionViewDir, const uint32 FrameNum, int32 ExistingItemIndex); }; @@ -727,14 +729,6 @@ public: /** List of previously (or currently if nothing changed last tick) focused actor data per connection */ UPROPERTY() TArray PastRelevantActors; - - UE_DEPRECATED(4.23, "ViewTargets are now handled inside the PastRelevantActorMap") - UPROPERTY() - AActor* LastViewer = nullptr; - - UE_DEPRECATED(4.23, "ViewTargets are now handled inside the PastRelevantActorMap") - UPROPERTY() - AActor* LastViewTarget = nullptr; }; // ----------------------------------- @@ -986,17 +980,19 @@ protected: /** Default Replication Path */ void ReplicateActorListsForConnections_Default(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists& GatheredReplicationListsForConnection, FNetViewerArray& Viewers); - UE_DEPRECATED(4.23, "Use the array format to support subconnections as well") - void ReplicateActorListsForConnection_Default(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists& GatheredReplicationListsForConnection, FNetViewer& Viewer); /** "FastShared" Replication Path */ void ReplicateActorListsForConnections_FastShared(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists& GatheredReplicationListsForConnection, FNetViewerArray& Viewers); - UE_DEPRECATED(4.23, "Use the array format to support subconnections as well") - void ReplicateActorListsForConnection_FastShared(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists& GatheredReplicationListsForConnection, FNetViewer& Viewer); /** Connections needing a FlushNet in PostTickDispatch */ TArray ConnectionsNeedingsPostTickDispatchFlush; + virtual void AddReplayViewers(UNetConnection* NetConnection, FNetViewerArray& Viewers) {} + +private: + + UNetReplicationGraphConnection* FixGraphConnectionList(TArray& OutList, int32& ConnectionId, UNetConnection* RemovedNetConnection); + private: /** Whether or not a connection was saturated during an update. */ @@ -1079,11 +1075,12 @@ public: bool bEnableDebugging; - // ID that is assigned by the replication graph. Will be reassigned/compacted as clients disconnect. Useful for spacing out connection operations. E.g., not stable but always compact. - int32 ConnectionId; + /** Index of the connection in the global list. Will be reassigned when any client disconnects so it is a key that can be referenced only during a single tick */ + int32 ConnectionOrderNum; - UE_DEPRECATED(4.23, "Use the LastGatherLocations to have support for subconnection lookups") - FVector LastGatherLocation; + // ID that is assigned by the replication graph. Will be reassigned/compacted as clients disconnect. Useful for spacing out connection operations. E.g., not stable but always compact. + UE_DEPRECATED(4.26, "This variable was renamed to ConnectionOrderNum to better reflect that it is not persistent and should not be considered an ID.") + int32 ConnectionId; UPROPERTY() TArray LastGatherLocations; diff --git a/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraphTypes.h b/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraphTypes.h index 068984697256..5d08f3dccb49 100644 --- a/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraphTypes.h +++ b/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraphTypes.h @@ -12,8 +12,10 @@ #include "UObject/UObjectHash.h" #include "ProfilingDebugging/CsvProfiler.h" #include "Engine/NetConnection.h" +#include "ReplicationGraphTypes.generated.h" class AActor; +class AController; class UNetConnection; class UNetReplicationGraphConnection; class UReplicationGraph; @@ -294,6 +296,17 @@ struct REPLICATIONGRAPH_API FActorRepListRefView : public TActorRepListViewBase< return false; } + bool RemoveFast(const FActorRepListType& ElementToRemove) + { + int32 idx = IndexOf(ElementToRemove); + if (idx >= 0) + { + RemoveAtSwap(idx); + return true; + } + return false; + } + void RemoveAtSwap(int32 idx) { repCheck(RepList && Num() > idx); @@ -357,15 +370,23 @@ REPLICATIONGRAPH_API void PreAllocateRepList(int32 ListSize, int32 NumLists); // -------------------------------------------------------------------------------------------------------------------------------------------- /** Per-Class actor data about how the actor replicates */ +USTRUCT() struct FClassReplicationInfo { - FClassReplicationInfo() { } + GENERATED_BODY() + + UPROPERTY() float DistancePriorityScale = 1.f; + UPROPERTY() float StarvationPriorityScale = 1.f; + UPROPERTY() float AccumulatedNetPriorityBias = 0.f; + UPROPERTY() uint16 ReplicationPeriodFrame = 1; + UPROPERTY() uint16 FastPath_ReplicationPeriodFrame = 1; + UPROPERTY() uint16 ActorChannelFrameTimeout = 4; TFunction FastSharedReplicationFunc = nullptr; @@ -418,7 +439,9 @@ struct FClassReplicationInfo private: + UPROPERTY() float CullDistance = 0.0f; + UPROPERTY() float CullDistanceSquared = 0.f; }; @@ -427,7 +450,6 @@ struct FGlobalActorReplicationInfo; struct FFastSharedReplicationInfo { - // LastBuiltFrameNum = 0; uint32 LastAttemptBuildFrameNum = 0; // the last frame we called FastSharedReplicationFunc on uint32 LastBunchBuildFrameNum = 0; // the last frame a new bunch was actually created FOutBunch Bunch; @@ -646,6 +668,8 @@ struct FGlobalActorReplicationInfoMap return *Ptr->Get(); } + ensureMsgf(IsActorValidForReplication(Actor), TEXT("This obj %s is pending to kill, storing this data will generate stale data in the map."), *GetPathNameSafe(Actor)); + // We need to add data for this actor FClassReplicationInfo& ClassInfo = GetClassInfo( GetActorRepListTypeClass(Actor) ); @@ -664,6 +688,8 @@ struct FGlobalActorReplicationInfoMap return *Ptr->Get(); } + ensureMsgf(IsActorValidForReplication(Actor), TEXT("This obj %s is pending to kill, storing this data will generate stale data in the map."), *GetPathNameSafe(Actor)); + bWasCreated = true; // We need to add data for this actor @@ -1174,22 +1200,18 @@ typedef TArray FNetViewerArra // Parameter structure for what we actually pass down during the Gather phase. struct FConnectionGatherActorListParameters { + UE_DEPRECATED(4.26, "Please use the constructor that takes a viewer array.") FConnectionGatherActorListParameters(FNetViewer& InViewer, UNetReplicationGraphConnection& InConnectionManager, TSet& InClientVisibleLevelNamesRef, uint32 InReplicationFrameNum, FGatheredReplicationActorLists& InOutGatheredReplicationLists) - : Viewer(InViewer), ConnectionManager(InConnectionManager), ReplicationFrameNum(InReplicationFrameNum), OutGatheredReplicationLists(InOutGatheredReplicationLists), ClientVisibleLevelNamesRef(InClientVisibleLevelNamesRef) + : ConnectionManager(InConnectionManager), ReplicationFrameNum(InReplicationFrameNum), OutGatheredReplicationLists(InOutGatheredReplicationLists), ClientVisibleLevelNamesRef(InClientVisibleLevelNamesRef) { - Viewers.Add(InViewer); + Viewers.Emplace(InViewer); } FConnectionGatherActorListParameters(FNetViewerArray& InViewers, UNetReplicationGraphConnection& InConnectionManager, TSet& InClientVisibleLevelNamesRef, uint32 InReplicationFrameNum, FGatheredReplicationActorLists& InOutGatheredReplicationLists) - : Viewer(InViewers[0]), Viewers(InViewers), ConnectionManager(InConnectionManager), ReplicationFrameNum(InReplicationFrameNum), OutGatheredReplicationLists(InOutGatheredReplicationLists), ClientVisibleLevelNamesRef(InClientVisibleLevelNamesRef) + : Viewers(InViewers), ConnectionManager(InConnectionManager), ReplicationFrameNum(InReplicationFrameNum), OutGatheredReplicationLists(InOutGatheredReplicationLists), ClientVisibleLevelNamesRef(InClientVisibleLevelNamesRef) { } - - /** In: The Data the nodes have to work with */ - UE_DEPRECATED(4.23, "Use the viewer arrays for support for subconnections") - FNetViewer& Viewer; - FNetViewerArray Viewers; UNetReplicationGraphConnection& ConnectionManager; uint32 ReplicationFrameNum; diff --git a/Engine/Plugins/Runtime/Steam/SteamSockets/Source/SteamSockets/Private/SteamSocketsSubsystem.cpp b/Engine/Plugins/Runtime/Steam/SteamSockets/Source/SteamSockets/Private/SteamSocketsSubsystem.cpp index 899c891306a4..ed78c0f48c05 100644 --- a/Engine/Plugins/Runtime/Steam/SteamSockets/Source/SteamSockets/Private/SteamSocketsSubsystem.cpp +++ b/Engine/Plugins/Runtime/Steam/SteamSockets/Source/SteamSockets/Private/SteamSocketsSubsystem.cpp @@ -14,6 +14,7 @@ #include "OnlineSubsystemSteam.h" #include "OnlineSubsystemNames.h" #include "SteamSocketsTypes.h" +#include "Stats/Stats.h" // Log Category for The API Debugger DEFINE_LOG_CATEGORY_STATIC(LogSteamSocketsAPI, Log, All); @@ -501,6 +502,8 @@ void FSteamSocketsSubsystem::DumpSocketInformationMap() const bool FSteamSocketsSubsystem::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FSteamSocketsSubsystem_Tick); + // Handle all of our updates from the Steam API callbacks if (SteamEventManager.IsValid()) { diff --git a/Engine/Plugins/Runtime/Steam/SteamVR/SteamVR.uplugin b/Engine/Plugins/Runtime/Steam/SteamVR/SteamVR.uplugin index 3c8ccec14c13..8f06b0727a73 100644 --- a/Engine/Plugins/Runtime/Steam/SteamVR/SteamVR.uplugin +++ b/Engine/Plugins/Runtime/Steam/SteamVR/SteamVR.uplugin @@ -17,8 +17,7 @@ "SupportedTargetPlatforms": [ "Win32", "Win64", - "Linux", - "Mac" + "Linux" ], "Modules": [ { @@ -28,8 +27,7 @@ "WhitelistPlatforms": [ "Win64", "Win32", - "Linux", - "Mac" + "Linux" ] }, { @@ -38,8 +36,7 @@ "LoadingPhase": "PostConfigInit", "WhitelistPlatforms": [ "Win64", - "Win32", - "Mac" + "Win32" ] }, { @@ -48,8 +45,7 @@ "LoadingPhase": "PostConfigInit", "WhitelistPlatforms": [ "Win64", - "Win32", - "Mac" + "Win32" ] }, { @@ -58,8 +54,7 @@ "LoadingPhase": "PostEngineInit", "WhitelistPlatforms": [ "Win64", - "Win32", - "Mac" + "Win32" ] } ], diff --git a/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Classes/SourceEffects/SourceEffectBitCrusher.h b/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Classes/SourceEffects/SourceEffectBitCrusher.h index ed30ddaa6f32..2951940f87bc 100644 --- a/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Classes/SourceEffects/SourceEffectBitCrusher.h +++ b/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Classes/SourceEffects/SourceEffectBitCrusher.h @@ -13,26 +13,34 @@ struct SYNTHESIS_API FSourceEffectBitCrusherSettings { GENERATED_USTRUCT_BODY() +#if WITH_EDITORONLY_DATA UPROPERTY(meta = (PropertyDeprecated)) float CrushedSampleRate; +#endif // if WITH_EDITORONLY_DATA // The reduced frequency to use for the audio stream. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset", meta = (DisplayName = "Sample Rate", AudioParam = "SampleRate", UIMin = "500.0", UIMax = "16000.0")) FSoundModulationDestinationSettings SampleRateModulation; +#if WITH_EDITORONLY_DATA UPROPERTY(meta = (PropertyDeprecated)) float CrushedBits; +#endif // if WITH_EDITORONLY_DATA // The reduced bit depth to use for the audio stream UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset", meta = (DisplayName = "Bit Depth", AudioParam = "BitDepth", ClampMin = "1.0", ClampMax = "24.0", UIMin = "1.0", UIMax = "16.0")) FSoundModulationDestinationSettings BitModulation; FSourceEffectBitCrusherSettings() +#if WITH_EDITORONLY_DATA : CrushedSampleRate(8000.0f) , CrushedBits(8.0f) +#endif // if WITH_EDITORONLY_DATA { +#if WITH_EDITORONLY_DATA SampleRateModulation.Value = CrushedSampleRate; BitModulation.Value = CrushedBits; +#endif // WITH_EDITORONLY_DATA } }; diff --git a/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Classes/SubmixEffects/SubmixEffectMultiBandCompressor.h b/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Classes/SubmixEffects/SubmixEffectMultiBandCompressor.h new file mode 100644 index 000000000000..884542f561db --- /dev/null +++ b/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Classes/SubmixEffects/SubmixEffectMultiBandCompressor.h @@ -0,0 +1,134 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DSP/DynamicsProcessor.h" +#include "Sound/SoundEffectSubmix.h" +#include "DSP/LinkwitzRileyBandSplitter.h" +#include "SubmixEffects/AudioMixerSubmixEffectDynamicsProcessor.h" +#include "SubmixEffectMultiBandCompressor.generated.h" + +USTRUCT(BlueprintType) +struct FDynamicsBandSettings +{ + GENERATED_BODY() + + // Frequency of the crossover between this band and the next. The last band will have this property ignored + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset", meta = (ClampMin = "20.0", ClampMax = "20000.0", UIMin = "20.0", UIMax = "20000")) + float CrossoverTopFrequency = 20000.f; + + // The amount of time to ramp into any dynamics processing effect in milliseconds. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset", meta = (ClampMin = "1.0", ClampMax = "300.0", UIMin = "1.0", UIMax = "200.0")) + float AttackTimeMsec = 10.f; + + // The amount of time to release the dynamics processing effect in milliseconds + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset", meta = (ClampMin = "20.0", ClampMax = "5000.0", UIMin = "20.0", UIMax = "5000.0")) + float ReleaseTimeMsec = 100.f; + + // The threshold at which to perform a dynamics processing operation + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset", meta = (ClampMin = "-72.0", ClampMax = "0.0", UIMin = "-72.0", UIMax = "0.0")) + float ThresholdDb = -6.f; + + // The dynamics processor ratio -- has different meaning depending on the processor type. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset", meta = (ClampMin = "1.0", ClampMax = "20.0", UIMin = "1.0", UIMax = "20.0")) + float Ratio = 1.5f; + + // The knee bandwidth of the compressor to use in dB + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset", meta = (ClampMin = "0.0", ClampMax = "20.0", UIMin = "0.0", UIMax = "20.0")) + float KneeBandwidthDb = 10.f; + + // The input gain of the dynamics processor in dB + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset", meta = (ClampMin = "-12.0", ClampMax = "20.0", UIMin = "-12.0", UIMax = "20.0")) + float InputGainDb = 0.f; + + // The output gain of the dynamics processor in dB + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset", meta = (ClampMin = "0.0", ClampMax = "20.0", UIMin = "0.0", UIMax = "20.0")) + float OutputGainDb = 0.f; +}; + +// A submix dynamics processor +USTRUCT(BlueprintType) +struct FSubmixEffectMultibandCompressorSettings +{ + GENERATED_BODY() + + // Controls how each band will react to audio above its threshold + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset") + ESubmixEffectDynamicsProcessorType DynamicsProcessorType = ESubmixEffectDynamicsProcessorType::Compressor; + + // Controls how quickly the bands will react to a signal above its threshold + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset") + ESubmixEffectDynamicsPeakMode PeakMode = ESubmixEffectDynamicsPeakMode::RootMeanSquared; + + // The amount of time to look ahead of the current audio. Allows for transients to be included in dynamics processing. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset", meta = (ClampMin = "0.0", ClampMax = "50.0", UIMin = "0.0", UIMax = "50.0")) + float LookAheadMsec = 3.f; + + // Whether or not to average all channels of audio before inputing into the dynamics processor + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset") + bool bLinkChannels = true; + + // Toggles treating the attack and release envelopes as analog-style vs digital-style. Analog will respond a bit more naturally/slower. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset") + bool bAnalogMode = true; + + // Turning off FourPole mode will use cheaper, shallower 2-pole crossovers + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset") + bool bFourPole = true; + + // Each band is a full dynamics processor, affecting at a unique frequency range + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SourceEffect|Preset") + TArray Bands; +}; + +class FSubmixEffectMultibandCompressor : public FSoundEffectSubmix +{ +public: + FSubmixEffectMultibandCompressor() {}; + + // Called on an audio effect at initialization on main thread before audio processing begins. + virtual void Init(const FSoundEffectSubmixInitData& InSampleRate) override; + + // Process the input block of audio. Called on audio thread. + virtual void OnProcessAudio(const FSoundEffectSubmixInputData& InData, FSoundEffectSubmixOutputData& OutData) override; + + // Called when an audio effect preset is changed + virtual void OnPresetChanged() override; + + // called from OnPresetChanged when something is changed that needs extra attention + void Initialize(FSubmixEffectMultibandCompressorSettings& Settings); + +protected: + TArray AudioInputFrame; + TArray AudioOutputFrame; + TArray DynamicsProcessors; + Audio::FLinkwitzRileyBandSplitter BandSplitter; + Audio::FMultibandBuffer MultiBandBuffer; + + // cached crossover + band info, so we can check if they need a re-build when editing + int32 PrevNumBands = 0; + TArray PrevCrossovers; + bool bPrevFourPole = true; + + int32 NumChannels = 0; + int32 FrameSize = 0; + float SampleRate = 48000.f; + + bool bInitialized = false; +}; + +UCLASS(ClassGroup = AudioSourceEffect, meta = (BlueprintSpawnableComponent)) +class USubmixEffectMultibandCompressorPreset : public USoundEffectSubmixPreset +{ + GENERATED_BODY() + +public: + + EFFECT_PRESET_METHODS(SubmixEffectMultibandCompressor) + + UFUNCTION(BlueprintCallable, Category = "Audio|Effects") + void SetSettings(const FSubmixEffectMultibandCompressorSettings& InSettings); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SubmixEffectPreset) + FSubmixEffectMultibandCompressorSettings Settings; +}; \ No newline at end of file diff --git a/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/SubmixEffectMultiBandCompressor.cpp b/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/SubmixEffectMultiBandCompressor.cpp new file mode 100644 index 000000000000..3bc53993cd77 --- /dev/null +++ b/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/SubmixEffectMultiBandCompressor.cpp @@ -0,0 +1,205 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SubmixEffects/SubmixEffectMultiBandCompressor.h" +#include "DSP/BufferVectorOperations.h" + +void FSubmixEffectMultibandCompressor::Init(const FSoundEffectSubmixInitData& InitData) +{ + NumChannels = 0; + SampleRate = InitData.SampleRate; + + AudioOutputFrame.Reset(); + MultiBandBuffer.Init(4, 2 * 1024); +} + +void FSubmixEffectMultibandCompressor::OnPresetChanged() +{ + GET_EFFECT_SETTINGS(SubmixEffectMultibandCompressor); + + if (Settings.Bands.Num() == 0 || NumChannels == 0) + { + return; + } + + bool bNeedsReinit = (Settings.Bands.Num() != PrevNumBands + || Settings.Bands.Num() - 1 != PrevCrossovers.Num() + || Settings.bFourPole != bPrevFourPole + || bInitialized == false); + + // check if crossovers have changed + // if so, update before potentially initializing + bool bCrossoversChanged = false; + for (int32 CrossoverId = 0; CrossoverId < PrevCrossovers.Num(); CrossoverId++) + { + float CrossoverFrequency = Settings.Bands[CrossoverId].CrossoverTopFrequency; + if (CrossoverFrequency != PrevCrossovers[CrossoverId]) + { + PrevCrossovers.Reset(Settings.Bands.Num()); + for (int32 BandId = 0; BandId < Settings.Bands.Num(); ++BandId) + { + PrevCrossovers.Add(Settings.Bands[BandId].CrossoverTopFrequency); + } + + bCrossoversChanged = true; + break; + } + } + + if (bNeedsReinit) + { + // only necessary when # of bands or filters is changed + Initialize(Settings); + } + else if (bCrossoversChanged) + { + // lighter way to update crossovers if nothing else needs to be reinit + BandSplitter.SetCrossovers(PrevCrossovers); + } + + Audio::EDynamicsProcessingMode::Type TypeToSet; + switch (Settings.DynamicsProcessorType) + { + default: + case ESubmixEffectDynamicsProcessorType::Compressor: + TypeToSet = Audio::EDynamicsProcessingMode::Compressor; + break; + + case ESubmixEffectDynamicsProcessorType::Limiter: + TypeToSet = Audio::EDynamicsProcessingMode::Limiter; + break; + + case ESubmixEffectDynamicsProcessorType::Expander: + TypeToSet = Audio::EDynamicsProcessingMode::Expander; + break; + + case ESubmixEffectDynamicsProcessorType::Gate: + TypeToSet = Audio::EDynamicsProcessingMode::Gate; + break; + } + + Audio::EPeakMode::Type PeakModeToSet; + switch (Settings.PeakMode) + { + default: + case ESubmixEffectDynamicsPeakMode::MeanSquared: + PeakModeToSet = Audio::EPeakMode::MeanSquared; + break; + + case ESubmixEffectDynamicsPeakMode::RootMeanSquared: + PeakModeToSet = Audio::EPeakMode::RootMeanSquared; + break; + + case ESubmixEffectDynamicsPeakMode::Peak: + PeakModeToSet = Audio::EPeakMode::Peak; + break; + } + + for (int32 BandId = 0; BandId < DynamicsProcessors.Num(); ++BandId) + { + Audio::FDynamicsProcessor& DynamicsProcessor = DynamicsProcessors[BandId]; + + Audio::EDynamicsProcessorChannelLinkMode ChannelLinkMode = Settings.bLinkChannels ? + Audio::EDynamicsProcessorChannelLinkMode::Peak + : Audio::EDynamicsProcessorChannelLinkMode::Disabled; + + DynamicsProcessor.SetChannelLinkMode(ChannelLinkMode); + + DynamicsProcessor.SetLookaheadMsec(Settings.LookAheadMsec); + DynamicsProcessor.SetAnalogMode(Settings.bAnalogMode); + + DynamicsProcessor.SetAttackTime(Settings.Bands[BandId].AttackTimeMsec); + DynamicsProcessor.SetReleaseTime(Settings.Bands[BandId].ReleaseTimeMsec); + DynamicsProcessor.SetThreshold(Settings.Bands[BandId].ThresholdDb); + DynamicsProcessor.SetRatio(Settings.Bands[BandId].Ratio); + DynamicsProcessor.SetKneeBandwidth(Settings.Bands[BandId].KneeBandwidthDb); + DynamicsProcessor.SetInputGain(Settings.Bands[BandId].InputGainDb); + DynamicsProcessor.SetOutputGain(Settings.Bands[BandId].OutputGainDb); + + DynamicsProcessor.SetProcessingMode(TypeToSet); + DynamicsProcessor.SetPeakMode(PeakModeToSet); + } +} + +void FSubmixEffectMultibandCompressor::Initialize(FSubmixEffectMultibandCompressorSettings& Settings) +{ + const int32 NumBands = Settings.Bands.Num(); + FrameSize = sizeof(float) * NumChannels; + + PrevCrossovers.Reset(NumBands - 1); + for (int32 BandId = 0; BandId < NumBands - 1; BandId++) + { + PrevCrossovers.Add(Settings.Bands[BandId].CrossoverTopFrequency); + } + + Audio::EFilterOrder CrossoverMode = Settings.bFourPole ? Audio::EFilterOrder::FourPole : Audio::EFilterOrder::TwoPole; + BandSplitter.Init(NumChannels, SampleRate, CrossoverMode, PrevCrossovers); + + MultiBandBuffer.SetBands(NumBands); + + DynamicsProcessors.Reset(NumBands); + DynamicsProcessors.AddDefaulted(NumBands); + for (int32 BandId = 0; BandId < NumBands; ++BandId) + { + DynamicsProcessors[BandId].Init(SampleRate, NumChannels); + } + + PrevNumBands = Settings.Bands.Num(); + bPrevFourPole = Settings.bFourPole; + + bInitialized = true; +} + +void FSubmixEffectMultibandCompressor::OnProcessAudio(const FSoundEffectSubmixInputData& InData, FSoundEffectSubmixOutputData& OutData) +{ + if (NumChannels != InData.NumChannels) + { + GET_EFFECT_SETTINGS(SubmixEffectMultibandCompressor); + NumChannels = InData.NumChannels; + + Initialize(Settings); + OnPresetChanged(); + } + + const int32 NumSamples = InData.NumFrames * NumChannels; + + if (bInitialized == false) + { + //passthrough + FMemory::Memcpy(OutData.AudioBuffer->GetData(), InData.AudioBuffer->GetData(), FrameSize * InData.NumFrames); + return; + } + + const Audio::AlignedFloatBuffer& InBuffer = *InData.AudioBuffer; + Audio::AlignedFloatBuffer& OutBuffer = *OutData.AudioBuffer; + + FMemory::Memzero(OutBuffer.GetData(), FrameSize * InData.NumFrames); + + Audio::FStackSampleBuffer ScratchBuffer; + const int32 BlockSize = FMath::Min(ScratchBuffer.Max(), NumSamples); + + if (BlockSize > MultiBandBuffer.NumSamples) + { + MultiBandBuffer.SetSamples(NumSamples); + } + + for (int32 SampleIdx = 0; SampleIdx < NumSamples; SampleIdx += BlockSize) + { + const float* InPtr = &InBuffer[SampleIdx]; + float* OutPtr = &OutBuffer[SampleIdx]; + + ScratchBuffer.SetNumUninitialized(BlockSize); + + BandSplitter.ProcessAudioBuffer(InPtr, MultiBandBuffer, BlockSize / NumChannels); + + for (int32 Band = 0; Band < DynamicsProcessors.Num(); ++Band) + { + DynamicsProcessors[Band].ProcessAudio(MultiBandBuffer[Band], BlockSize, ScratchBuffer.GetData()); + Audio::MixInBufferFast(ScratchBuffer.GetData(), OutPtr, BlockSize); + } + } +} + +void USubmixEffectMultibandCompressorPreset::SetSettings(const FSubmixEffectMultibandCompressorSettings& InSettings) +{ + UpdateSettings(InSettings); +} diff --git a/Engine/Plugins/Runtime/TimeSynth/Source/TimeSynth/Private/TimeSynthComponent.cpp b/Engine/Plugins/Runtime/TimeSynth/Source/TimeSynth/Private/TimeSynthComponent.cpp index 97765147fd91..b9fd2a6240ad 100644 --- a/Engine/Plugins/Runtime/TimeSynth/Source/TimeSynth/Private/TimeSynthComponent.cpp +++ b/Engine/Plugins/Runtime/TimeSynth/Source/TimeSynth/Private/TimeSynthComponent.cpp @@ -338,7 +338,7 @@ void UTimeSynthComponent::TickComponent(float DeltaTime, enum ELevelTick TickTyp } else { - UE_LOG(LogTimeSynth, Warning, TEXT("Could not find clip %s "), *(ClipHandle.ClipName.GetPlainNameString())); + UE_LOG(LogTimeSynth, Verbose, TEXT("Could not find clip %s "), *(ClipHandle.ClipName.GetPlainNameString())); } }); } diff --git a/Engine/Plugins/Runtime/Windows/WinDualShock/Source/WinDualShock/Private/WinDualShock.cpp b/Engine/Plugins/Runtime/Windows/WinDualShock/Source/WinDualShock/Private/WinDualShock.cpp index 87bf3ab0d803..50acef5ca5db 100644 --- a/Engine/Plugins/Runtime/Windows/WinDualShock/Source/WinDualShock/Private/WinDualShock.cpp +++ b/Engine/Plugins/Runtime/Windows/WinDualShock/Source/WinDualShock/Private/WinDualShock.cpp @@ -22,30 +22,30 @@ public: : MessageHandler(InMessageHandler) { // Configure touch and mouse events - bool bDS4TouchEvents = false; - bool bDS4TouchAxisButtons = false; - bool bDS4MouseEvents = false; - bool bDS4MotionEvents = false; + bool bDSTouchEvents = false; + bool bDSTouchAxisButtons = false; + bool bDSMouseEvents = false; + bool bDSMotionEvents = false; if ( GConfig ) { // Configure PS4Controllers to emit touch events from the DS4 touchpad if the application wants them. - GConfig->GetBool( TEXT( "PS4Application" ), TEXT( "bDS4TouchEvents" ), bDS4TouchEvents, GEngineIni ); + GConfig->GetBool( TEXT( "SonyController" ), TEXT( "bDSTouchEvents" ), bDSTouchEvents, GEngineIni ); // Configure PS4Controllers to emit axis events from the DS4 touchpad if the application wants them. - GConfig->GetBool( TEXT("PS4Application"), TEXT("bDS4TouchAxisButtons"), bDS4TouchAxisButtons, GEngineIni ); + GConfig->GetBool( TEXT("SonyController"), TEXT("bDSTouchAxisButtons"), bDSTouchAxisButtons, GEngineIni ); // Configure PS4Controllers to emit mouse events from the DS4 touchpad if the application wants them - GConfig->GetBool( TEXT( "PS4Application" ), TEXT( "bDS4MouseEvents" ), bDS4MouseEvents, GEngineIni ); + GConfig->GetBool( TEXT( "SonyController" ), TEXT( "bDSMouseEvents" ), bDSMouseEvents, GEngineIni ); // Configure PS4Controllers to emit motion events from the DS4 if the application wants them - GConfig->GetBool(TEXT("PS4Application"), TEXT("bDS4MotionEvents"), bDS4MotionEvents, GEngineIni); + GConfig->GetBool(TEXT("SonyController"), TEXT("bDSMotionEvents"), bDSMotionEvents, GEngineIni); } - Controllers.SetEmitTouchEvents( bDS4TouchEvents ); - Controllers.SetEmitTouchAxisEvents( bDS4TouchAxisButtons ); - Controllers.SetEmitMouseEvents( bDS4MouseEvents ); - Controllers.SetEmitMotionEvents(bDS4MotionEvents); + Controllers.SetEmitTouchEvents( bDSTouchEvents ); + Controllers.SetEmitTouchAxisEvents( bDSTouchAxisButtons ); + Controllers.SetEmitMouseEvents( bDSMouseEvents ); + Controllers.SetEmitMotionEvents(bDSMotionEvents); } virtual ~FWinDualShock() diff --git a/Engine/Plugins/Tests/EditorTests/Source/EditorTests/Private/Animation/AnimationBlueprintFastPathTest.cpp b/Engine/Plugins/Tests/EditorTests/Source/EditorTests/Private/Animation/AnimationBlueprintFastPathTest.cpp index 7c911f6fd8c8..78dc0e5c17d5 100644 --- a/Engine/Plugins/Tests/EditorTests/Source/EditorTests/Private/Animation/AnimationBlueprintFastPathTest.cpp +++ b/Engine/Plugins/Tests/EditorTests/Source/EditorTests/Private/Animation/AnimationBlueprintFastPathTest.cpp @@ -70,35 +70,9 @@ bool FCheckFastPathLatentCommand::Update() } for (const FExposedValueCopyRecord& CopyRecord : AnimNode->GetEvaluateGraphExposedInputs().CopyRecords) { - if (CopyRecord.SourcePropertyName == NAME_None) + if (CopyRecord.CopyIndex == INDEX_NONE) { - UE_LOG(LogAnimBlueprintFastPathTests, Error, TEXT("Anim blueprint has an invalid source property name (%s)"), *AnimBlueprint->GetName()); - } - if (bIsStructTest) - { - if (CopyRecord.SourceSubPropertyName == NAME_None) - { - UE_LOG(LogAnimBlueprintFastPathTests, Error, TEXT("Anim blueprint has an invalid source sub struct property name (%s)"), *AnimBlueprint->GetName()); - } - } - else - { - if (CopyRecord.SourceSubPropertyName != NAME_None) - { - UE_LOG(LogAnimBlueprintFastPathTests, Error, TEXT("Anim blueprint specifies a sub struct when it shouldnt (%s)"), *AnimBlueprint->GetName()); - } - } - if (CopyRecord.DestProperty == nullptr) - { - UE_LOG(LogAnimBlueprintFastPathTests, Error, TEXT("Anim blueprint has an invalid dest property ptr (%s)"), *AnimBlueprint->GetName()); - } - if (CopyRecord.DestArrayIndex < 0) - { - UE_LOG(LogAnimBlueprintFastPathTests, Error, TEXT("Anim blueprint has an invalid dest array index (%s)"), *AnimBlueprint->GetName()); - } - if (CopyRecord.Size <= 0) - { - UE_LOG(LogAnimBlueprintFastPathTests, Error, TEXT("Anim blueprint has an invalid size (%s)"), *AnimBlueprint->GetName()); + UE_LOG(LogAnimBlueprintFastPathTests, Error, TEXT("Anim blueprint has an invalid copy index (%s)"), *AnimBlueprint->GetName()); } } } diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMesh.usf b/Engine/Plugins/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMesh.usf index 128bea1d441b..e5c22f8651d1 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMesh.usf +++ b/Engine/Plugins/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMesh.usf @@ -2,20 +2,24 @@ #include "/Engine/Private/Common.ush" #include "/Engine/Private/MortonCode.ush" -#include "VirtualHeightfieldMesh.ush" - //#include "/Engine/Private/ShaderPrintCommon.ush" //#include "/Engine/Private/ShaderDrawDebug.ush" +#include "VirtualHeightfieldMesh.ush" + groupshared uint NumGroupTasks; globallycoherent RWStructuredBuffer RWQueueInfo; -globallycoherent RWStructuredBuffer RWQueueBuffer; +globallycoherent RWBuffer RWQueueBuffer; +uint QueueBufferSizeMask; -RWStructuredBuffer RWQuadBuffer; -StructuredBuffer QuadBuffer; +RWBuffer RWQuadBuffer; +Buffer QuadBuffer; -RWStructuredBuffer RWQuadNeighborBuffer; -StructuredBuffer QuadNeighborBuffer; +RWBuffer RWIndirectArgsBuffer; +Buffer IndirectArgsBufferSRV; + +RWBuffer RWQuadNeighborBuffer; +Buffer QuadNeighborBuffer; RWStructuredBuffer RWInstanceBuffer; StructuredBuffer InstanceBuffer; @@ -23,9 +27,6 @@ StructuredBuffer InstanceBuffer; RWBuffer RWFeedbackBuffer; Texture2D PageTableTexture; -uint4 PageTablePackedUniform; -float3 PageTableSize; -uint PageTableFeedbackId; Texture2D MinMaxTexture; SamplerState MinMaxTextureSampler; @@ -33,213 +34,221 @@ SamplerState MinMaxTextureSampler; Texture2D OcclusionTexture; int OcclusionLevelOffset; -float4x4 UVToWorld; -float3 CameraPosition; -float ProjectionScale; -float LodThreshold; +Texture2D LodTexture; + +uint MaxLevel; +uint PageTableFeedbackId; +uint NumPhysicalAddressBits; +float4 PageTableSize; +float4 PhysicalPageTransform; + +float4 LodDistances; +float3 ViewOrigin; float4 FrustumPlanes[5]; +float4x4 UVToWorld; +float3 UVToWorldScale; -Texture2D LodTexture; - -RWBuffer RWIndirectArgsBuffer; int NumIndices; -/** - * Clear the buffers and seed with the queue with the lowest mip page. - */ - -[numthreads(1, 1, 1)] -void InitBuffersCS() +/** Unpack the virtual level for a PhysicalAddress entry in the virtual texture page table. */ +uint GetVirtualLevelFromPhysicalAddress(uint InPhysicalAddress) { - RWQueueInfo[0].Read = 0; - RWQueueInfo[0].Write = 1; - RWQueueInfo[0].NumActive = 1; - - RWQueueBuffer[0].Address = 0; - RWQueueBuffer[0].Level = (uint)PageTableSize.z; - - RWQuadBuffer[0].Address = 0; - RWQuadBuffer[0].Level = 0; - - RWFeedbackBuffer[0] = 0; + // See packing in PageTableUpdate.usf + return InPhysicalAddress & 0xf; } -/** */ -struct FUniform +/** Compute physical UV from virtual UV in the tile with the given PhysicalAddress. */ +float2 VirtualToPhysicalUV(float2 InVirtualUV, uint InPhysicalAddress, float4 InTransformFactors, uint InNumAddressBits) { - // Page sizes are scaled by RcpPhysicalTextureSize - float pPageSize; - float vPageSize; - float vPageBorderSize; - uint PageCoordinateBitCount; -}; + // See packing in PageTableUpdate.usf + float PageX = (float)((InPhysicalAddress >> 4) & ((1 << InNumAddressBits) - 1)); + float PageY = (float)(InPhysicalAddress >> (4 + InNumAddressBits)); + float UVScale = 1.f / (float)(1 << GetVirtualLevelFromPhysicalAddress(InPhysicalAddress)); -/** */ -FUniform VTUniform_Unpack2(uint4 InPackedUniform) -{ - FUniform result; - result.pPageSize = asfloat(InPackedUniform.w); - result.vPageSize = asfloat(InPackedUniform.y); - result.vPageBorderSize = asfloat(InPackedUniform.z); - result.PageCoordinateBitCount = InPackedUniform.x == 0 ? 8 : 6; - return result; + float2 BaseUV = float2(PageX, PageY) * InTransformFactors.x; + float2 PageUV = InVirtualUV * InTransformFactors.y; + float2 BorderUV = InTransformFactors.z; + float2 HalfTexelUV = InTransformFactors.w; + + return BaseUV + PageUV + BorderUV - HalfTexelUV; } -/** */ -void Split( in QuadItem InItem, out QuadItem OutItems0, out QuadItem OutItems1, out QuadItem OutItems2, out QuadItem OutItems3 ) +/** Returns transform from virtual to physical UV in the tile with the given PhysicalAddress. Returns float3 where .xy is bias and .z is scale. */ +float3 GetVirtualToPhysicalUVTransform(uint2 InPos, uint InLevel, uint InPhysicalAddress, float4 InTransformFactors, uint InNumAddressBits) { - uint Address = InItem.Address << 2; - uint Level = InItem.Level - 1; + uint LodShift = (uint)max((int)GetVirtualLevelFromPhysicalAddress(InPhysicalAddress) - (int)InLevel, 0); + float PosDivider = 1.f / (float)(1 << LodShift); + float2 MinVirtualUV = frac((float2)InPos * PosDivider); + float2 MaxVirtualUV = MinVirtualUV + PosDivider; - OutItems0.Address = Address; - OutItems0.Level = Level; - OutItems1.Address = Address + 1; - OutItems1.Level = Level; - OutItems2.Address = Address + 2; - OutItems2.Level = Level; - OutItems3.Address = Address + 3; - OutItems3.Level = Level; + float2 MinPhysicalUV = VirtualToPhysicalUV(MinVirtualUV, InPhysicalAddress, InTransformFactors, InNumAddressBits); + float2 MaxPhysicalUV = VirtualToPhysicalUV(MaxVirtualUV, InPhysicalAddress, InTransformFactors, InNumAddressBits); + + return float3(MinPhysicalUV, MaxPhysicalUV.x - MinPhysicalUV.x); // Assume Max.y - Min.y == Max.x - Min.x } -/** */ -float2 UnPackMinMaxHeight(float4 Packed) +/** Return false if location is marked as occluded in the occlusion texture. */ +bool OcclusionTest(uint2 InPos, int InLevel) { - uint4 PackedScaled = (uint4)floor(Packed *= 255.f); + int OcclusionLevel = InLevel - OcclusionLevelOffset; + if (OcclusionLevel < 0) + { + return true; + } + return OcclusionTexture.Load(uint3(InPos, OcclusionLevel)) == 0; +} + +/** Unpack the values from the MinMaxHeight texture from the packed 8888 format. */ +float2 UnPackMinMaxHeight(float4 InPacked) +{ + uint4 PackedScaled = (uint4)floor(InPacked *= 255.f); uint2 UnPackedScaled = uint2(PackedScaled.x << 8 | PackedScaled.y, PackedScaled.z << 8 | PackedScaled.w); float2 UnPacked = (float2)UnPackedScaled / 65535.f; return UnPacked; } -/** */ -float GetSphereScreenSpaceSquared(float3 Center, float3 Extent, float3 LookPos, float ProjectiveScaleSq) +/** Return false if the AABB is completely outside one of the planes. */ +bool PlaneTestAABB(float4 InPlanes[5], float3 InCenter, float3 InExtent) { - float RSq = dot(Extent, Extent); - float DSq = dot(Center - LookPos, Center - LookPos); - return (RSq * ProjectiveScaleSq) / max(1, DSq); -} - -/** */ -bool PlaneTestAABB(float4 Planes[5], float3 PlaneSigns[5], float3 Center, float3 Extent) -{ - bool bInsideAllPlanes = true; + bool bPlaneTest = true; + [unroll] for (uint PlaneIndex = 0; PlaneIndex < 5; ++PlaneIndex) { - float D = dot(Planes[PlaneIndex], float4(Center + Extent * PlaneSigns[PlaneIndex], 1.0f)); - bInsideAllPlanes = bInsideAllPlanes && (D > 0); + float3 PlaneSigns; + PlaneSigns.x = InPlanes[PlaneIndex].x >= 0.f ? 1.f : -1.f; + PlaneSigns.y = InPlanes[PlaneIndex].y >= 0.f ? 1.f : -1.f; + PlaneSigns.z = InPlanes[PlaneIndex].z >= 0.f ? 1.f : -1.f; + + bool bInsidePlane = dot(InPlanes[PlaneIndex], float4(InCenter + InExtent * PlaneSigns, 1.0f)) > 0.f; + bPlaneTest = bPlaneTest && bInsidePlane; } - return bInsideAllPlanes; + return bPlaneTest; } -/** */ -bool OcclusionTest(uint2 Pos, int Level) +/* Return squared distance of closest distance between a point and a bounding box. */ +float SquaredMinDistanceToAABB(float3 InPos, float3 InMin, float3 InMax, float3 InScale) { - int OcclusionLevel = Level - OcclusionLevelOffset; - if (Level < 0) - { - return true; - } - return OcclusionTexture.Load(uint3(Pos, OcclusionLevel)) == 0; + float3 D1 = max(InMin - InPos, 0) * InScale; + float3 D2 = max(InPos - InMax, 0) * InScale; + return dot(D1, D1) + dot(D2, D2); } -/** */ -void DebugDrawUVBox(float3 UVMin, float3 UVMax, float4x4 Mat, float4 Color) +/* Return squared distance of furthest distance between a point and a bounding box. */ +float SquaredMaxDistanceToAABB(float3 InPos, float3 InMin, float3 InMax, float3 InScale) { + float3 D = max(abs(InPos - InMin), (InPos - InMax)) * InScale; + return dot(D, D); +} + +/** Draw a bounding box using the ShaderDrawDebug system. */ +void DebugDrawUVBox(float3 InUVMin, float3 InUVMax, float4x4 InTransform, float4 InColor) +{ +#if 0 // Enable only if ShaderDrawDebug is enabled float3 WorldPos[8]; - WorldPos[0] = mul(float4(UVMin.x, UVMin.y, UVMin.z, 1), Mat); - WorldPos[1] = mul(float4(UVMax.x, UVMin.y, UVMin.z, 1), Mat); - WorldPos[2] = mul(float4(UVMin.x, UVMax.y, UVMin.z, 1), Mat); - WorldPos[3] = mul(float4(UVMax.x, UVMax.y, UVMin.z, 1), Mat); - WorldPos[4] = mul(float4(UVMin.x, UVMin.y, UVMax.z, 1), Mat); - WorldPos[5] = mul(float4(UVMax.x, UVMin.y, UVMax.z, 1), Mat); - WorldPos[6] = mul(float4(UVMin.x, UVMax.y, UVMax.z, 1), Mat); - WorldPos[7] = mul(float4(UVMax.x, UVMax.y, UVMax.z, 1), Mat); + WorldPos[0] = mul(float4(InUVMin.x, InUVMin.y, InUVMin.z, 1), InTransform); + WorldPos[1] = mul(float4(InUVMax.x, InUVMin.y, InUVMin.z, 1), InTransform); + WorldPos[2] = mul(float4(InUVMin.x, InUVMax.y, InUVMin.z, 1), InTransform); + WorldPos[3] = mul(float4(InUVMax.x, InUVMax.y, InUVMin.z, 1), InTransform); + WorldPos[4] = mul(float4(InUVMin.x, InUVMin.y, InUVMax.z, 1), InTransform); + WorldPos[5] = mul(float4(InUVMax.x, InUVMin.y, InUVMax.z, 1), InTransform); + WorldPos[6] = mul(float4(InUVMin.x, InUVMax.y, InUVMax.z, 1), InTransform); + WorldPos[7] = mul(float4(InUVMax.x, InUVMax.y, InUVMax.z, 1), InTransform); - //AddQuad(WorldPos[0], WorldPos[2], WorldPos[3], WorldPos[1], Color); - //AddQuad(WorldPos[4], WorldPos[6], WorldPos[7], WorldPos[5], Color); - //AddLine(WorldPos[0], WorldPos[4], Color, Color); - //AddLine(WorldPos[1], WorldPos[5], Color, Color); - //AddLine(WorldPos[2], WorldPos[6], Color, Color); - //AddLine(WorldPos[3], WorldPos[7], Color, Color); -} - -/** */ -float2 VTComputePhysicalUVs(float2 LocalUV, uint PhysicalAddress, FUniform Uniform) -{ - // See packing in PageTableUpdate.usf - const uint vLevel = PhysicalAddress & 0xf; - const float UVScale = 1.0f / (float)(1 << vLevel); - const float pPageX = (float)((PhysicalAddress >> 4) & ((1 << Uniform.PageCoordinateBitCount) - 1)); - const float pPageY = (float)(PhysicalAddress >> (4 + Uniform.PageCoordinateBitCount)); - - const float2 pUV = float2(pPageX, pPageY) * Uniform.pPageSize + (LocalUV * Uniform.vPageSize + Uniform.vPageBorderSize); - - return pUV; -} - -/** */ -float3 GetLocalToPhysicalUV(float4 LocalUVMinMax, uint PhysicalAddress, uint4 InPackedUniform) -{ - FUniform Uniform = VTUniform_Unpack2(InPackedUniform); - float2 Min = VTComputePhysicalUVs(LocalUVMinMax.xy, PhysicalAddress, Uniform); - float2 Max = VTComputePhysicalUVs(LocalUVMinMax.zw, PhysicalAddress, Uniform); - return float3(Min, Max.x - Min.x); // Assume Max.y - Min.y == Max.x - Min.x + AddQuad(WorldPos[0], WorldPos[2], WorldPos[3], WorldPos[1], InColor); + AddQuad(WorldPos[4], WorldPos[6], WorldPos[7], WorldPos[5], InColor); + AddLine(WorldPos[0], WorldPos[4], InColor, InColor); + AddLine(WorldPos[1], WorldPos[5], InColor, InColor); + AddLine(WorldPos[2], WorldPos[6], InColor, InColor); + AddLine(WorldPos[3], WorldPos[7], InColor, InColor); +#endif } /** - * + * Compute shader to initialize all buffers, including adding the lowest mip page(s) to the QuadBuffer. + */ + +[numthreads(1, 1, 1)] +void InitBuffersCS() +{ + // Seed with one item in the queue. + RWQueueInfo[0].Read = 0; + RWQueueInfo[0].Write = 1; + RWQueueInfo[0].NumActive = 1; + + RWQueueBuffer[0] = Pack(InitQuadItem(0, MaxLevel)); + + // RenderLodMap indirect args + RWIndirectArgsBuffer[0] = 6; + RWIndirectArgsBuffer[1] = 0; // Increment this counter during CollectQuadsCS. + RWIndirectArgsBuffer[2] = 0; + RWIndirectArgsBuffer[3] = 0; + RWIndirectArgsBuffer[4] = 0; + + // ResolveNeighborLods and CullInstances indirect args + RWIndirectArgsBuffer[5] = 0; // Increment this counter during CollectQuadsCS. + RWIndirectArgsBuffer[6] = 1; + RWIndirectArgsBuffer[7] = 1; + RWIndirectArgsBuffer[8] = 0; + + // Clear virtual texture feedback counter. + RWFeedbackBuffer[0] = 0; +} + +/** + * Compute shader to traverse the virtual texture page table and generate an array of items to potentially render for a view. */ [numthreads(64, 1, 1)] void CollectQuadsCS( uint3 DispatchThreadId : SV_DispatchThreadID, - uint GroupIndex : SV_GroupIndex -) + uint GroupIndex : SV_GroupIndex ) { - //if (any(DispatchThreadId) != 0) return; - - float3 FrustumPlaneSigns[5]; - for (int i = 0; i < 5; ++i) - { - FrustumPlaneSigns[i].x = FrustumPlanes[i].x >= 0 ? 1 : -1; - FrustumPlaneSigns[i].y = FrustumPlanes[i].y >= 0 ? 1 : -1; - FrustumPlaneSigns[i].z = FrustumPlanes[i].z >= 0 ? 1 : -1; - } - + // Persistant threads stay alive until the work queue is drained. bool bExit = false; while (!bExit) - //for (int i=0; i < 2024; ++i) { + // Sync and init group task count. NumGroupTasks = 0; GroupMemoryBarrierWithGroupSync(); - // Try and pull a task + // Try and pull a task. int NumActive; InterlockedAdd(RWQueueInfo[0].NumActive, -1, NumActive); - if (NumActive > 0) + + if (NumActive <= 0) { + // No task pulled. Rewind. + InterlockedAdd(RWQueueInfo[0].NumActive, 1, NumActive); + } + else + { + // Increment group task count for this loop. uint Dummy; InterlockedAdd(NumGroupTasks, 1, Dummy); + // Read item to process from queue. uint Read; InterlockedAdd(RWQueueInfo[0].Read, 1, Read); - Read &= 4095; - QuadItem Item = RWQueueBuffer[Read]; - uint2 Pos = MortonDecode(Item.Address); - - bool bSubdivide = false; - - // Do we want to subdivide? + uint PackedItem = RWQueueBuffer[Read & QueueBufferSizeMask]; + QuadItem Item = UnpackQuadItem(PackedItem); + uint Address = Item.Address; + uint2 Pos = MortonDecode(Address); + uint Level = Item.Level; + + // Check if occluded. + bool bOcclude = !OcclusionTest(Pos, Level); + // Get UV bounding box - float2 Scale = (float)(1 << Item.Level) / PageTableSize.xy; + float2 Scale = (float)(1 << Level) * PageTableSize.zw; float2 UV0 = ((float2)Pos + float2(0, 0)) * Scale; float2 UV1 = ((float2)Pos + float2(1, 1)) * Scale; - float2 MinMaxHeight = UnPackMinMaxHeight(MinMaxTexture.SampleLevel(MinMaxTextureSampler, UV0, Item.Level)); + float2 MinMaxHeight = UnPackMinMaxHeight(MinMaxTexture.SampleLevel(MinMaxTextureSampler, UV0, (float)Level)); float3 UVMin = float3(UV0, MinMaxHeight.x); float3 UVMax = float3(UV1, MinMaxHeight.y); @@ -247,99 +256,59 @@ void CollectQuadsCS( float3 UVCenter = (UVMax + UVMin) * 0.5f; float3 UVExtent = UVMax - UVCenter; - // Get World space sphere for lod query - float3 World0 = mul(float4(UVMin, 1), UVToWorld).xyz; - float3 World1 = mul(float4(UVMax, 1), UVToWorld).xyz; - float3 WorldCenter = (World0 + World1) * 0.5f; - float3 WorldExtent = abs(World1 - WorldCenter); - float ScreenSizeSq = GetSphereScreenSpaceSquared(WorldCenter, WorldExtent, CameraPosition, ProjectionScale); - - bool bRequestSubdivide = Item.Level > 0 && ScreenSizeSq > LodThreshold; - - // check: clamp or saturate? - const float LodFracScale = 0.5; // reduce to force smoother but shorter lod transition - maybe expose this? - float LodFrac = clamp((LodThreshold - ScreenSizeSq) / (LodThreshold * LodFracScale), 0, 0.999); - - bool bCull = !PlaneTestAABB(FrustumPlanes, FrustumPlaneSigns, UVCenter, UVExtent); - bool bOcclude = !OcclusionTest(Pos, Item.Level); - - // hack - //if (bCull) { bCull = false; bRequestSubdivide = false; } - // hack + // Check if frustum culled + bool bCull = !PlaneTestAABB(FrustumPlanes, UVCenter, UVExtent); + bool bSubdivide = false; if (bCull || bOcclude) { - // we will store without further subdivision - bSubdivide = false; - //DebugDrawUVBox(UVMin, UVMax, UVToWorld, float4(0, 0, 1, 1)); + // Store, but don't subdivide. + DebugDrawUVBox(UVMin, UVMax, UVToWorld, float4(0, 0, 1, 1)); } - else if (bRequestSubdivide) + else if (Level > 0) { - // Can we subdivide? - uint2 PosMulTwo = Pos<<1; - uint LevelMinus1 = Item.Level - 1; - uint Level0 = PageTableTexture.Load(int3(PosMulTwo + uint2(0,0), LevelMinus1)) & 0xf; - uint Level1 = PageTableTexture.Load(int3(PosMulTwo + uint2(0,1), LevelMinus1)) & 0xf; - uint Level2 = PageTableTexture.Load(int3(PosMulTwo + uint2(1,0), LevelMinus1)) & 0xf; - uint Level3 = PageTableTexture.Load(int3(PosMulTwo + uint2(1,1), LevelMinus1)) & 0xf; + // Subdivide if any part of bbox could be inside the LOD range. + float DistanceSq = SquaredMinDistanceToAABB(ViewOrigin, UVMin, UVMax, UVToWorldScale) * LodDistances.w * LodDistances.w; + float LodDistance = LodDistances.x * (LodDistances.y - 1 + pow(LodDistances.z, Level - 1)); + float LodDistanceSq = LodDistance * LodDistance; - // Write page requests to feedback buffer - uint Request0 = PageTableFeedbackId | (PosMulTwo.x + 0) | ((PosMulTwo.y + 0) << 12) | (LevelMinus1 << 24); - uint Request1 = PageTableFeedbackId | (PosMulTwo.x + 0) | ((PosMulTwo.y + 1) << 12) | (LevelMinus1 << 24); - uint Request2 = PageTableFeedbackId | (PosMulTwo.x + 1) | ((PosMulTwo.y + 0) << 12) | (LevelMinus1 << 24); - uint Request3 = PageTableFeedbackId | (PosMulTwo.x + 1) | ((PosMulTwo.y + 1) << 12) | (LevelMinus1 << 24); - - uint FeedbackPos; - InterlockedAdd(RWFeedbackBuffer[0], 4, FeedbackPos); - RWFeedbackBuffer[FeedbackPos + 1] = Request0; - RWFeedbackBuffer[FeedbackPos + 2] = Request1; - RWFeedbackBuffer[FeedbackPos + 3] = Request2; - RWFeedbackBuffer[FeedbackPos + 4] = Request3; + bSubdivide = DistanceSq < LodDistanceSq; + } - bool bSplit = Level0 <= LevelMinus1 && Level1 <= LevelMinus1 && Level2 <= LevelMinus1 && Level3 <= LevelMinus1; - if (bSplit) + if (bSubdivide) + { + // Add children to queue. + uint Write; + InterlockedAdd(RWQueueInfo[0].Write, 4, Write); + + RWQueueBuffer[(Write + 0) & QueueBufferSizeMask] = Pack(InitQuadItem(Address * 4 + 0, Level - 1)); + RWQueueBuffer[(Write + 1) & QueueBufferSizeMask] = Pack(InitQuadItem(Address * 4 + 1, Level - 1)); + RWQueueBuffer[(Write + 2) & QueueBufferSizeMask] = Pack(InitQuadItem(Address * 4 + 2, Level - 1)); + RWQueueBuffer[(Write + 3) & QueueBufferSizeMask] = Pack(InitQuadItem(Address * 4 + 3, Level - 1)); + + InterlockedAdd(RWQueueInfo[0].NumActive, 4, NumActive); + } + else + { + // Add to output list. + uint PhysicalAddress = PageTableTexture.Load(int3(Pos, Level)); + + uint Write; + InterlockedAdd(RWIndirectArgsBuffer[1], 1, Write); + InterlockedMax(RWIndirectArgsBuffer[5], ((Write + 1) + 63) / 64); + + RWQuadBuffer[Write] = Pack(InitQuadRenderItem(Pos, Level, PhysicalAddress, bCull || bOcclude)); + + if (!(bCull || bOcclude)) { - // Divide and add new nodes to queue - QuadItem C0, C1, C2, C3; - Split(Item, C0, C1, C2, C3); - - uint Write; - InterlockedAdd(RWQueueInfo[0].Write, 4, Write); - - RWQueueBuffer[(Write + 0) & 4095] = C0; - RWQueueBuffer[(Write + 1) & 4095] = C2; - RWQueueBuffer[(Write + 2) & 4095] = C1; - RWQueueBuffer[(Write + 3) & 4095] = C3; - //DeviceMemoryBarrier(); - - InterlockedAdd(RWQueueInfo[0].NumActive, 4, NumActive); - - bSubdivide = true; + // Debug draw the bounds. + DebugDrawUVBox(UVMin, UVMax, UVToWorld, float4(1, 0, 0, 1)); } } - - if (!bSubdivide) - { - // Move to final list - uint Write; - InterlockedAdd(RWQuadBuffer[0].Address, 1, Write); - RWQuadBuffer[Write + 1].Address = Item.Address; - RWQuadBuffer[Write + 1].Level = Item.Level; - RWQuadBuffer[Write + 1].LocalToPhysicalUV = GetLocalToPhysicalUV(float4(0, 0, 1, 1), PageTableTexture.Load(int3(Pos, Item.Level)), PageTablePackedUniform); - RWQuadBuffer[Write + 1].Lod = LodFrac + ((bCull || bOcclude) ? 1 : 0); - - float4 Color = float4(1, 0, 0, 1); - //if (Item.Address == 25 && Item.Level == 3) Color = float4(0, 1, 0, 1); - //DebugDrawUVBox(UVMin, UVMax, UVToWorld, Color); - } - } - else - { - InterlockedAdd(RWQueueInfo[0].NumActive, 1, NumActive); } + // Exit if no work was found. DeviceMemoryBarrier(); - //GroupMemoryBarrierWithGroupSync(); if (NumGroupTasks == 0) { bExit = true; @@ -348,207 +317,183 @@ void CollectQuadsCS( } /** - * - */ - -[numthreads(1, 1, 1)] -void BuildIndirectArgsForLodAndCullCS() -{ - // RenderLodMap - RWIndirectArgsBuffer[0] = 6; - RWIndirectArgsBuffer[1] = QuadBuffer[0].Address; - RWIndirectArgsBuffer[2] = 0; - RWIndirectArgsBuffer[3] = 0; - RWIndirectArgsBuffer[4] = 0; - - // FetchNeighborLod - RWIndirectArgsBuffer[5] = (QuadBuffer[0].Address * 4 + 63) / 64; - RWIndirectArgsBuffer[6] = 1; - RWIndirectArgsBuffer[7] = 1; - RWIndirectArgsBuffer[8] = 0; - - // FinalCull - RWIndirectArgsBuffer[9] = (QuadBuffer[0].Address + 63) / 64; - RWIndirectArgsBuffer[10] = 1; - RWIndirectArgsBuffer[11] = 1; - RWIndirectArgsBuffer[12] = 0; -} - -/** - * + * Vertex/Pixel shaders that draw the Lod info for all quads output by the Collect pass. */ void RenderLodMapVS( in uint InstanceId : SV_InstanceID, in uint VertexId : SV_VertexID, out float4 OutPosition : SV_POSITION, - out float2 OutLod : TEXCOORD0 - ) + out float OutLod : TEXCOORD0 ) { - uint2 VertexUV = float2((VertexId >> 0) & 1, (VertexId >> 1) & 1); + uint2 PackedItem = QuadBuffer[InstanceId]; + QuadRenderItem Item = UnpackQuadRenderItem(PackedItem); - uint Address = QuadBuffer[InstanceId + 1].Address; - uint Level = QuadBuffer[InstanceId + 1].Level; - float Lod = frac(QuadBuffer[InstanceId + 1].Lod); + // Calculate vertex position for this quad. + uint2 Pos = Item.Pos; + uint Level = Item.Level; + uint2 Offset = uint2((VertexId >> 0) & 1, (VertexId >> 1) & 1); + uint2 VertexPosLod0 = (Pos + Offset) << Level; + float2 NormalizedVertexPos = (float2)VertexPosLod0 * PageTableSize.zw; + + OutPosition.xy = NormalizedVertexPos * float2(2.f, -2.f) + float2(-1.f, 1.f); + OutPosition.zw = float2(0, 1); - uint2 BaseXY = uint2( ReverseMortonCode2(Address), ReverseMortonCode2(Address >> 1) ); - uint2 XY = (BaseXY + VertexUV) * (1 << Level); - float2 VertexPos = (float2(XY) / PageTableSize.xy) * float2(2.f, -2.f) + float2(-1.f, 1.f); + // Calculate currently streamed level in the virtual texture for this quad. + uint Lod = GetVirtualLevelFromPhysicalAddress(Item.PhysicalAddress); + + // Pack along with culling state. + uint CullBit = Item.bCull ? 1 : 0; + float PackedLod = (float)(Lod | (CullBit << 7)) / 255.f; - OutPosition = float4(VertexPos, 0.f, 1.f); - OutLod = float2(Level, Lod); + OutLod = PackedLod; } void RenderLodMapPS( in float4 InPosition : SV_POSITION, - in noperspective float2 InLod : TEXCOORD0, - out float4 OutColor : SV_Target0 - ) + in noperspective float InLod : TEXCOORD0, + out float4 OutColor : SV_Target0 ) { - // Need to pack to render target... - OutColor = float4(InLod.x / 255.f, InLod.y, 0.f, 1.f); + OutColor = float4(InLod, 0.f, 0.f, 1.f); } /** - * + * Read the Lod info for the neighbors of each quad and resolve Lods at borders. */ -[numthreads(64, 1, 1)] -void ReadNeighborLodsCS( uint3 DispatchThreadId : SV_DispatchThreadID ) +[numthreads(64, 4, 1)] +void ResolveNeighborLodsCS( + uint GroupId : SV_GroupID, + uint GroupIndex : SV_GroupIndex ) { - uint QuadIndex = DispatchThreadId.x >> 2; - uint NeighborIndex = DispatchThreadId.x & 3; + // Process one neighbor per thread. + uint ThreadId = GroupId * 256 + GroupIndex; + uint QuadIndex = ThreadId >> 2; + uint NeighborIndex = ThreadId & 3; - if (QuadIndex >= QuadBuffer[0].Address) + if (QuadIndex >= IndirectArgsBufferSRV[1]) return; - QuadRenderItem Item = QuadBuffer[QuadIndex + 1]; - uint Address = Item.Address; + uint2 PackedItem = QuadBuffer[QuadIndex]; + QuadRenderItem Item = UnpackQuadRenderItem(PackedItem); + uint2 Pos = Item.Pos; uint Level = Item.Level; + uint PhysicalAddress = Item.PhysicalAddress; + + // Get sample location of neighbor in the LodTexture. + int2 SampleOffset = 0; + SampleOffset += (NeighborIndex == 0) ? int2(-1, 0) : 0; + SampleOffset += (NeighborIndex == 1) ? int2(0, (1 << Level)) : 0; + SampleOffset += (NeighborIndex == 2) ? int2((1 << Level), 0) : 0; + SampleOffset += (NeighborIndex == 3) ? int2(0, -1) : 0; + + uint2 PosLod0 = Pos << Level; + int2 ClampedPosLod0 = clamp((int2)PosLod0 + SampleOffset, 0, (int2)PageTableSize.xy - 1); + + // Read the neighbor Lod information. + float NeighborLodSample = LodTexture.Load(int3(ClampedPosLod0, 0)); + uint NeighborLodPacked = (uint)floor(NeighborLodSample * 255.f); + uint NeighborLod = NeighborLodPacked & 0x7f; + bool bNeighborActive = (NeighborLodPacked >> 7) == 0; - float LocalLodFloor = Level; - float LocalLodFrac = frac(Item.Lod); + // Get the PhysicalAddress for the local location but sampling at the neighbor Lod. + // Note that this will be the same as the local PhysicalAddress if the Lods are the same. + uint LocalLod = GetVirtualLevelFromPhysicalAddress(PhysicalAddress); + uint LodShift = max(NeighborLod - LocalLod, 0u); + uint NeighborLodPhysicalAddress = PageTableTexture.Load(int3(Pos >> LodShift, Level + LodShift)); - uint2 AddressXY = uint2(ReverseMortonCode2(Address), ReverseMortonCode2(Address >> 1)); + // We will need to blend if we are transitioning to an on-screen lower detail Lod. + bool bNeighborLerp = bNeighborActive && NeighborLod > LocalLod; + uint PhysicalAddressToWrite = bNeighborLerp ? NeighborLodPhysicalAddress : PhysicalAddress; - uint2 BaseXY = AddressXY << Level; + // Write the results to a flat buffer with neighbor entries per render item. + RWQuadNeighborBuffer[ThreadId] = Pack(InitQuadNeighborItem(PhysicalAddressToWrite, bNeighborLerp)); - //bool bPrint = Address == 795 && Level == 0; - //ShaderPrintFilter(bPrint); - //ShaderPrint(uint2(Address, Level)); ShaderPrintNewline(); - //ShaderPrint(Item.LocalToPhysicalUV * 2040); ShaderPrintNewline(); - //ShaderPrint(DispatchThreadId.x >> 2); - int2 Offset; - if (NeighborIndex == 0) Offset = int2(-1, 0); - else if (NeighborIndex == 1) Offset = int2(0, (1 << Level)); - else if (NeighborIndex == 2) Offset = int2((1 << Level), 0); - else Offset = int2(0, -1); + // Add requests to the virtual texture feedback buffer. + uint NumFeedbackItems = (NeighborIndex == 0) ? 2 : 1; + uint FeedbackPos; + InterlockedAdd(RWFeedbackBuffer[0], NumFeedbackItems, FeedbackPos); - int2 XY = (int2)BaseXY + Offset; - XY = clamp(XY, 0, (int2)PageTableSize.xy - 1); - - float2 NeighborLodPacked = LodTexture.Load(int3(XY, 0)); - float NeighborLodFloor = floor(NeighborLodPacked.r * 255.f); - float NeighborLodFrac = NeighborLodPacked.g; - - if (NeighborLodFloor != LocalLodFloor) + RWFeedbackBuffer[FeedbackPos + 1] = (Pos.x >> LodShift) | ((Pos.y >> LodShift) << 12) | ((Level + LodShift) << 24) | PageTableFeedbackId; + if (NeighborIndex == 0) { - NeighborLodFloor = max(LocalLodFloor, NeighborLodFloor); - NeighborLodFrac = 0.f; + RWFeedbackBuffer[FeedbackPos + 2] = Pos.x | (Pos.y << 12) | (Level << 24) | PageTableFeedbackId; } - else - { - NeighborLodFrac = (LocalLodFrac + NeighborLodFrac) * 0.5; - } - - QuadNeighborItem Neighbor; - Neighbor.Lod = NeighborLodFloor + NeighborLodFrac; - //ShaderPrint(Neighbor.Lod); - //ShaderPrint(LocalLodFrac); - - uint LodShift = (uint)(max(NeighborLodFloor - LocalLodFloor, 0)); - float2 MinUV = frac((float2)AddressXY / (float)(1 << LodShift)); - float2 MaxUV = MinUV + 1.f / (float)(1 << LodShift); - - uint PhysicalAddress = PageTableTexture.Load(int3(AddressXY >> LodShift, Level + LodShift)); - Neighbor.LocalToPhysicalUV = GetLocalToPhysicalUV(float4(MinUV, MaxUV), PhysicalAddress, PageTablePackedUniform); - - RWQuadNeighborBuffer[DispatchThreadId.x] = Neighbor; } /** - * + * Initialise the indirect args for the final culled indirect draw call. */ [numthreads(1, 1, 1)] void InitInstanceBufferCS() { - RWInstanceBuffer[0].Quad.Address = 0; -} - -/** - * - */ - -[numthreads(64, 1, 1)] -void CullInstancesCS(uint3 DispatchThreadId : SV_DispatchThreadID) -{ - uint QuadIndex = DispatchThreadId.x; - if (QuadIndex >= QuadBuffer[0].Address) - return; - - QuadRenderItem Item = QuadBuffer[QuadIndex + 1]; - uint2 Pos = MortonDecode(Item.Address); - bool bWasCulled = Item.Lod >= 1; - - float2 Scale = (float)(1 << Item.Level) / PageTableSize.xy; - float2 UV0 = ((float2)Pos + float2(0, 0)) * Scale; - float2 UV1 = ((float2)Pos + float2(1, 1)) * Scale; - - float2 MinMaxHeight = UnPackMinMaxHeight(MinMaxTexture.SampleLevel(MinMaxTextureSampler, UV0, Item.Level)); - - float3 UVMin = float4(UV0, MinMaxHeight.x, 1); - float3 UVMax = float4(UV1, MinMaxHeight.y, 1); - - float3 UVCenter = (UVMax + UVMin) * 0.5f; - float3 UVExtent = UVMax - UVCenter; - - float3 FrustumPlaneSigns[5]; - for (int i = 0; i < 5; ++i) - { - FrustumPlaneSigns[i].x = FrustumPlanes[i].x >= 0 ? 1 : -1; - FrustumPlaneSigns[i].y = FrustumPlanes[i].y >= 0 ? 1 : -1; - FrustumPlaneSigns[i].z = FrustumPlanes[i].z >= 0 ? 1 : -1; - } - - bool bCull = !PlaneTestAABB(FrustumPlanes, FrustumPlaneSigns, UVCenter, UVExtent) || bWasCulled; - - if (!bCull) - { - // Add to final list - uint Write; - InterlockedAdd(RWInstanceBuffer[0].Quad.Address, 1, Write); - - RWInstanceBuffer[Write + 1].Quad = Item; - RWInstanceBuffer[Write + 1].Neighbor[0] = QuadNeighborBuffer[QuadIndex * 4]; - RWInstanceBuffer[Write + 1].Neighbor[1] = QuadNeighborBuffer[QuadIndex * 4 + 1]; - RWInstanceBuffer[Write + 1].Neighbor[2] = QuadNeighborBuffer[QuadIndex * 4 + 2]; - RWInstanceBuffer[Write + 1].Neighbor[3] = QuadNeighborBuffer[QuadIndex * 4 + 3]; - } -} - -/** - * - */ - -[numthreads(1, 1, 1)] -void BuildIndirectArgsForDrawCS() -{ - // Final draw call RWIndirectArgsBuffer[0] = NumIndices; - RWIndirectArgsBuffer[1] = InstanceBuffer[0].Quad.Address; + RWIndirectArgsBuffer[1] = 0; // Increment this counter during CullInstancesCS. RWIndirectArgsBuffer[2] = 0; RWIndirectArgsBuffer[3] = 0; RWIndirectArgsBuffer[4] = 0; } + +/** + * Cull the potentially visible render items for a view and generate the final buffer of instances to render. + */ + +[numthreads(64, 1, 1)] +void CullInstancesCS( uint3 DispatchThreadId : SV_DispatchThreadID ) +{ + uint QuadIndex = DispatchThreadId.x; + if (QuadIndex >= IndirectArgsBufferSRV[1]) + return; + + uint2 PackedItem = QuadBuffer[QuadIndex]; + QuadRenderItem Item = UnpackQuadRenderItem(PackedItem); + uint2 Pos = Item.Pos; + uint Level = Item.Level; + +#if REUSE_CULL + // Reuse main view culling flag set in the Collect pass. + bool bCull = Item.bCull; +#else + // Cull against planes for this view. + float2 Scale = (float)(1 << Level) * PageTableSize.zw; + float2 UV0 = ((float2)Pos + float2(0, 0)) * Scale; + float2 UV1 = ((float2)Pos + float2(1, 1)) * Scale; + + float2 MinMaxHeight = UnPackMinMaxHeight(MinMaxTexture.SampleLevel(MinMaxTextureSampler, UV0, (float)Level)); + + float3 UVMin = float3(UV0, MinMaxHeight.x); + float3 UVMax = float3(UV1, MinMaxHeight.y); + + float3 UVCenter = (UVMax + UVMin) * 0.5f; + float3 UVExtent = UVMax - UVCenter; + + bool bCull = !PlaneTestAABB(FrustumPlanes, UVCenter, UVExtent); +#endif + + if (!bCull) + { + // Add to final render intance list. + QuadRenderInstance OutInstance; + OutInstance.PosLevelPacked = Pos.x | (Pos.y << 12) | (Level << 24); + + // Unpack physical address into full LocalToPhysicalUV here ready for use by vertex shader. + // Could move unpacking to vertex shader if we want to balance memory bandwidth. + OutInstance.UVTransform.xyz = GetVirtualToPhysicalUVTransform(Pos, Level, Item.PhysicalAddress, PhysicalPageTransform, NumPhysicalAddressBits); + + [unroll] + for (int NeighborIndex = 0; NeighborIndex < 4; ++ NeighborIndex) + { + const uint PackedNeighborItem = QuadNeighborBuffer[QuadIndex * 4 + NeighborIndex]; + const QuadNeighborItem NeighborItem = UnpackQuadNeighborItem(PackedNeighborItem); + + OutInstance.NeigborUVTransform[NeighborIndex] = GetVirtualToPhysicalUVTransform(Pos, Level, NeighborItem.PhysicalAddress, PhysicalPageTransform, NumPhysicalAddressBits); + } + + uint Write; + InterlockedAdd(RWIndirectArgsBuffer[1], 1, Write); + + RWInstanceBuffer[Write] = OutInstance; + } +} diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMesh.ush b/Engine/Plugins/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMesh.ush index a2ec91ed4aa5..d8e159b4f2d9 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMesh.ush +++ b/Engine/Plugins/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMesh.ush @@ -4,7 +4,7 @@ /** * Structures used by VirtualHeightfieldMesh. - * These all should be kept in sync with C++ definitions in VirtualHeightfieldMeshSceneProxy.cpp + * These need to be kept in sync with any C++ buffer definitions in VirtualHeightfieldMeshSceneProxy.cpp */ /** Structure used for tracking work queues in persistent wave style shaders. */ @@ -15,32 +15,124 @@ struct WorkerQueueInfo int NumActive; }; -/** Item description used when traversing the virtual page table quad tree. */ +/** Item description used when traversing the virtual page table quad tree. Packs as uint so store in Buffer declared as uint. */ struct QuadItem { uint Address; uint Level; }; -/** Description for items that are kept by the quad tree traversal stage. */ +uint Pack(QuadItem Item) +{ + uint PackedItem = 0; + PackedItem |= Item.Address; + PackedItem |= Item.Level << 24; + return PackedItem; +} + +QuadItem InitQuadItem(uint Address, uint Level) +{ + QuadItem Item; + Item.Address = Address; + Item.Level = Level; + return Item; +} + +QuadItem UnpackQuadItem(uint PackedItem) +{ + QuadItem Item; + Item.Address = PackedItem & 0x00FFFFFF; + Item.Level = PackedItem >> 24; + return Item; +} + +/** Description for items that are output by the quad tree collect stage. Packs as uint2 so store in Buffer declared as uint2. */ struct QuadRenderItem { - uint Address; + uint2 Pos; uint Level; - float3 LocalToPhysicalUV; - float Lod; + uint PhysicalAddress; + bool bCull; }; -/** Description of neighbor items. We fill 4 of these for each QuadRenderItem. */ +uint2 Pack(QuadRenderItem Item) +{ + uint2 PackedItem = 0; + PackedItem.x |= Item.Pos.x; + PackedItem.x |= Item.Pos.y << 12; + PackedItem.x |= Item.Level << 24; + PackedItem.y |= Item.PhysicalAddress & 0x000FFFFF; + PackedItem.y |= Item.bCull ? 1 << 20 : 0; + return PackedItem; +} + +QuadRenderItem InitQuadRenderItem(uint2 Pos, uint Level, uint PhysicalAddress, bool bCull) +{ + QuadRenderItem Item; + Item.Pos = Pos; + Item.Level = Level; + Item.PhysicalAddress = PhysicalAddress; + Item.bCull = bCull; + return Item; +} + +QuadRenderItem UnpackQuadRenderItem(uint2 PackedItem) +{ + QuadRenderItem Item; + Item.Pos.x = PackedItem.x & 0x00000FFF; + Item.Pos.y = (PackedItem.x & 0x00FFF000) >> 12; + Item.Level = PackedItem.x >> 24; + Item.PhysicalAddress = PackedItem.y & 0x000FFFFF; + Item.bCull = ((PackedItem.y & 0x00100000) >> 20) == 1; + return Item; +} + +/** Description of neighbor items. We fill 4 of these for each QuadRenderItem. Packs as uint so store in Buffer declared as uint. */ struct QuadNeighborItem { - float3 LocalToPhysicalUV; - float Lod; + uint PhysicalAddress; + bool bLerp; }; +uint Pack(QuadNeighborItem Item) +{ + uint PackedItem = 0; + PackedItem |= Item.PhysicalAddress & 0x000FFFFF; + PackedItem |= Item.bLerp ? 1 << 20 : 0; + return PackedItem; +} + +QuadNeighborItem InitQuadNeighborItem(uint PhysicalAddress, bool bLerp) +{ + QuadNeighborItem Item; + Item.PhysicalAddress = PhysicalAddress; + Item.bLerp = bLerp; + return Item; +} + +QuadNeighborItem UnpackQuadNeighborItem(uint PackedItem) +{ + QuadNeighborItem Item; + Item.PhysicalAddress = PackedItem & 0x000FFFFF; + Item.bLerp = ((PackedItem & 0x00100000) >> 20) == 1; + return Item; +} + /** Final render instance description used by the DrawInstancedIndirect(). */ struct QuadRenderInstance { - QuadRenderItem Quad; - QuadNeighborItem Neighbor[4]; + // Total of 64 bytes is nice for cache alignment. Take care when modifying. + uint PosLevelPacked; + float3 UVTransform; + float3 NeigborUVTransform[4]; }; + +uint2 UnpackPos(QuadRenderInstance Item) +{ + return uint2(Item.PosLevelPacked & 0x00000FFF, (Item.PosLevelPacked & 0x00FFF000) >> 12); +} + +uint UnpackLevel(QuadRenderInstance Item) +{ + return Item.PosLevelPacked >> 24; +} diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMeshVertexFactory.ush b/Engine/Plugins/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMeshVertexFactory.ush index 0979bc12227c..5d1519783edc 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMeshVertexFactory.ush +++ b/Engine/Plugins/VirtualHeightfieldMesh/Shaders/Private/VirtualHeightfieldMeshVertexFactory.ush @@ -1,143 +1,145 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "/Engine/Private/VertexFactoryCommon.ush" -#include "/Engine/Private/MortonCode.ush" #include "VirtualHeightfieldMesh.ush" StructuredBuffer InstanceBuffer; + Texture2D HeightTexture; -//Texture2D NormalTexture; -//Texture2D DisplacementTexture; SamplerState HeightSampler; -//SamplerState NormalSampler; -//SamplerState DisplacementSampler; -uint4 VTPackedUniform; -float2 PageTableSize; -float4x4 LocalToWorld; +float4 PageTableSize; +float MaxLod; +float4x4 VirtualHeightfieldToLocal; +float4x4 VirtualHeightfieldToWorld; -#define GRID_SIZE (VirtualHeightfieldMeshVF.NumQuadsPerTileSide+1) -//65 +float3 LodViewOrigin; +float4 LodDistances; -struct FVertexFactoryInterpolantsVSToPS -{ -#if NUM_TEX_COORD_INTERPOLATORS - float4 TexCoords[(NUM_TEX_COORD_INTERPOLATORS + 1) / 2] : TEXCOORD0; -#endif +#define GRID_SIZE (VirtualHeightfieldMeshVF.NumQuadsPerTileSide+1) -#if VF_USE_PRIMITIVE_SCENE_DATA - nointerpolation uint PrimitiveId : PRIMITIVE_ID; -#endif -}; - -/** - * Per-vertex inputs from bound vertex buffers - */ +/** Per-vertex inputs. No vertex buffers are bound. */ struct FVertexFactoryInput { uint InstanceId : SV_InstanceID; uint VertexId : SV_VertexID; - -#if VF_USE_PRIMITIVE_SCENE_DATA - uint PrimitiveId : ATTRIBUTE13; -#endif }; -/** - * Per-vertex inputs from bound vertex buffers. Used by passes with a trimmed down position-only shader. - */ -struct FPositionOnlyVertexFactoryInput -{ - uint InstanceId : SV_InstanceID; - uint VertexId : SV_VertexID; - -#if VF_USE_PRIMITIVE_SCENE_DATA - uint PrimitiveId : ATTRIBUTE1; -#endif -}; - -/** - * Per-vertex inputs from bound vertex buffers. Used by passes with a trimmed down position-and-normal-only shader. - */ -struct FPositionAndNormalOnlyVertexFactoryInput -{ - uint InstanceId : SV_InstanceID; - uint VertexId : SV_VertexID; - -#if VF_USE_PRIMITIVE_SCENE_DATA - uint PrimitiveId : ATTRIBUTE1; -#endif -}; - -/** - * Caches intermediates that would otherwise have to be computed multiple times. Avoids relying on the compiler to optimize out redundant operations. - */ +/** Cached intermediates that would otherwise have to be computed multiple times. Avoids relying on the compiler to optimize out redundant operations. */ struct FVertexFactoryIntermediates { - float2 LocalUV; - float3 LocalPos; -// float4 WorldPos; -// float4 WorldPosPreDisplacement; - float3 WorldNormal; -// float Displacement; - uint PrimitiveId; + float2 LocalUV; + float3 VTPos; + float3 LocalPos; + float3 WorldNormal; }; -uint GetPrimitiveId(FVertexFactoryInterpolantsVSToPS Interpolants) +/** Attributes to interpolate from the vertex shader to the pixel shader. */ +struct FVertexFactoryInterpolantsVSToPS { -#if VF_USE_PRIMITIVE_SCENE_DATA - return Interpolants.PrimitiveId; -#else - return 0; -#endif -} - -void SetPrimitiveId(inout FVertexFactoryInterpolantsVSToPS Interpolants, uint PrimitiveId) -{ -#if VF_USE_PRIMITIVE_SCENE_DATA - Interpolants.PrimitiveId = PrimitiveId; -#endif -} - #if NUM_TEX_COORD_INTERPOLATORS -float2 GetUV(FVertexFactoryInterpolantsVSToPS Interpolants, int UVIndex) -{ - float4 UVVector = Interpolants.TexCoords[UVIndex / 2]; - return UVIndex % 2 ? UVVector.zw : UVVector.xy; -} - -void SetUV(inout FVertexFactoryInterpolantsVSToPS Interpolants, int UVIndex, float2 InValue) -{ - FLATTEN - if (UVIndex % 2) - { - Interpolants.TexCoords[UVIndex / 2].zw = InValue; - } - else - { - Interpolants.TexCoords[UVIndex / 2].xy = InValue; - } -} + float4 TexCoords[(NUM_TEX_COORD_INTERPOLATORS + 1) / 2] : TEXCOORD0; #endif +}; -/** Converts from vertex factory specific interpolants to a FMaterialPixelParameters, which is used by material inputs. */ -FMaterialPixelParameters GetMaterialPixelParameters(FVertexFactoryInterpolantsVSToPS Interpolants, float4 SvPosition) +/** Helper to morph the UV location of a vertex. First snaps down InMorphFactorFloor LOD levels and then morphs InMorphFactorFrac towards the next LOD level. */ +float2 MorphVertex(float2 InLocalUV, uint InGridSize, uint InMorphFactorFloor, float InMorphFactorFrac) { - // GetMaterialPixelParameters is responsible for fully initializing the result - FMaterialPixelParameters Result = MakeInitializedMaterialPixelParameters(); + float2 MorphedUV = InLocalUV; + + // Full morph levels + float MorphGridSize = InGridSize >> InMorphFactorFloor; + float2 MorphGridDimensions = float2(MorphGridSize, 1.f / MorphGridSize); + float2 MorphOffset1 = frac(InLocalUV * MorphGridDimensions.x) * MorphGridDimensions.y; + MorphedUV -= MorphOffset1; + + // Partial morph to next level + float2 MorphOffset2 = frac(MorphedUV * MorphGridDimensions.x * 0.5f) * MorphGridDimensions.y * 2.f; + MorphedUV -= MorphOffset2 * InMorphFactorFrac; + + return MorphedUV; +} -#if NUM_TEX_COORD_INTERPOLATORS - UNROLL - for (int CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++) - { - Result.TexCoords[CoordinateIndex] = GetUV(Interpolants, CoordinateIndex); - } -#endif //NUM_MATERIAL_TEXCOORDS +/** Compute the intermediates for a given vertex. */ +FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input) +{ + FVertexFactoryIntermediates Intermediates; - Result.TwoSidedSign = 0; - Result.PrimitiveId = GetPrimitiveId(Interpolants); + const QuadRenderInstance Item = InstanceBuffer[Input.InstanceId]; + const uint2 Pos = UnpackPos(Item); + const uint Level = UnpackLevel(Item); + + const float3 LocalUVTransform = Item.UVTransform; - return Result; + uint2 VertexCoord = uint2(Input.VertexId % GRID_SIZE, Input.VertexId / GRID_SIZE); + float2 LocalUV = (float2)VertexCoord / (float)(GRID_SIZE - 1); + + // Get neighbor details according to location. + uint NeighborIndex = ((LocalUV.x + LocalUV.y) > 1) ? + (LocalUV.x < LocalUV.y ? 1 : 2) : + (LocalUV.x < LocalUV.y ? 0 : 3); + float NeighborWeight = ((LocalUV.x + LocalUV.y) > 1) ? + (LocalUV.x < LocalUV.y ? LocalUV.y - 0.5 : LocalUV.x - 0.5) : + (LocalUV.x < LocalUV.y ? 0.5 - LocalUV.x : 0.5 - LocalUV.y); + NeighborWeight *= 2; + + float3 NeigborUVTransform = Item.NeigborUVTransform[NeighborIndex]; + float2 NeighborPhysicalUV = NeigborUVTransform.xy + LocalUV * NeigborUVTransform.z; + + // Calculate vertex UV details before morphing + float2 XY = ((float2)Pos + LocalUV) * (float)(1 << Level); + float2 NormalizedPos = (XY * PageTableSize.zw); + float2 LocalPhysicalUV = LocalUVTransform.xy + LocalUV * LocalUVTransform.z; + + // Sample height once to approximate distance and morph the LocalUV. + { + float Height = HeightTexture.SampleLevel(HeightSampler, LocalPhysicalUV, 0); + + float3 WorldPos = mul(float4(NormalizedPos, Height, 1), VirtualHeightfieldToWorld).xyz; + float DistanceSq = dot(LodViewOrigin - WorldPos, LodViewOrigin - WorldPos); + float Distance = sqrt(DistanceSq) * LodDistances.w; + + // Continous LOD at LOD 0. + float LodForDistance0 = saturate(Distance / (LodDistances.x * LodDistances.y)); + // Continous LOD at LOD 1 and above. + float LodForDistanceN = log2(1 + max((Distance / LodDistances.x - LodDistances.y), 0)) / log2(LodDistances.z); + // Sum to get total continous LOD at distance from view origin. + float LodForDistance = LodForDistance0 + LodForDistanceN; + // Clamp between the LOD level for this instance and the max LOD. + // Note that the culling phase should already ensure that LOD >= Level. + float LodForDistanceClamped = clamp(LodForDistance, (float)Level, MaxLod); + // Number of full levels that we need to morph. + float LodMorphFloor = floor(LodForDistanceClamped) - (float)Level; + // Final fractional morph has smoothstep to push transition region out to end of the LOD level. + float LodMorphFrac = smoothstep(0.7f, 0.98f, frac(LodForDistanceClamped)); + + // Apply morph to vertex postion. + LocalUV = MorphVertex(LocalUV, GRID_SIZE - 1, (uint)LodMorphFloor, LodMorphFrac); + + XY = ((float2)Pos + LocalUV) * (float)(1 << Level); + NormalizedPos = (XY * PageTableSize.zw); + LocalPhysicalUV = LocalUVTransform.xy + LocalUV * LocalUVTransform.z; + NeighborPhysicalUV = NeigborUVTransform.xy + LocalUV * NeigborUVTransform.z; + } + + // Height + float LocalHeight = HeightTexture.SampleLevel(HeightSampler, LocalPhysicalUV, 0); + float NeighborHeight = HeightTexture.SampleLevel(HeightSampler, NeighborPhysicalUV, 0); + float Height = lerp(LocalHeight, NeighborHeight, NeighborWeight); + + // Position in space of virtual texture volume + Intermediates.VTPos = float3(NormalizedPos, Height); + + // Position in local space + Intermediates.LocalPos = mul(float4(Intermediates.VTPos, 1), VirtualHeightfieldToLocal).xyz; + + Intermediates.LocalUV = NormalizedPos; + + Intermediates.WorldNormal = float3(0, 0, 1); + + Intermediates.PrimitiveId = 0; + + return Intermediates; } /** Converts from vertex factory specific input to a FMaterialVertexParameters, which is used by vertex shader material inputs. */ @@ -148,13 +150,13 @@ FMaterialVertexParameters GetMaterialVertexParameters(FVertexFactoryInput Input, Result.WorldPosition = WorldPosition; // needs fixing! - Result.TangentToWorld = mul(TangentToLocal, (float3x3)LocalToWorld); + Result.TangentToWorld = mul(TangentToLocal, (float3x3)VirtualHeightfieldToWorld); Result.TangentToWorld[2] = Intermediates.WorldNormal; Result.PreSkinnedPosition = WorldPosition;// Intermediates.WorldPosPreDisplacement.xyz; Result.PreSkinnedNormal = float3(0, 0, 1); -#if VIRTUAL_HEIGHTFIELD_MESH_FACTORY +#if VF_VIRTUAL_HEIGHFIELD_MESH //Result.Displacement = Intermediates.Displacement; #endif @@ -169,132 +171,101 @@ FMaterialVertexParameters GetMaterialVertexParameters(FVertexFactoryInput Input, return Result; } -float3 VirtualTextureUnpackNormal2(in float2 PackedXY, in float PackedSignZ) +/** Get ID in GPU Scene. We don't implement support because we create/consume our own instancing buffer. */ +uint GetPrimitiveId(FVertexFactoryInterpolantsVSToPS Interpolants) { - float2 NormalXY = PackedXY * 2.f - 1.f; - float SignZ = PackedSignZ * 2.f - 1.f; - float NormalZ = sqrt(saturate(1.0f - dot(NormalXY, NormalXY))) * SignZ; - return float3(NormalXY, NormalZ); + return 0; } -float VirtualTextureUnpackDisplacementR16(in float PackedValue) +/** Get ID in the GPU Scene. */ +uint VertexFactoryGetPrimitiveId(FVertexFactoryInterpolantsVSToPS Interpolants) { - float Height = (PackedValue * 65535.f) / 256.f; - return Height; + return GetPrimitiveId(Interpolants); } -float2 MorphVertex(float2 InLocalUV, float2 InGridDimensions, float InMorphFactor) -{ - float2 FracPart = frac(InLocalUV * InGridDimensions.x) * InGridDimensions.y; - return InLocalUV - FracPart * InMorphFactor; -} - -FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input) -{ - FVertexFactoryIntermediates Intermediates; - - QuadRenderItem Item = InstanceBuffer[Input.InstanceId + 1].Quad; - uint Address = Item.Address; - uint Level = Item.Level; - float3 LocalToPhysicalUV = Item.LocalToPhysicalUV; - - uint2 VertexCoord = uint2(Input.VertexId % GRID_SIZE, Input.VertexId / GRID_SIZE); - float2 LocalUV = (float2)VertexCoord / (float)(GRID_SIZE - 1);; - - uint NeighborIndex = ((LocalUV.x + LocalUV.y) > 1) ? - (LocalUV.x < LocalUV.y ? 1 : 2) : - (LocalUV.x < LocalUV.y ? 0 : 3); - float NeighborWeight = ((LocalUV.x + LocalUV.y) > 1) ? - (LocalUV.x < LocalUV.y ? LocalUV.y - 0.5 : LocalUV.x - 0.5) : - (LocalUV.x < LocalUV.y ? 0.5 - LocalUV.x : 0.5 - LocalUV.y); - NeighborWeight *= 2; - - QuadNeighborItem Neighbor = InstanceBuffer[Input.InstanceId + 1].Neighbor[NeighborIndex]; - - // Continuous Lod morph - float LodAtMaxMorph = max(ceil(Neighbor.Lod), (float)Level + 1); - float LodAtMinMorph = (float)Level; - - float LocalMorphLod = (float)Level + Item.Lod; - float NeighborMorphLod = Neighbor.Lod; - - float LocalMorph = (LocalMorphLod - LodAtMinMorph) / (LodAtMaxMorph - LodAtMinMorph); - float NeighborMorph = (NeighborMorphLod - LodAtMinMorph) / (LodAtMaxMorph - LodAtMinMorph); - float Morph = lerp(LocalMorph, NeighborMorph, NeighborWeight); - - int LodShift = (int)floor(LodAtMaxMorph - LodAtMinMorph); - float GridSize = (GRID_SIZE - 1) >> (LodShift); - float2 GridDimensions = float2(GridSize, 1.f / GridSize); - - LocalUV = MorphVertex(LocalUV, GridDimensions, Morph); - - // Normalized space within global mesh - uint2 BaseXY = uint2(ReverseMortonCode2(Address), ReverseMortonCode2(Address >> 1)); - float2 XY = (BaseXY + LocalUV) * (1 << Level); - float2 NormalizedPos = (XY / PageTableSize); - - // Physical Texture space - float2 LocalPhysicalUV = LocalToPhysicalUV.xy + LocalUV * LocalToPhysicalUV.z; - float2 NeighborPhysicalUV = Neighbor.LocalToPhysicalUV.xy + LocalUV * Neighbor.LocalToPhysicalUV.z; - - // Height - float LocalHeight = HeightTexture.SampleLevel(HeightSampler, LocalPhysicalUV, 0); - float NeighborHeight = HeightTexture.SampleLevel(HeightSampler, NeighborPhysicalUV, 0); - float Height = lerp(LocalHeight, NeighborHeight, NeighborWeight); - - // Local space - Intermediates.LocalPos = float3(NormalizedPos, Height); - -// WorldPos = mul(LocalToWorld, float4(WorldPos, 0)).xyz; -// Intermediates.WorldPosPreDisplacement = float4(WorldPos + ResolvedView.PreViewTranslation.xyz, 1); - - Intermediates.LocalUV = NormalizedPos; - - Intermediates.WorldNormal = float3(0, 0, 1); -// Intermediates.WorldNormal = VirtualTextureUnpackNormal2(NormalTexture.SampleLevel(NormalSampler, PhysicalUV, 0).xy, 1); -// Intermediates.Displacement = VirtualTextureUnpackDisplacementR16(DisplacementTexture.SampleLevel(DisplacementSampler, PhysicalUV, 0)); - -// Intermediates.WorldPos = Intermediates.WorldPosPreDisplacement; -// Intermediates.WorldPos.xyz += Intermediates.WorldNormal * Intermediates.Displacement; - -#if VF_USE_PRIMITIVE_SCENE_DATA - Intermediates.PrimitiveId = Input.PrimitiveId; -#else - Intermediates.PrimitiveId = 0; -#endif - - return Intermediates; -} - -/** -* Get the 3x3 tangent basis vectors for this vertex factory -* this vertex factory will calculate the binormal on-the-fly -* -* @param Input - vertex input stream structure -* @return 3x3 matrix -*/ -half3x3 VertexFactoryGetTangentToLocal(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates) -{ - return half3x3(1, 0, 0, 0, 1, 0, 0, 0, 1); -} - -// @return translated world position +/** Computes the world space position of this vertex. */ float4 VertexFactoryGetWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates) { + float4x4 LocalToWorld = GetPrimitiveData(Intermediates.PrimitiveId).LocalToWorld; float3 RotatedPosition = LocalToWorld[0].xyz * Intermediates.LocalPos.xxx + LocalToWorld[1].xyz * Intermediates.LocalPos.yyy + LocalToWorld[2].xyz * Intermediates.LocalPos.zzz; return float4(RotatedPosition + (LocalToWorld[3].xyz + ResolvedView.PreViewTranslation.xyz), 1); } +/** Computes the world space position of this vertex. */ float4 VertexFactoryGetRasterizedWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float4 InWorldPosition) { +#if 0 // todo: rough vertex cull outside bounds + float4x4 WorldToLocal = GetPrimitiveData(Intermediates.PrimitiveId).WorldToLocal; + float3 LocalObjectBoundsMin = GetPrimitiveData(Intermediates.PrimitiveId).LocalObjectBoundsMin - 0.05; + float3 LocalObjectBoundsMax = GetPrimitiveData(Intermediates.PrimitiveId).LocalObjectBoundsMax + 0.05; + float3 LocalPos = mul(float4(InWorldPosition.xyz - ResolvedView.PreViewTranslation.xyz, 1), WorldToLocal).xyz; + float Divider = (any(LocalPos > LocalObjectBoundsMax) || any(LocalPos < LocalObjectBoundsMin)) ? 0 : 1; + return float4(InWorldPosition.xyz, InWorldPosition.w / Divider); +#else return InWorldPosition; +#endif } +/** Computes the world space position used by vertex lighting for this vertex. */ float3 VertexFactoryGetPositionForVertexLighting(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float3 TranslatedWorldPosition) { return TranslatedWorldPosition; } +/** Computes the world space position of this vertex last frame. */ +float4 VertexFactoryGetPreviousWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates) +{ + float4x4 PreviousLocalToWorldTranslated = GetPrimitiveData(Intermediates.PrimitiveId).PreviousLocalToWorld; + PreviousLocalToWorldTranslated[3][0] += ResolvedView.PrevPreViewTranslation.x; + PreviousLocalToWorldTranslated[3][1] += ResolvedView.PrevPreViewTranslation.y; + PreviousLocalToWorldTranslated[3][2] += ResolvedView.PrevPreViewTranslation.z; + + return mul(float4(Intermediates.LocalPos, 1), PreviousLocalToWorldTranslated); +} + +/** Computes the world space normal of this vertex. */ +float3 VertexFactoryGetWorldNormal(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates) +{ + return Intermediates.WorldNormal; +} + +/** Get the 3x3 tangent basis vectors for this vertex factory. This vertex factory will calculate the binormal on-the-fly. */ +half3x3 VertexFactoryGetTangentToLocal(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates) +{ + return half3x3(1, 0, 0, 0, 1, 0, 0, 0, 1); +} + +/** Get the translated bounding sphere for this primitive. */ +float4 VertexFactoryGetTranslatedPrimitiveVolumeBounds(FVertexFactoryInterpolantsVSToPS Interpolants) +{ + float4 ObjectWorldPositionAndRadius = GetPrimitiveData(GetPrimitiveId(Interpolants)).ObjectWorldPositionAndRadius; + return float4(ObjectWorldPositionAndRadius.xyz + ResolvedView.PreViewTranslation.xyz, ObjectWorldPositionAndRadius.w); +} + +#if NUM_TEX_COORD_INTERPOLATORS + +void SetUV(inout FVertexFactoryInterpolantsVSToPS Interpolants, uint UVIndex, float2 InValue) +{ + FLATTEN + if (UVIndex % 2) + { + Interpolants.TexCoords[UVIndex / 2].zw = InValue; + } + else + { + Interpolants.TexCoords[UVIndex / 2].xy = InValue; + } +} + +float2 GetUV(FVertexFactoryInterpolantsVSToPS Interpolants, uint UVIndex) +{ + float4 UVVector = Interpolants.TexCoords[UVIndex / 2]; + return UVIndex % 2 ? UVVector.zw : UVVector.xy; +} + +#endif + +/** Constructs values that need to be interpolated from the vertex shader to the pixel shader. */ FVertexFactoryInterpolantsVSToPS VertexFactoryGetInterpolantsVSToPS(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, FMaterialVertexParameters VertexParameters) { FVertexFactoryInterpolantsVSToPS Interpolants; @@ -309,53 +280,31 @@ FVertexFactoryInterpolantsVSToPS VertexFactoryGetInterpolantsVSToPS(FVertexFacto GetCustomInterpolators(VertexParameters, CustomizedUVs); UNROLL - for (int CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++) - { - SetUV(Interpolants, CoordinateIndex, CustomizedUVs[CoordinateIndex]); - } + for (int CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++) + { + SetUV(Interpolants, CoordinateIndex, CustomizedUVs[CoordinateIndex]); + } #endif - SetPrimitiveId(Interpolants, Intermediates.PrimitiveId); - return Interpolants; } -/** for depth-only pass */ -float4 VertexFactoryGetWorldPosition(FPositionOnlyVertexFactoryInput Input) +/** Converts from vertex factory specific interpolants to a FMaterialPixelParameters, which is used by material inputs. */ +FMaterialPixelParameters GetMaterialPixelParameters(FVertexFactoryInterpolantsVSToPS Interpolants, float4 SvPosition) { - float3 LocalPos = float3(Input.VertexId % GRID_SIZE, Input.VertexId / GRID_SIZE, 0.0f); - float3 WorldPos = LocalPos * 10.f + float3(0, 0, 100); - return float4(WorldPos + ResolvedView.PreViewTranslation.xyz, 1); -} + // GetMaterialPixelParameters is responsible for fully initializing the result + FMaterialPixelParameters Result = MakeInitializedMaterialPixelParameters(); -// @return previous translated world position -float4 VertexFactoryGetPreviousWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates) -{ - float4x4 PreviousLocalToWorldTranslated = GetPrimitiveData(Intermediates.PrimitiveId).PreviousLocalToWorld; - PreviousLocalToWorldTranslated[3][0] += ResolvedView.PrevPreViewTranslation.x; - PreviousLocalToWorldTranslated[3][1] += ResolvedView.PrevPreViewTranslation.y; - PreviousLocalToWorldTranslated[3][2] += ResolvedView.PrevPreViewTranslation.z; +#if NUM_TEX_COORD_INTERPOLATORS + UNROLL + for (uint CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++) + { + Result.TexCoords[CoordinateIndex] = GetUV(Interpolants, CoordinateIndex); + } +#endif //NUM_MATERIAL_TEXCOORDS - return mul(float4(Intermediates.LocalPos, 1), PreviousLocalToWorldTranslated); -} + Result.TwoSidedSign = 0; + Result.PrimitiveId = GetPrimitiveId(Interpolants); -float4 VertexFactoryGetTranslatedPrimitiveVolumeBounds(FVertexFactoryInterpolantsVSToPS Interpolants) -{ - float4 ObjectWorldPositionAndRadius = GetPrimitiveData(GetPrimitiveId(Interpolants)).ObjectWorldPositionAndRadius; - return float4(ObjectWorldPositionAndRadius.xyz + ResolvedView.PreViewTranslation.xyz, ObjectWorldPositionAndRadius.w); -} - -uint VertexFactoryGetPrimitiveId(FVertexFactoryInterpolantsVSToPS Interpolants) -{ - return GetPrimitiveId(Interpolants); -} - -float3 VertexFactoryGetWorldNormal(FPositionAndNormalOnlyVertexFactoryInput Input) -{ - return float3(0.0f, 0.0f, 1.0f); -} - -float3 VertexFactoryGetWorldNormal(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates) -{ - return Intermediates.WorldNormal; + return Result; } diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshComponent.cpp b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshComponent.cpp index edbe6223bbcc..17da513fa195 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshComponent.cpp +++ b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshComponent.cpp @@ -6,6 +6,7 @@ #include "Engine/World.h" #include "VirtualHeightfieldMeshSceneProxy.h" #include "VT/RuntimeVirtualTexture.h" +#include "VT/RuntimeVirtualTextureVolume.h" UVirtualHeightfieldMeshComponent::UVirtualHeightfieldMeshComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) @@ -18,15 +19,26 @@ UVirtualHeightfieldMeshComponent::UVirtualHeightfieldMeshComponent(const FObject Mobility = EComponentMobility::Static; } +ARuntimeVirtualTextureVolume* UVirtualHeightfieldMeshComponent::GetVirtualTextureVolume() const +{ + return VirtualTexture.Get(); +} + +FTransform UVirtualHeightfieldMeshComponent::GetVirtualTextureTransform() const +{ + URuntimeVirtualTextureComponent* RuntimeVirtualTextureComponent = VirtualTexture.IsValid() ? VirtualTexture.Get()->VirtualTextureComponent : nullptr; + return RuntimeVirtualTextureComponent ? RuntimeVirtualTextureComponent->GetComponentTransform() * RuntimeVirtualTextureComponent->GetTexelSnapTransform() : FTransform::Identity; +} + URuntimeVirtualTexture* UVirtualHeightfieldMeshComponent::GetVirtualTexture() const { - URuntimeVirtualTextureComponent* RuntimeVirtualTextureComponent = Cast(VirtualTexture.GetComponent(GetOwner())); + URuntimeVirtualTextureComponent* RuntimeVirtualTextureComponent = VirtualTexture.IsValid() ? VirtualTexture.Get()->VirtualTextureComponent : nullptr; return RuntimeVirtualTextureComponent ? RuntimeVirtualTextureComponent->GetVirtualTexture() : nullptr; } UTexture2D* UVirtualHeightfieldMeshComponent::GetMinMaxTexture() const { - URuntimeVirtualTextureComponent* RuntimeVirtualTextureComponent = Cast(VirtualTexture.GetComponent(GetOwner())); + URuntimeVirtualTextureComponent* RuntimeVirtualTextureComponent = VirtualTexture.IsValid() ? VirtualTexture.Get()->VirtualTextureComponent : nullptr; return RuntimeVirtualTextureComponent ? RuntimeVirtualTextureComponent->GetMinMaxTexture() : nullptr; } @@ -41,6 +53,14 @@ bool UVirtualHeightfieldMeshComponent::IsVisible() const FPrimitiveSceneProxy* UVirtualHeightfieldMeshComponent::CreateSceneProxy() { + //hack[vhm]: No scene representation when disabled for now. + static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VHM.Enable")); + bool bVHMEnable = CVar != nullptr && CVar->GetValueOnAnyThread() != 0; + if (!bVHMEnable) + { + return nullptr; + } + return new FVirtualHeightfieldMeshSceneProxy(this); } diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshEnable.cpp b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshEnable.cpp index 6d89ba1fb5aa..7f581dc3966a 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshEnable.cpp +++ b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshEnable.cpp @@ -9,15 +9,15 @@ namespace VirtualHeightfieldMesh { - /** */ + /** CVar to toggle support for virtual heightfield mesh. */ static TAutoConsoleVariable CVarVHMEnable( TEXT("r.VHM.Enable"), - 1, + 0, TEXT("Enable virtual heightfield mesh"), ECVF_RenderThreadSafe ); - /** */ + /** Sink to apply updates when virtual heightfield mesh settings change. */ static void OnUpdate() { const bool bEnable = CVarVHMEnable.GetValueOnGameThread() != 0; @@ -32,7 +32,7 @@ namespace VirtualHeightfieldMesh for (TObjectIterator It; It; ++It) { - It->SetRenderInMainPass(bEnable); + It->MarkRenderStateDirty(); if (It->GetVirtualTexture() != nullptr) { @@ -46,11 +46,11 @@ namespace VirtualHeightfieldMesh { if (It->GetRuntimeVirtualTextures().Contains(RuntimeVirtualTexture)) { - It->SetRenderInMainPass(!bEnable); + It->MarkRenderStateDirty(); break; } } - } + } } } diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshSceneProxy.cpp b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshSceneProxy.cpp index 10b7258bcaeb..624883d291c2 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshSceneProxy.cpp +++ b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshSceneProxy.cpp @@ -4,6 +4,7 @@ #include "CommonRenderResources.h" #include "EngineModule.h" +#include "Engine/Engine.h" #include "GlobalShader.h" #include "HAL/IConsoleManager.h" #include "Materials/Material.h" @@ -15,11 +16,6 @@ #include "VT/RuntimeVirtualTexture.h" #include "VT/VirtualTextureFeedbackBuffer.h" -//#include "ShaderPrintParameters.h" -//#include "GpuDebugRendering.h" - -PRAGMA_DISABLE_OPTIMIZATION - DECLARE_STATS_GROUP(TEXT("Virtual Heightfield Mesh"), STATGROUP_VirtualHeightfieldMesh, STATCAT_Advanced); DECLARE_LOG_CATEGORY_EXTERN(LogVirtualHeightfieldMesh, Warning, All); @@ -32,13 +28,6 @@ static TAutoConsoleVariable CVarVHMLodScale( ECVF_RenderThreadSafe ); -static TAutoConsoleVariable CVarVHMFixCullingCamera( - TEXT("r.VHM.FixCullingCamera"), - 0, - TEXT("Disable update of Virtual Heightfield Mesh culling camera."), - ECVF_RenderThreadSafe -); - static TAutoConsoleVariable CVarVHMOcclusion( TEXT("r.VHM.Occlusion"), 1, @@ -46,6 +35,34 @@ static TAutoConsoleVariable CVarVHMOcclusion( ECVF_RenderThreadSafe ); +static TAutoConsoleVariable CVarVHMMaxRenderItems( + TEXT("r.VHM.MaxRenderInstances"), + 1024 * 4, + TEXT("Size of buffers used to collect render instances."), + ECVF_RenderThreadSafe +); + +static TAutoConsoleVariable CVarVHMMaxFeedbackItems( + TEXT("r.VHM.MaxFeedbackItems"), + 1024, + TEXT("Size of buffer used by virtual texture feedback."), + ECVF_RenderThreadSafe +); + +static TAutoConsoleVariable CVarVHMMaxPersistentQueueItems( + TEXT("r.VHM.MaxPersistentQueueItems"), + 1024 * 4, + TEXT("Size of queue used in the collect pass. This is rounded to the nearest power of 2."), + ECVF_RenderThreadSafe +); + +static TAutoConsoleVariable CVarVHMCollectPassWavefronts( + TEXT("r.VHM.CollectPassWavefronts"), + 16, + TEXT("Number of wavefronts to use for collect pass."), + ECVF_RenderThreadSafe +); + namespace VirtualHeightfieldMesh { /** Buffers filled by GPU culling used by the Virtual Heightfield Mesh final draw call. */ @@ -103,8 +120,25 @@ struct FOcclusionResultsKey /** */ TMap< FOcclusionResultsKey, FOcclusionResults > GOcclusionResults; +namespace VirtualHeightfieldMesh +{ + /** Calculate distances used for LODs in a given view for a given scene proxy. */ + FVector4 CalculateLodRanges(FSceneView const* InView, FVirtualHeightfieldMeshSceneProxy const* InProxy) + { + const uint32 MaxLevel = InProxy->AllocatedVirtualTexture->GetMaxLevel(); + const float Lod0UVSize = 1.f / (float)(1 << MaxLevel); + const FVector2D Lod0WorldSize = FVector2D(InProxy->UVToWorldScale.X, InProxy->UVToWorldScale.Y) * Lod0UVSize; + const float Lod0WorldRadius = Lod0WorldSize.Size(); + const float ScreenMultiple = FMath::Max(0.5f * InView->ViewMatrices.GetProjectionMatrix().M[0][0], 0.5f * InView->ViewMatrices.GetProjectionMatrix().M[1][1]); + const float Lod0Distance = Lod0WorldRadius * ScreenMultiple / InProxy->Lod0ScreenSize; + const float LodScale = InView->LODDistanceFactor * CVarVHMLodScale.GetValueOnRenderThread(); + + return FVector4(Lod0Distance, InProxy->Lod0Distribution, InProxy->LodDistribution, LodScale); + } +} + /** Renderer extension to manage the buffer pool and add hooks for GPU culling passes. */ -class FVirtualHeightfieldMeshRendererExtension : public IPersistentViewUniformBufferExtension +class FVirtualHeightfieldMeshRendererExtension : public FRenderResource { public: FVirtualHeightfieldMeshRendererExtension() @@ -124,12 +158,16 @@ public: void SubmitWork(FRHICommandListImmediate& InRHICmdList); protected: - //~ Begin IPersistentViewUniformBufferExtension Interface - virtual void BeginFrame() override; - virtual void EndFrame() override; - //~ End IPersistentViewUniformBufferExtension Interface + //~ Begin FRenderResource Interface + virtual void ReleaseRHI() override; + //~ End FRenderResource Interface private: + /** Called by renderer at start of render frame. */ + void BeginFrame(); + /** Called by renderer at end of render frame. */ + void EndFrame(); + /** Flag for frame validation. */ bool bInFrame; @@ -174,19 +212,25 @@ private: }; }; -/** Single global instance of the VirtualHeightfieldMesh extension. */ -FVirtualHeightfieldMeshRendererExtension GVirtualHeightfieldMeshViewUniformBufferExtension; +/** Single global instance of the VirtualHeightfieldMesh renderer extension. */ +TGlobalResource< FVirtualHeightfieldMeshRendererExtension > GVirtualHeightfieldMeshViewRendererExtension; void FVirtualHeightfieldMeshRendererExtension::RegisterExtension() { static bool bInit = false; if (!bInit) { - GetRendererModule().RegisterPersistentViewUniformBufferExtension(this); + GEngine->GetPreRenderDelegate().AddRaw(this, &FVirtualHeightfieldMeshRendererExtension::BeginFrame); + GEngine->GetPostRenderDelegate().AddRaw(this, &FVirtualHeightfieldMeshRendererExtension::EndFrame); bInit = true; } } +void FVirtualHeightfieldMeshRendererExtension::ReleaseRHI() +{ + Buffers.Empty(); +} + VirtualHeightfieldMesh::FDrawInstanceBuffers& FVirtualHeightfieldMeshRendererExtension::AddWork(FVirtualHeightfieldMeshSceneProxy const* InProxy, FSceneView const* InMainView, FSceneView const* InCullView) { // If we hit this then BegineFrame()/EndFrame() logic needs fixing in the Scene Renderer. @@ -256,7 +300,7 @@ void FVirtualHeightfieldMeshRendererExtension::BeginFrame() void FVirtualHeightfieldMeshRendererExtension::EndFrame() { - check(bInFrame); + ensure(bInFrame); bInFrame = false; SceneProxies.Reset(); @@ -294,20 +338,30 @@ FVirtualHeightfieldMeshSceneProxy::FVirtualHeightfieldMeshSceneProxy(UVirtualHei , bCallbackRegistered(false) , NumQuadsPerTileSide(0) , VertexFactory(nullptr) - , LODScale(InComponent->GetLODDistanceScale()) + , Lod0ScreenSize(InComponent->GetLod0ScreenSize()) + , Lod0Distribution(InComponent->GetLod0Distribution()) + , LodDistribution(InComponent->GetLodDistribution()) + , NumSubdivisionLODs(InComponent->GetNumSubdivisionLods()) + , NumTailLods(InComponent->GetNumTailLods()) , OcclusionData(InComponent->GetOcclusionData()) , NumOcclusionLods(InComponent->GetNumOcclusionLods()) , OcclusionGridSize(0, 0) { - GVirtualHeightfieldMeshViewUniformBufferExtension.RegisterExtension(); + GVirtualHeightfieldMeshViewRendererExtension.RegisterExtension(); const bool bValidMaterial = InComponent->GetMaterial(0) != nullptr && InComponent->GetMaterial(0)->CheckMaterialUsage_Concurrent(MATUSAGE_VirtualHeightfieldMesh); Material = bValidMaterial ? InComponent->GetMaterial(0)->GetRenderProxy() : UMaterial::GetDefaultMaterial(MD_Surface)->GetRenderProxy(); - UVToWorld = GetLocalToWorld(); + const FTransform VirtualTextureTransform = InComponent->GetVirtualTextureTransform(); + + UVToWorldScale = VirtualTextureTransform.GetScale3D(); + UVToWorld = VirtualTextureTransform.ToMatrixWithScale(); + WorldToUV = UVToWorld.Inverse(); WorldToUVTransposeAdjoint = WorldToUV.TransposeAdjoint(); + UVToLocal = UVToWorld * GetLocalToWorld().Inverse(); + BuildOcclusionVolumes(); } @@ -324,11 +378,9 @@ uint32 FVirtualHeightfieldMeshSceneProxy::GetMemoryFootprint() const void FVirtualHeightfieldMeshSceneProxy::OnTransformChanged() { - UVToWorld = GetLocalToWorld(); - WorldToUV = UVToWorld.Inverse(); - WorldToUVTransposeAdjoint = WorldToUV.TransposeAdjoint(); + UVToLocal = UVToWorld * GetLocalToWorld().Inverse(); - BuildOcclusionVolumes(); + // BuildOcclusionVolumes(); } void FVirtualHeightfieldMeshSceneProxy::CreateRenderThreadResources() @@ -401,7 +453,7 @@ void FVirtualHeightfieldMeshSceneProxy::GetDynamicMeshElements(const TArray(); - UserData->InstanceBufferSRV = Buffers.InstanceBufferSRV; - UserData->HeightPhysicalTexture = AllocatedVirtualTexture->GetPhysicalTexture(0); - UserData->PageTableSize = FIntPoint(AllocatedVirtualTexture->GetWidthInTiles(), AllocatedVirtualTexture->GetHeightInTiles()); - UserData->LocalToWorld = GetLocalToWorld(); BatchElement.UserData = (void*)UserData; - //todo: use primitive buffer + UserData->InstanceBufferSRV = Buffers.InstanceBufferSRV; + UserData->HeightPhysicalTexture = AllocatedVirtualTexture->GetPhysicalTexture(0); + + const float PageTableSizeX = AllocatedVirtualTexture->GetWidthInTiles(); + const float PageTableSizeY = AllocatedVirtualTexture->GetHeightInTiles(); + UserData->PageTableSize = FVector4(PageTableSizeX, PageTableSizeY, 1.f / PageTableSizeX, 1.f / PageTableSizeY); + + UserData->MaxLod = AllocatedVirtualTexture->GetMaxLevel() + NumTailLods; + UserData->VirtualHeightfieldToLocal = UVToLocal; + UserData->VirtualHeightfieldToWorld = UVToWorld; + + FSceneView const* MainView = ViewFamily.Views[0]; + + UserData->LodViewOrigin = MainView->ViewMatrices.GetViewOrigin(); + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + // Support the freezerendering mode. Use any frozen view state for culling. + const FViewMatrices* FrozenViewMatrices = MainView->State != nullptr ? MainView->State->GetFrozenViewMatrices() : nullptr; + if (FrozenViewMatrices != nullptr) + { + UserData->LodViewOrigin = FrozenViewMatrices->GetViewOrigin(); + } +#endif + + UserData->LodDistances = VirtualHeightfieldMesh::CalculateLodRanges(MainView, this); + BatchElement.PrimitiveIdMode = PrimID_ForceZero; - BatchElement.PrimitiveUniformBufferResource = &GIdentityPrimitiveUniformBuffer; + BatchElement.PrimitiveUniformBuffer = GetUniformBuffer(); } Collector.AddMesh(ViewIndex, Mesh); } @@ -466,7 +539,7 @@ void FVirtualHeightfieldMeshSceneProxy::BuildOcclusionVolumes() OcclusionVolumes.Reserve(OcclusionData.Num()); - const FMatrix Transform = GetLocalToWorld(); + const FMatrix Transform = UVToWorld; int32 OcclusionDataIndex = 0; for (int32 LodIndex = 0; LodIndex < NumOcclusionLods; ++LodIndex) @@ -543,16 +616,11 @@ void FVirtualHeightfieldMeshSceneProxy::AcceptOcclusionResults(FSceneView const* namespace VirtualHeightfieldMesh { - // todo: make these console configurable (or even track per frame and make dynamic) - static const int32 MaxRenderItems = 1024 * 4; - static const int32 MaxPersistentQueueItems = 1024 * 4; - static const int32 MaxFeedbackItems = 1024 * 4; - /* Keep indirect args offsets in sync with VirtualHeightfieldMesh.usf. */ static const int32 IndirectArgsByteOffset_RenderLodMap = 0; static const int32 IndirectArgsByteOffset_FetchNeighborLod = 5 * sizeof(uint32); - static const int32 IndirectArgsByteOffset_FinalCull = 9 * sizeof(uint32); - static const int32 IndirectArgsByteSize = 13 * sizeof(uint32); + static const int32 IndirectArgsByteOffset_FinalCull = 5 * sizeof(uint32); + static const int32 IndirectArgsByteSize = 9 * sizeof(uint32); /** Shader structure used for tracking work queues in persistent wave style shaders. Keep in sync with VirtualHeightfieldMesh.ush. */ struct WorkerQueueInfo @@ -562,34 +630,12 @@ namespace VirtualHeightfieldMesh int32 NumActive; }; - /** Item description used when traversing the virtual page table quad tree. Keep in sync with VirtualHeightfieldMesh.ush. */ - struct QuadItem - { - uint32 Address; - uint32 Level; - }; - - /** Description for items that are kept by the quad tree traversal stage. Keep in sync with VirtualHeightfieldMesh.ush. */ - struct QuadRenderItem - { - uint32 Address; - uint32 Level; - FVector LocalToPhysicalUV; - float Lod; - }; - - /** Description of neighbor items. We fill 4 of these for each QuadRenderItem. Keep in sync with VirtualHeightfieldMesh.ush. */ - struct QuadNeighborItem - { - FVector LocalToPhysicalUV; - float Lod; - }; - /** Final render instance description used by the DrawInstancedIndirect(). Keep in sync with VirtualHeightfieldMesh.ush. */ struct QuadRenderInstance { - QuadRenderItem Quad; - QuadNeighborItem Neighbor[4]; + uint32 AddressLevelPacked; + float UVTransform[3]; + float NeigborUVTransform[4][3]; }; /** Compute shader to initialize all buffers, including adding the lowest mip page(s) to the QuadBuffer. */ @@ -600,16 +646,17 @@ namespace VirtualHeightfieldMesh SHADER_USE_PARAMETER_STRUCT(FInitBuffersCS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) - SHADER_PARAMETER(FVector, PageTableSize) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWQuadBuffer) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWFeedbackBuffer) + SHADER_PARAMETER(uint32, MaxLevel) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWQueueInfo) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWQueueBuffer) + SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWQueueBuffer) + SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWQuadBuffer) + SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWIndirectArgsBuffer) + SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWFeedbackBuffer) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(FGlobalShaderPermutationParameters const& Parameters) { - return RHISupportsComputeShaders(Parameters.Platform); + return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } }; @@ -623,61 +670,32 @@ namespace VirtualHeightfieldMesh SHADER_USE_PARAMETER_STRUCT(FCollectQuadsCS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) - //SHADER_PARAMETER_STRUCT_INCLUDE(ShaderPrint::FShaderParameters, ShaderPrintParameters) - //SHADER_PARAMETER_STRUCT_INCLUDE(ShaderDrawDebug::FShaderDrawDebugParameters, ShaderDrawParameters) SHADER_PARAMETER_TEXTURE(Texture2D, MinMaxTexture) SHADER_PARAMETER_SAMPLER(SamplerState, MinMaxTextureSampler) SHADER_PARAMETER_TEXTURE(Texture2D, OcclusionTexture) SHADER_PARAMETER(int32, OcclusionLevelOffset) SHADER_PARAMETER_TEXTURE(Texture2D, PageTableTexture) - SHADER_PARAMETER(FVector, PageTableSize) - SHADER_PARAMETER(uint32, PageTableFeedbackId) - SHADER_PARAMETER(FMatrix, UVToWorld) - SHADER_PARAMETER(FVector, CameraPosition) - SHADER_PARAMETER(float, ProjectionScale) + SHADER_PARAMETER(FVector4, PageTableSize) + SHADER_PARAMETER(FVector4, LodDistances) + SHADER_PARAMETER(FVector, ViewOrigin) SHADER_PARAMETER_ARRAY(FVector4, FrustumPlanes, [5]) - SHADER_PARAMETER(float, LodThreshold) - SHADER_PARAMETER(FUintVector4, PageTablePackedUniform) + SHADER_PARAMETER(FMatrix, UVToWorld) + SHADER_PARAMETER(FVector, UVToWorldScale) + SHADER_PARAMETER(uint32, QueueBufferSizeMask) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWQueueInfo) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWQueueBuffer) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWQuadBuffer) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWFeedbackBuffer) + SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWQueueBuffer) + SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWQuadBuffer) + SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWIndirectArgsBuffer) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(FGlobalShaderPermutationParameters const& Parameters) { - return RHISupportsComputeShaders(Parameters.Platform); - } - - static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) - { - FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); - OutEnvironment.SetDefine(TEXT("COMPUTE_SHADER"), 1); + return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } }; IMPLEMENT_GLOBAL_SHADER(FCollectQuadsCS, "/Plugin/VirtualHeightfieldMesh/Private/VirtualHeightfieldMesh.usf", "CollectQuadsCS", SF_Compute); - /** Compute shader to build indirect args buffer used by subsequent LOD and culling steps. */ - class FBuildIndirectArgsForLodAndCullCS : public FGlobalShader - { - public: - DECLARE_GLOBAL_SHADER(FBuildIndirectArgsForLodAndCullCS); - SHADER_USE_PARAMETER_STRUCT(FBuildIndirectArgsForLodAndCullCS, FGlobalShader); - - BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) - SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, QuadBuffer) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWIndirectArgsBuffer) - END_SHADER_PARAMETER_STRUCT() - - static bool ShouldCompilePermutation(FGlobalShaderPermutationParameters const& Parameters) - { - return RHISupportsComputeShaders(Parameters.Platform); - } - }; - - IMPLEMENT_GLOBAL_SHADER(FBuildIndirectArgsForLodAndCullCS, "/Plugin/VirtualHeightfieldMesh/Private/VirtualHeightfieldMesh.usf", "BuildIndirectArgsForLodAndCullCS", SF_Compute); - /** Shader that draws to a render target the Lod info for the quads output by the Collect pass. */ class FRenderLodMap : public FGlobalShader { @@ -686,14 +704,14 @@ namespace VirtualHeightfieldMesh BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) RENDER_TARGET_BINDING_SLOTS() - SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, QuadBuffer) - SHADER_PARAMETER(FVector, PageTableSize) + SHADER_PARAMETER(FVector4, PageTableSize) + SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, QuadBuffer) SHADER_PARAMETER_RDG_BUFFER(Buffer, IndirectArgsBuffer) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(FGlobalShaderPermutationParameters const& Parameters) { - return true; + return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } }; @@ -730,30 +748,31 @@ namespace VirtualHeightfieldMesh IMPLEMENT_GLOBAL_SHADER(FRenderLodMapPS, "/Plugin/VirtualHeightfieldMesh/Private/VirtualHeightfieldMesh.usf", "RenderLodMapPS", SF_Pixel); /** */ - class FReadNeighborLodsCS : public FGlobalShader + class FResolveNeighborLodsCS : public FGlobalShader { public: - DECLARE_GLOBAL_SHADER(FReadNeighborLodsCS); - SHADER_USE_PARAMETER_STRUCT(FReadNeighborLodsCS, FGlobalShader); + DECLARE_GLOBAL_SHADER(FResolveNeighborLodsCS); + SHADER_USE_PARAMETER_STRUCT(FResolveNeighborLodsCS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) - //SHADER_PARAMETER_STRUCT_INCLUDE(ShaderPrint::FShaderParameters, ShaderPrintParameters) - SHADER_PARAMETER(FVector, PageTableSize) + SHADER_PARAMETER(FVector4, PageTableSize) SHADER_PARAMETER_TEXTURE(Texture2D, PageTableTexture) - SHADER_PARAMETER(FUintVector4, PageTablePackedUniform) - SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, QuadBuffer) + SHADER_PARAMETER(uint32, PageTableFeedbackId) + SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, QuadBuffer) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, LodTexture) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWQuadNeighborBuffer) SHADER_PARAMETER_RDG_BUFFER(Buffer, IndirectArgsBuffer) + SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, IndirectArgsBufferSRV) + SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWQuadNeighborBuffer) + SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWFeedbackBuffer) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(FGlobalShaderPermutationParameters const& Parameters) { - return RHISupportsComputeShaders(Parameters.Platform); + return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } }; - IMPLEMENT_GLOBAL_SHADER(FReadNeighborLodsCS, "/Plugin/VirtualHeightfieldMesh/Private/VirtualHeightfieldMesh.usf", "ReadNeighborLodsCS", SF_Compute); + IMPLEMENT_GLOBAL_SHADER(FResolveNeighborLodsCS, "/Plugin/VirtualHeightfieldMesh/Private/VirtualHeightfieldMesh.usf", "ResolveNeighborLodsCS", SF_Compute); /** */ class FInitInstanceBufferCS : public FGlobalShader @@ -763,66 +782,70 @@ namespace VirtualHeightfieldMesh SHADER_USE_PARAMETER_STRUCT(FInitInstanceBufferCS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWInstanceBuffer) + SHADER_PARAMETER(int32, NumIndices) + SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWIndirectArgsBuffer) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(FGlobalShaderPermutationParameters const& Parameters) { - return RHISupportsComputeShaders(Parameters.Platform); + return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } }; IMPLEMENT_GLOBAL_SHADER(FInitInstanceBufferCS, "/Plugin/VirtualHeightfieldMesh/Private/VirtualHeightfieldMesh.usf", "InitInstanceBufferCS", SF_Compute); /** */ - class FCullInstancesCS : public FGlobalShader + class FCullInstances : public FGlobalShader { public: - DECLARE_GLOBAL_SHADER(FCullInstancesCS); - SHADER_USE_PARAMETER_STRUCT(FCullInstancesCS, FGlobalShader); + SHADER_USE_PARAMETER_STRUCT(FCullInstances, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) - //SHADER_PARAMETER_STRUCT_INCLUDE(ShaderPrint::FShaderParameters, ShaderPrintParameters) SHADER_PARAMETER_TEXTURE(Texture2D, MinMaxTexture) SHADER_PARAMETER_SAMPLER(SamplerState, MinMaxTextureSampler) SHADER_PARAMETER_TEXTURE(Texture2D, PageTableTexture) - SHADER_PARAMETER(FVector, PageTableSize) - SHADER_PARAMETER(FMatrix, UVToWorld) + SHADER_PARAMETER(FVector4, PageTableSize) SHADER_PARAMETER_ARRAY(FVector4, FrustumPlanes, [5]) - SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, QuadBuffer) - SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, QuadNeighborBuffer) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWInstanceBuffer) + SHADER_PARAMETER(FVector4, PhysicalPageTransform) + SHADER_PARAMETER(uint32, NumPhysicalAddressBits) + SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, QuadBuffer) + SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, QuadNeighborBuffer) SHADER_PARAMETER_RDG_BUFFER(Buffer, IndirectArgsBuffer) + SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, IndirectArgsBufferSRV) + SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWInstanceBuffer) + SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWIndirectArgsBuffer) END_SHADER_PARAMETER_STRUCT() - - static bool ShouldCompilePermutation(FGlobalShaderPermutationParameters const& Parameters) - { - return RHISupportsComputeShaders(Parameters.Platform); - } }; - IMPLEMENT_GLOBAL_SHADER(FCullInstancesCS, "/Plugin/VirtualHeightfieldMesh/Private/VirtualHeightfieldMesh.usf", "CullInstancesCS", SF_Compute); - - /** */ - class FBuildIndirectArgsForDrawCS : public FGlobalShader + template< bool bReuseCull > + class TCullInstancesCS : public FCullInstances { public: - DECLARE_GLOBAL_SHADER(FBuildIndirectArgsForDrawCS); - SHADER_USE_PARAMETER_STRUCT(FBuildIndirectArgsForDrawCS, FGlobalShader); + typedef TCullInstancesCS< bReuseCull > ClassName; + DECLARE_GLOBAL_SHADER(ClassName); - BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) - SHADER_PARAMETER(int, NumIndices) - SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, InstanceBuffer) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, RWIndirectArgsBuffer) - END_SHADER_PARAMETER_STRUCT() + TCullInstancesCS() + {} + + TCullInstancesCS(const ShaderMetaType::CompiledShaderInitializerType& Initializer) + : FCullInstances(Initializer) + {} static bool ShouldCompilePermutation(FGlobalShaderPermutationParameters const& Parameters) { - return RHISupportsComputeShaders(Parameters.Platform); + return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); + } + + static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) + { + FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); + OutEnvironment.SetDefine(TEXT("REUSE_CULL"), bReuseCull ? 1 : 0); } }; - IMPLEMENT_GLOBAL_SHADER(FBuildIndirectArgsForDrawCS, "/Plugin/VirtualHeightfieldMesh/Private/VirtualHeightfieldMesh.usf", "BuildIndirectArgsForDrawCS", SF_Compute); + IMPLEMENT_SHADER_TYPE(template<>, TCullInstancesCS, TEXT("/Plugin/VirtualHeightfieldMesh/Private/VirtualHeightfieldMesh.usf"), TEXT("CullInstancesCS"), SF_Compute); + IMPLEMENT_SHADER_TYPE(template<>, TCullInstancesCS, TEXT("/Plugin/VirtualHeightfieldMesh/Private/VirtualHeightfieldMesh.usf"), TEXT("CullInstancesCS"), SF_Compute); + /** Default Min/Max texture has the fixed maximum [0,1]. */ class FMinMaxDefaultTexture : public FTexture @@ -852,12 +875,51 @@ namespace VirtualHeightfieldMesh /** Single global instance of default Min/Max texture. */ FTexture* GMinMaxDefaultTexture = new TGlobalResource; + /** */ + struct FViewData + { + FVector ViewOrigin; + FMatrix ProjectionMatrix; + FConvexVolume ViewFrustum; + bool bViewFrozen; + }; + + /** Fill the FViewData from an FSceneView respecting the freezerendering mode. */ + void GetViewData(FSceneView const* InSceneView, FViewData& OutViewData) + { +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + const FViewMatrices* FrozenViewMatrices = InSceneView->State != nullptr ? InSceneView->State->GetFrozenViewMatrices() : nullptr; + if (FrozenViewMatrices != nullptr) + { + OutViewData.ViewOrigin = FrozenViewMatrices->GetViewOrigin(); + OutViewData.ProjectionMatrix = FrozenViewMatrices->GetProjectionMatrix(); + GetViewFrustumBounds(OutViewData.ViewFrustum, FrozenViewMatrices->GetViewProjectionMatrix(), true); + OutViewData.bViewFrozen = true; + } + else +#endif + { + OutViewData.ViewOrigin = InSceneView->ViewMatrices.GetViewOrigin(); + OutViewData.ProjectionMatrix = InSceneView->ViewMatrices.GetProjectionMatrix(); + OutViewData.ViewFrustum = InSceneView->ViewFrustum; + OutViewData.bViewFrozen = false; + } + } + /** Convert FPlane to Xx+Yy+Zz+W=0 form for simpler use in shader. */ FVector4 ConvertPlane(FPlane const& Plane) { return FVector4(-Plane.X, -Plane.Y, -Plane.Z, Plane.W); } + /** Translate a plane. This is a simpler case than the full TransformPlane(). */ + FPlane TranslatePlane(FPlane const& Plane, FVector const& Translation) + { + FPlane OutPlane = Plane / Plane.Size(); + OutPlane.W -= FVector::DotProduct(FVector(OutPlane), Translation); + return OutPlane; + } + /** Transform a plane using a transform matrix. Precalculate and pass in transpose adjoint to avoid work when transforming multiple planes. */ FPlane TransformPlane(FPlane const& Plane, FMatrix const& Matrix, FMatrix const& TransposeAdjoint) { @@ -875,24 +937,28 @@ namespace VirtualHeightfieldMesh { FRHITexture* PageTableTexture; FRHITexture* MinMaxTexture; - FVector PageTableSize; - FUintVector4 PageTablePackedUniform; + + uint32 MaxLevel; uint32 PageTableFeedbackId; + uint32 NumPhysicalAddressBits; + FVector4 PageTableSize; + FVector4 PhysicalPageTransform; FMatrix UVToWorld; + FVector UVToWorldScale; uint32 NumQuadsPerTileSide; - float LodScale; int32 MaxPersistentQueueItems; + int32 MaxRenderItems; int32 MaxFeedbackItems; + int32 NumCollectPassWavefronts; }; /** View description used for LOD calculation in the main view. */ struct FMainViewDesc { FSceneView const* ViewDebug; - FVector CameraPosition; - float ProjectionScale; - float LodThreshold; + FVector ViewOrigin; + FVector4 LodDistances; FVector4 Planes[5]; FTextureRHIRef OcclusionTexture; int32 OcclusionLevelOffset; @@ -902,6 +968,7 @@ namespace VirtualHeightfieldMesh struct FChildViewDesc { FSceneView const* ViewDebug; + bool bIsMainView; FVector4 Planes[5]; }; @@ -922,6 +989,7 @@ namespace VirtualHeightfieldMesh FRDGBufferRef IndirectArgsBuffer; FRDGBufferUAVRef IndirectArgsBufferUAV; + FRDGBufferSRVRef IndirectArgsBufferSRV; FRDGTextureRef LodTexture; @@ -953,7 +1021,8 @@ namespace VirtualHeightfieldMesh // An alternative is use standard RHI allocation, but then we need to be manage resource transitions. FRDGBuilder GraphBuilder(InRHICmdList); - FRDGBufferRef InstanceBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(VirtualHeightfieldMesh::QuadRenderInstance), VirtualHeightfieldMesh::MaxRenderItems + 1), TEXT("QuadBuffer")); + int32 InstanceBufferSize = CVarVHMMaxRenderItems.GetValueOnRenderThread(); + FRDGBufferRef InstanceBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(VirtualHeightfieldMesh::QuadRenderInstance), InstanceBufferSize), TEXT("InstanceBuffer")); FRDGBufferRef IndirectArgsBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(5), TEXT("IndirectArgsBuffer")); FCreateBufferParameters* Parameters = GraphBuilder.AllocParameters(); @@ -984,12 +1053,12 @@ namespace VirtualHeightfieldMesh { OutResources.QueueInfo = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(WorkerQueueInfo), 1), TEXT("QueueInfo")); OutResources.QueueInfoUAV = GraphBuilder.CreateUAV(OutResources.QueueInfo); - OutResources.QueueBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(QuadItem), InDesc.MaxPersistentQueueItems), TEXT("QuadQueue")); - OutResources.QueueBufferUAV = GraphBuilder.CreateUAV(OutResources.QueueBuffer); + OutResources.QueueBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), InDesc.MaxPersistentQueueItems), TEXT("QuadQueue")); + OutResources.QueueBufferUAV = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(OutResources.QueueBuffer, PF_R32_UINT)); - OutResources.QuadBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(VirtualHeightfieldMesh::QuadRenderItem), VirtualHeightfieldMesh::MaxRenderItems + 1), TEXT("QuadBuffer")); - OutResources.QuadBufferUAV = GraphBuilder.CreateUAV(OutResources.QuadBuffer); - OutResources.QuadBufferSRV = GraphBuilder.CreateSRV(OutResources.QuadBuffer); + OutResources.QuadBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32) * 2, InDesc.MaxRenderItems), TEXT("QuadBuffer")); + OutResources.QuadBufferUAV = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(OutResources.QuadBuffer, PF_R32G32_UINT)); + OutResources.QuadBufferSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(OutResources.QuadBuffer, PF_R32G32_UINT)); FRDGBufferDesc FeedbackBufferDesc = FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), InDesc.MaxFeedbackItems + 1); FeedbackBufferDesc.Usage = EBufferUsageFlags(FeedbackBufferDesc.Usage | BUF_SourceCopy); @@ -998,6 +1067,7 @@ namespace VirtualHeightfieldMesh OutResources.IndirectArgsBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(IndirectArgsByteSize), TEXT("IndirectArgsBuffer")); OutResources.IndirectArgsBufferUAV = GraphBuilder.CreateUAV(OutResources.IndirectArgsBuffer); + OutResources.IndirectArgsBufferSRV = GraphBuilder.CreateSRV(OutResources.IndirectArgsBuffer); FPooledRenderTargetDesc LodTextureDesc = FPooledRenderTargetDesc::Create2DDesc( FIntPoint(InDesc.PageTableSize.X, InDesc.PageTableSize.Y), @@ -1006,9 +1076,9 @@ namespace VirtualHeightfieldMesh TexCreate_None, TexCreate_RenderTargetable | TexCreate_ShaderResource, false); OutResources.LodTexture = GraphBuilder.CreateTexture(LodTextureDesc, TEXT("LodTexture")); - OutResources.QuadNeighborBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(VirtualHeightfieldMesh::QuadNeighborItem), VirtualHeightfieldMesh::MaxRenderItems * 4), TEXT("QuadNeighborBuffer")); - OutResources.QuadNeighborBufferUAV = GraphBuilder.CreateUAV(OutResources.QuadNeighborBuffer); - OutResources.QuadNeighborBufferSRV = GraphBuilder.CreateSRV(OutResources.QuadNeighborBuffer); + OutResources.QuadNeighborBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), InDesc.MaxRenderItems * 4), TEXT("QuadNeighborBuffer")); + OutResources.QuadNeighborBufferUAV = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(OutResources.QuadNeighborBuffer, PF_R32_UINT)); + OutResources.QuadNeighborBufferSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(OutResources.QuadNeighborBuffer, PF_R32_UINT)); } /** Initialize the output resources used in the render graph. */ @@ -1028,11 +1098,12 @@ namespace VirtualHeightfieldMesh TShaderMapRef ComputeShader(InGlobalShaderMap); FInitBuffersCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); - PassParameters->PageTableSize = InDesc.PageTableSize; - PassParameters->RWQuadBuffer = InVolatileResources.QuadBufferUAV; - PassParameters->RWFeedbackBuffer = InVolatileResources.FeedbackBufferUAV; + PassParameters->MaxLevel = InDesc.MaxLevel; PassParameters->RWQueueInfo = InVolatileResources.QueueInfoUAV; PassParameters->RWQueueBuffer = InVolatileResources.QueueBufferUAV; + PassParameters->RWQuadBuffer = InVolatileResources.QuadBufferUAV; + PassParameters->RWIndirectArgsBuffer = InVolatileResources.IndirectArgsBufferUAV; + PassParameters->RWFeedbackBuffer = InVolatileResources.FeedbackBufferUAV; GraphBuilder.AddPass( RDG_EVENT_NAME("InitBuffers"), @@ -1054,48 +1125,30 @@ namespace VirtualHeightfieldMesh TShaderMapRef ComputeShader(InGlobalShaderMap); FCollectQuadsCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); - //ShaderPrint::SetParameters(*InViewDesc.ViewDebug, PassParameters->ShaderPrintParameters); - //ShaderDrawDebug::SetParameters(GraphBuilder, *InViewDesc.ViewDebug, PassParameters->ShaderDrawParameters); PassParameters->MinMaxTexture = InDesc.MinMaxTexture; PassParameters->MinMaxTextureSampler = TStaticSamplerState::GetRHI(); PassParameters->OcclusionTexture = InViewDesc.OcclusionTexture; PassParameters->OcclusionLevelOffset = InViewDesc.OcclusionLevelOffset; PassParameters->PageTableTexture = InDesc.PageTableTexture; PassParameters->PageTableSize = InDesc.PageTableSize; - PassParameters->PageTableFeedbackId = InDesc.PageTableFeedbackId; PassParameters->UVToWorld = InDesc.UVToWorld; - PassParameters->CameraPosition = InViewDesc.CameraPosition; - PassParameters->ProjectionScale = InViewDesc.ProjectionScale; + PassParameters->UVToWorldScale = InDesc.UVToWorldScale; + PassParameters->ViewOrigin = InViewDesc.ViewOrigin; + PassParameters->LodDistances = InViewDesc.LodDistances; for (int32 PlaneIndex = 0; PlaneIndex < 5; ++PlaneIndex) { PassParameters->FrustumPlanes[PlaneIndex] = InViewDesc.Planes[PlaneIndex]; } - PassParameters->LodThreshold = InDesc.LodScale * InViewDesc.LodThreshold; - PassParameters->PageTablePackedUniform = InDesc.PageTablePackedUniform; + PassParameters->QueueBufferSizeMask = InDesc.MaxPersistentQueueItems - 1; // Assumes MaxPersistentQueueItems is a power of 2 so that we can wrap with a mask. PassParameters->RWQueueInfo = InVolatileResources.QueueInfoUAV; PassParameters->RWQueueBuffer = InVolatileResources.QueueBufferUAV; PassParameters->RWQuadBuffer = InVolatileResources.QuadBufferUAV; - PassParameters->RWFeedbackBuffer = InVolatileResources.FeedbackBufferUAV; - - FComputeShaderUtils::AddPass( - GraphBuilder, - RDG_EVENT_NAME("CollectQuads"), - ComputeShader, PassParameters, FIntVector(128, 1, 1)); - } - - /** */ - void AddPass_BuildIndirectArgsForLodAndCull(FRDGBuilder& GraphBuilder, FGlobalShaderMap* InGlobalShaderMap, FProxyDesc const& InDesc, FVolatileResources& InVolatileResources) - { - TShaderMapRef ComputeShader(InGlobalShaderMap); - - FBuildIndirectArgsForLodAndCullCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); - PassParameters->QuadBuffer = InVolatileResources.QuadBufferSRV; PassParameters->RWIndirectArgsBuffer = InVolatileResources.IndirectArgsBufferUAV; FComputeShaderUtils::AddPass( GraphBuilder, - RDG_EVENT_NAME("BuildIndirectArgs"), - ComputeShader, PassParameters, FIntVector(1, 1, 1)); + RDG_EVENT_NAME("CollectQuads"), + ComputeShader, PassParameters, FIntVector(InDesc.NumCollectPassWavefronts, 1, 1)); } /** */ @@ -1138,22 +1191,23 @@ namespace VirtualHeightfieldMesh } /** */ - void AddPass_ReadNeighborLods(FRDGBuilder& GraphBuilder, FGlobalShaderMap* InGlobalShaderMap, FProxyDesc const& InDesc, FVolatileResources& InVolatileResources, FMainViewDesc const& InViewDesc) + void AddPass_ResolveNeighborLods(FRDGBuilder& GraphBuilder, FGlobalShaderMap* InGlobalShaderMap, FProxyDesc const& InDesc, FVolatileResources& InVolatileResources, FMainViewDesc const& InViewDesc) { - TShaderMapRef ComputeShader(InGlobalShaderMap); + TShaderMapRef ComputeShader(InGlobalShaderMap); - FReadNeighborLodsCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); - //ShaderPrint::SetParameters(*InViewDesc.ViewDebug, PassParameters->ShaderPrintParameters); + FResolveNeighborLodsCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->PageTableSize = InDesc.PageTableSize; + PassParameters->PageTableFeedbackId = InDesc.PageTableFeedbackId; PassParameters->PageTableTexture = InDesc.PageTableTexture; - PassParameters->PageTablePackedUniform = InDesc.PageTablePackedUniform; PassParameters->QuadBuffer = InVolatileResources.QuadBufferSRV; PassParameters->LodTexture = InVolatileResources.LodTexture; - PassParameters->RWQuadNeighborBuffer = InVolatileResources.QuadNeighborBufferUAV; PassParameters->IndirectArgsBuffer = InVolatileResources.IndirectArgsBuffer; + PassParameters->IndirectArgsBufferSRV = InVolatileResources.IndirectArgsBufferSRV; + PassParameters->RWQuadNeighborBuffer = InVolatileResources.QuadNeighborBufferUAV; + PassParameters->RWFeedbackBuffer = InVolatileResources.FeedbackBufferUAV; GraphBuilder.AddPass( - RDG_EVENT_NAME("ReadNeighborLods"), + RDG_EVENT_NAME("ResolveNeighborLods"), PassParameters, ERDGPassFlags::Compute, [PassParameters, ComputeShader, IndirectArgsBuffer = InVolatileResources.IndirectArgsBuffer](FRHICommandList& RHICmdList) @@ -1170,7 +1224,8 @@ namespace VirtualHeightfieldMesh TShaderMapRef ComputeShader(InGlobalShaderMap); FInitInstanceBufferCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); - PassParameters->RWInstanceBuffer = InOutputResources.InstanceBufferUAV; + PassParameters->NumIndices = InDesc.NumQuadsPerTileSide * InDesc.NumQuadsPerTileSide * 6; + PassParameters->RWIndirectArgsBuffer = InOutputResources.IndirectArgsBufferUAV; FComputeShaderUtils::AddPass( GraphBuilder, @@ -1181,23 +1236,33 @@ namespace VirtualHeightfieldMesh /** */ void AddPass_CullInstances(FRDGBuilder& GraphBuilder, FGlobalShaderMap* InGlobalShaderMap, FProxyDesc const& InDesc, FVolatileResources& InVolatileResources, FOutputResources& InOutputResources, FChildViewDesc const& InViewDesc) { - TShaderMapRef ComputeShader(InGlobalShaderMap); - - FCullInstancesCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); - //ShaderPrint::SetParameters(*InViewDesc.ViewDebug, PassParameters->ShaderPrintParameters); + FCullInstances::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->MinMaxTexture = InDesc.MinMaxTexture; PassParameters->MinMaxTextureSampler = TStaticSamplerState::GetRHI(); PassParameters->PageTableTexture = InDesc.PageTableTexture; PassParameters->PageTableSize = InDesc.PageTableSize; - PassParameters->UVToWorld = InDesc.UVToWorld; for (int32 PlaneIndex = 0; PlaneIndex < 5; ++PlaneIndex) { PassParameters->FrustumPlanes[PlaneIndex] = InViewDesc.Planes[PlaneIndex]; } + PassParameters->PhysicalPageTransform = InDesc.PhysicalPageTransform; + PassParameters->NumPhysicalAddressBits = InDesc.NumPhysicalAddressBits; PassParameters->QuadBuffer = InVolatileResources.QuadBufferSRV; PassParameters->QuadNeighborBuffer = InVolatileResources.QuadNeighborBufferSRV; - PassParameters->RWInstanceBuffer = InOutputResources.InstanceBufferUAV; PassParameters->IndirectArgsBuffer = InVolatileResources.IndirectArgsBuffer; + PassParameters->IndirectArgsBufferSRV = InVolatileResources.IndirectArgsBufferSRV; + PassParameters->RWInstanceBuffer = InOutputResources.InstanceBufferUAV; + PassParameters->RWIndirectArgsBuffer = InOutputResources.IndirectArgsBufferUAV; + + TShaderRef ComputeShader; + if (InViewDesc.bIsMainView) + { + ComputeShader = InGlobalShaderMap->GetShader< TCullInstancesCS >(); + } + else + { + ComputeShader = InGlobalShaderMap->GetShader< TCullInstancesCS >(); + } GraphBuilder.AddPass( RDG_EVENT_NAME("CullInstances"), @@ -1211,22 +1276,6 @@ namespace VirtualHeightfieldMesh }); } - /** */ - void AddPass_BuildFinalIndirectArgsForDraw(FRDGBuilder& GraphBuilder, FGlobalShaderMap* InGlobalShaderMap, FProxyDesc const& InDesc, FVolatileResources& InVolatileResources, FOutputResources& InOutputResources) - { - TShaderMapRef ComputeShader(InGlobalShaderMap); - - FBuildIndirectArgsForDrawCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); - PassParameters->NumIndices = InDesc.NumQuadsPerTileSide * InDesc.NumQuadsPerTileSide * 6; - PassParameters->InstanceBuffer = InOutputResources.InstanceBufferSRV; - PassParameters->RWIndirectArgsBuffer = InOutputResources.IndirectArgsBufferUAV; - - FComputeShaderUtils::AddPass( - GraphBuilder, - RDG_EVENT_NAME("BuildIndirectArgs"), - ComputeShader, PassParameters, FIntVector(1, 1, 1)); - } - /** */ void GPUCullMainView( FRDGBuilder& GraphBuilder, FGlobalShaderMap* InGlobalShaderMap, FProxyDesc const& InDesc, FVolatileResources& InVolatileResources, FMainViewDesc const& InViewDesc) { @@ -1234,9 +1283,8 @@ namespace VirtualHeightfieldMesh AddPass_InitBuffers(GraphBuilder, InGlobalShaderMap, InDesc, InVolatileResources); AddPass_CollectQuads(GraphBuilder, InGlobalShaderMap, InDesc, InVolatileResources, InViewDesc); - AddPass_BuildIndirectArgsForLodAndCull(GraphBuilder, InGlobalShaderMap, InDesc, InVolatileResources); AddPass_RenderLodMap(GraphBuilder, InGlobalShaderMap, InDesc, InVolatileResources); - AddPass_ReadNeighborLods(GraphBuilder, InGlobalShaderMap, InDesc, InVolatileResources, InViewDesc); + AddPass_ResolveNeighborLods(GraphBuilder, InGlobalShaderMap, InDesc, InVolatileResources, InViewDesc); } /** */ @@ -1246,7 +1294,6 @@ namespace VirtualHeightfieldMesh AddPass_InitInstanceBuffer(GraphBuilder, InGlobalShaderMap, InDesc, InVolatileResources, InOutputResources); AddPass_CullInstances(GraphBuilder, InGlobalShaderMap, InDesc, InVolatileResources, InOutputResources, InViewDesc); - AddPass_BuildFinalIndirectArgsForDraw(GraphBuilder, InGlobalShaderMap, InDesc, InVolatileResources, InOutputResources); } } @@ -1272,60 +1319,66 @@ void FVirtualHeightfieldMeshRendererExtension::SubmitWork(FRHICommandListImmedia FVirtualHeightfieldMeshSceneProxy const* Proxy = SceneProxies[WorkDescs[WorkIndex].ProxyIndex]; IAllocatedVirtualTexture* AllocatedVirtualTexture = Proxy->AllocatedVirtualTexture; - FUintVector4 PageTablePackedUniform; - AllocatedVirtualTexture->GetPackedUniform(&PageTablePackedUniform, 0); + const float PageSize = AllocatedVirtualTexture->GetVirtualTileSize(); + const float PageBorderSize = AllocatedVirtualTexture->GetTileBorderSize(); + const float PageAndBorderSize = PageSize + PageBorderSize * 2.f; + const float HalfTexelSize = 0.5f; + const float PhysicalTextureSize = AllocatedVirtualTexture->GetPhysicalTextureSize(0); + const FVector4 PhysicalPageTransform = FVector4(PageAndBorderSize, PageSize, PageBorderSize, HalfTexelSize) * (1.f / PhysicalTextureSize); + + const float PageTableSizeX = AllocatedVirtualTexture->GetWidthInTiles(); + const float PageTableSizeY = AllocatedVirtualTexture->GetHeightInTiles(); + const FVector4 PageTableSize = FVector4(PageTableSizeX, PageTableSizeY, 1.f / PageTableSizeX, 1.f / PageTableSizeY); VirtualHeightfieldMesh::FProxyDesc ProxyDesc; ProxyDesc.PageTableTexture = AllocatedVirtualTexture->GetPageTableTexture(0); ProxyDesc.MinMaxTexture = Proxy->MinMaxTexture ? Proxy->MinMaxTexture->Resource->TextureRHI : VirtualHeightfieldMesh::GMinMaxDefaultTexture->TextureRHI;; - ProxyDesc.PageTableSize = FVector(AllocatedVirtualTexture->GetWidthInTiles(), AllocatedVirtualTexture->GetHeightInTiles(), AllocatedVirtualTexture->GetMaxLevel()); - ProxyDesc.PageTablePackedUniform = PageTablePackedUniform; + ProxyDesc.MaxLevel = AllocatedVirtualTexture->GetMaxLevel(); + ProxyDesc.PageTableSize = PageTableSize; + ProxyDesc.PhysicalPageTransform = PhysicalPageTransform; + ProxyDesc.NumPhysicalAddressBits = AllocatedVirtualTexture->GetPageTableFormat() == EVTPageTableFormat::UInt16 ? 6 : 8; // See packing in PageTableUpdate.usf ProxyDesc.PageTableFeedbackId = AllocatedVirtualTexture->GetSpaceID() << 28; ProxyDesc.UVToWorld = Proxy->UVToWorld; + ProxyDesc.UVToWorldScale = Proxy->UVToWorldScale; ProxyDesc.NumQuadsPerTileSide = Proxy->NumQuadsPerTileSide; - ProxyDesc.LodScale = FMath::Max(Proxy->LODScale * CVarVHMLodScale.GetValueOnRenderThread(), 0.01f); - ProxyDesc.MaxPersistentQueueItems = VirtualHeightfieldMesh::MaxPersistentQueueItems; - ProxyDesc.MaxFeedbackItems = VirtualHeightfieldMesh::MaxFeedbackItems; + ProxyDesc.MaxPersistentQueueItems = 1 << FMath::CeilLogTwo(CVarVHMMaxPersistentQueueItems.GetValueOnRenderThread()); + ProxyDesc.MaxRenderItems = CVarVHMMaxRenderItems.GetValueOnRenderThread(); + ProxyDesc.MaxFeedbackItems = CVarVHMMaxFeedbackItems.GetValueOnRenderThread(); + ProxyDesc.NumCollectPassWavefronts = CVarVHMCollectPassWavefronts.GetValueOnRenderThread(); while (WorkIndex < NumWorkItems && SceneProxies[WorkDescs[WorkIndex].ProxyIndex] == Proxy) { // Gather data per main view FSceneView const* MainView = MainViews[WorkDescs[WorkIndex].MainViewIndex]; - - static FMatrix ProjectionMatrix; - static FConvexVolume ViewFrustum; - static FVector CameraPosition; - if (CVarVHMFixCullingCamera.GetValueOnRenderThread() == 0) - { - ProjectionMatrix = MainView->ViewMatrices.GetProjectionMatrix(); - ViewFrustum = MainView->ViewFrustum; - CameraPosition = MainView->ViewMatrices.GetViewOrigin(); - } - - const float TargetPixelsPerTri = 64.f; + + VirtualHeightfieldMesh::FViewData MainViewData; + VirtualHeightfieldMesh::GetViewData(MainView, MainViewData); VirtualHeightfieldMesh::FMainViewDesc MainViewDesc; MainViewDesc.ViewDebug = MainView; - MainViewDesc.CameraPosition = CameraPosition; - MainViewDesc.ProjectionScale = FMath::Max(0.5f * ProjectionMatrix.M[0][0], 0.5f * ProjectionMatrix.M[1][1]); - MainViewDesc.LodThreshold = MainView->LODDistanceFactor * TargetPixelsPerTri * (Proxy->NumQuadsPerTileSide * Proxy->NumQuadsPerTileSide * 2) / MainView->UnconstrainedViewRect.Area(); - + + // ViewOrigin and Frustum Planes are all converted to UV space for the shader. + MainViewDesc.ViewOrigin = Proxy->WorldToUV.TransformPosition(MainViewData.ViewOrigin); + MainViewDesc.LodDistances = VirtualHeightfieldMesh::CalculateLodRanges(MainView, Proxy); + for (int32 PlaneIndex = 0; PlaneIndex < 5; ++PlaneIndex) { - const int32 CopyPlaneIndex = FMath::Min(PlaneIndex, ViewFrustum.Planes.Num() - 1); - MainViewDesc.Planes[PlaneIndex] = VirtualHeightfieldMesh::ConvertPlane(VirtualHeightfieldMesh::TransformPlane(ViewFrustum.Planes[CopyPlaneIndex], Proxy->WorldToUV, Proxy->WorldToUVTransposeAdjoint)); + const int32 CopyPlaneIndex = FMath::Min(PlaneIndex, MainViewData.ViewFrustum.Planes.Num() - 1); + FPlane Plane = MainViewData.ViewFrustum.Planes[CopyPlaneIndex]; + Plane = VirtualHeightfieldMesh::TransformPlane(Plane, Proxy->WorldToUV, Proxy->WorldToUVTransposeAdjoint); + MainViewDesc.Planes[PlaneIndex] = VirtualHeightfieldMesh::ConvertPlane(Plane); } FOcclusionResults* OcclusionResults = GOcclusionResults.Find(FOcclusionResultsKey(Proxy, MainView)); if (OcclusionResults == nullptr) { MainViewDesc.OcclusionTexture = GBlackTexture->TextureRHI; - MainViewDesc.OcclusionLevelOffset = ProxyDesc.PageTableSize.Z; + MainViewDesc.OcclusionLevelOffset = (int32)ProxyDesc.MaxLevel; } else { MainViewDesc.OcclusionTexture = OcclusionResults->OcclusionTexture; - MainViewDesc.OcclusionLevelOffset = ProxyDesc.PageTableSize.Z - OcclusionResults->NumTextureMips + 1; + MainViewDesc.OcclusionLevelOffset = (int32)ProxyDesc.MaxLevel - OcclusionResults->NumTextureMips + 1; } // Build volatile graph resources @@ -1344,15 +1397,20 @@ void FVirtualHeightfieldMeshRendererExtension::SubmitWork(FRHICommandListImmedia // Gather data per child view FSceneView const* CullView = CullViews[WorkDescs[WorkIndex].CullViewIndex]; FConvexVolume const* ShadowFrustum = CullView->GetDynamicMeshElementsShadowCullFrustum(); - FConvexVolume const& Frustum = ShadowFrustum != nullptr && ShadowFrustum->Planes.Num() > 0 ? *ShadowFrustum : (CVarVHMFixCullingCamera.GetValueOnRenderThread() == 0) ? CullView->ViewFrustum : ViewFrustum; + FConvexVolume const& Frustum = ShadowFrustum != nullptr && ShadowFrustum->Planes.Num() > 0 ? *ShadowFrustum : CullView->ViewFrustum; + const FVector PreShadowTranslation = ShadowFrustum != nullptr ? CullView->GetPreShadowTranslation() : FVector::ZeroVector; VirtualHeightfieldMesh::FChildViewDesc ChildViewDesc; ChildViewDesc.ViewDebug = MainView; + ChildViewDesc.bIsMainView = CullView == MainView; for (int32 PlaneIndex = 0; PlaneIndex < 5; ++PlaneIndex) { const int32 CopyPlaneIndex = FMath::Min(PlaneIndex, Frustum.Planes.Num() - 1); - ChildViewDesc.Planes[PlaneIndex] = VirtualHeightfieldMesh::ConvertPlane(VirtualHeightfieldMesh::TransformPlane(Frustum.Planes[CopyPlaneIndex], Proxy->WorldToUV, Proxy->WorldToUVTransposeAdjoint)); + FPlane Plane = Frustum.Planes[CopyPlaneIndex]; + Plane = VirtualHeightfieldMesh::TranslatePlane(Plane, PreShadowTranslation); + Plane = VirtualHeightfieldMesh::TransformPlane(Plane, Proxy->WorldToUV, Proxy->WorldToUVTransposeAdjoint); + ChildViewDesc.Planes[PlaneIndex] = VirtualHeightfieldMesh::ConvertPlane(Plane); } // Build output graph resources @@ -1377,10 +1435,8 @@ void FVirtualHeightfieldMeshRendererExtension::SubmitWork(FRHICommandListImmedia for (int32 FeedbackIndex = 0; FeedbackIndex < FeedbackBuffers.Num(); ++FeedbackIndex) { FVirtualTextureFeedbackBufferDesc Desc; - Desc.Init(VirtualHeightfieldMesh::MaxFeedbackItems + 1); + Desc.Init(CVarVHMMaxFeedbackItems.GetValueOnRenderThread() + 1); SubmitVirtualTextureFeedbackBuffer(GraphBuilder.RHICmdList, FeedbackBuffers[0].GetReference()->VertexBuffer, Desc); } } } - -PRAGMA_ENABLE_OPTIMIZATION diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshVertexFactory.cpp b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshVertexFactory.cpp index 6409ba9ede4d..e31f89d185ed 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshVertexFactory.cpp +++ b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshVertexFactory.cpp @@ -103,7 +103,11 @@ public: HeightTextureParameter.Bind(ParameterMap, TEXT("HeightTexture")); HeightSamplerParameter.Bind(ParameterMap, TEXT("HeightSampler")); PageTableSizeParameter.Bind(ParameterMap, TEXT("PageTableSize")); - LocalToWorldParameter.Bind(ParameterMap, TEXT("LocalToWorld")); + MaxLodParameter.Bind(ParameterMap, TEXT("MaxLod")); + VirtualHeightfieldToLocalParameter.Bind(ParameterMap, TEXT("VirtualHeightfieldToLocal")); + VirtualHeightfieldToWorldParameter.Bind(ParameterMap, TEXT("VirtualHeightfieldToWorld")); + LodViewOriginParameter.Bind(ParameterMap, TEXT("LodViewOrigin")); + LodDistancesParameter.Bind(ParameterMap, TEXT("LodDistances")); } void GetElementShaderBindings( @@ -136,9 +140,25 @@ public: { ShaderBindings.Add(PageTableSizeParameter, UserData->PageTableSize); } - if (LocalToWorldParameter.IsBound()) + if (MaxLodParameter.IsBound()) { - ShaderBindings.Add(LocalToWorldParameter, UserData->LocalToWorld); + ShaderBindings.Add(MaxLodParameter, UserData->MaxLod); + } + if (VirtualHeightfieldToLocalParameter.IsBound()) + { + ShaderBindings.Add(VirtualHeightfieldToLocalParameter, UserData->VirtualHeightfieldToLocal); + } + if (VirtualHeightfieldToWorldParameter.IsBound()) + { + ShaderBindings.Add(VirtualHeightfieldToWorldParameter, UserData->VirtualHeightfieldToWorld); + } + if (LodViewOriginParameter.IsBound()) + { + ShaderBindings.Add(LodViewOriginParameter, UserData->LodViewOrigin); + } + if (LodDistancesParameter.IsBound()) + { + ShaderBindings.Add(LodDistancesParameter, UserData->LodDistances); } } } @@ -148,7 +168,11 @@ protected: LAYOUT_FIELD(FShaderResourceParameter, HeightTextureParameter); LAYOUT_FIELD(FShaderResourceParameter, HeightSamplerParameter); LAYOUT_FIELD(FShaderParameter, PageTableSizeParameter); - LAYOUT_FIELD(FShaderParameter, LocalToWorldParameter); + LAYOUT_FIELD(FShaderParameter, MaxLodParameter); + LAYOUT_FIELD(FShaderParameter, VirtualHeightfieldToLocalParameter); + LAYOUT_FIELD(FShaderParameter, VirtualHeightfieldToWorldParameter); + LAYOUT_FIELD(FShaderParameter, LodViewOriginParameter); + LAYOUT_FIELD(FShaderParameter, LodDistancesParameter); }; IMPLEMENT_VERTEX_FACTORY_PARAMETER_TYPE(FVirtualHeightfieldMeshVertexFactory, SF_Vertex, FVirtualHeightfieldMeshVertexFactoryShaderParameters); @@ -219,15 +243,19 @@ void FVirtualHeightfieldMeshVertexFactory::ReleaseRHI() bool FVirtualHeightfieldMeshVertexFactory::ShouldCompilePermutation(const FVertexFactoryShaderPermutationParameters& Parameters) { + //todo[vhm]: Fallback path for mobile. + if (!IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5)) + { + return false; + } return (Parameters.MaterialParameters.MaterialDomain == MD_Surface && Parameters.MaterialParameters.bIsUsedWithVirtualHeightfieldMesh) || Parameters.MaterialParameters.bIsSpecialEngineMaterial; } void FVirtualHeightfieldMeshVertexFactory::ModifyCompilationEnvironment(const FVertexFactoryShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { - OutEnvironment.SetDefine(TEXT("VIRTUAL_HEIGHTFIELD_MESH_FACTORY"), 1); - OutEnvironment.SetDefine(TEXT("VF_SUPPORTS_DISPLACEMENT"), 1); + OutEnvironment.SetDefine(TEXT("VF_VIRTUAL_HEIGHFIELD_MESH"), 1); #if 0 - OutEnvironment.SetDefine(TEXT("VF_SUPPORTS_PRIMITIVE_SCENE_DATA"), Parameters.VertexFactoryType->SupportsPrimitiveIdStream() && UseGPUScene(Platform, GetMaxSupportedFeatureLevel(Platform))); + OutEnvironment.SetDefine(TEXT("VF_SUPPORTS_PRIMITIVE_SCENE_DATA"), Parameters.VertexFactoryType->SupportsPrimitiveIdStream() && UseGPUScene(Parameters.Platform, GetMaxSupportedFeatureLevel(Parameters.Platform))); #endif } @@ -243,9 +271,4 @@ void FVirtualHeightfieldMeshVertexFactory::ValidateCompiledResult(const FVertexF #endif } -FVertexFactoryShaderParameters* FVirtualHeightfieldMeshVertexFactory::ConstructShaderParameters(EShaderFrequency ShaderFrequency) -{ - return ShaderFrequency == SF_Vertex ? new FVirtualHeightfieldMeshVertexFactoryShaderParameters() : nullptr; -} - -IMPLEMENT_VERTEX_FACTORY_TYPE_EX(FVirtualHeightfieldMeshVertexFactory, "/Plugin/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshVertexFactory.ush", true, false, true, true, false, false, true, false); +IMPLEMENT_VERTEX_FACTORY_TYPE_EX(FVirtualHeightfieldMeshVertexFactory, "/Plugin/VirtualHeightfieldMesh/Private/VirtualHeightfieldMeshVertexFactory.ush", true, false, true, false, false, false, true, false); diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Public/VirtualHeightfieldMeshComponent.h b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Public/VirtualHeightfieldMeshComponent.h index ab0bc7ab35a8..0cb2c474a052 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Public/VirtualHeightfieldMeshComponent.h +++ b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Public/VirtualHeightfieldMeshComponent.h @@ -7,37 +7,57 @@ #include "VirtualHeightfieldMeshComponent.generated.h" /** Component to render a heightfield mesh using a virtual texture heightmap. */ -UCLASS(ClassGroup = Rendering, hideCategories = (Activation, Collision, Cooking, HLOD, Navigation, Mobility, Object, Physics, VirtualTexture)) +UCLASS(Blueprintable, ClassGroup = Rendering, hideCategories = (Activation, Collision, Cooking, HLOD, Navigation, Mobility, Object, Physics, VirtualTexture)) class VIRTUALHEIGHTFIELDMESH_API UVirtualHeightfieldMeshComponent : public UPrimitiveComponent { GENERATED_UCLASS_BODY() protected: /** The RuntimeVirtualTextureComponent that contains virtual texture heightmap. */ - UPROPERTY(EditAnywhere, Category = Rendering, meta = (DisplayName = "Virtual Texture Heightmap", UseComponentPicker, AllowAnyActor, AllowedClasses = "RuntimeVirtualTextureComponent")) - FComponentReference VirtualTexture; + UPROPERTY(EditAnywhere, Category = Heightfield) + TSoftObjectPtr VirtualTexture; /** The material to apply. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Rendering) + UPROPERTY(EditAnywhere, Category = Rendering) class UMaterialInterface* Material = nullptr; + /** Target sceen size for LOD 0. A larger value uniformly increases the geometry resolution on screen. */ + UPROPERTY(EditAnywhere, Category = Rendering, meta = (DisplayName = "LOD 0 Screen Size", ClampMin = "0.1", UIMin = "0.1")) + float Lod0ScreenSize = 1.f; + + /** Distribution multiplier applied only for LOD 0. A larger value increases the distance to the first LOD transition. */ + UPROPERTY(EditAnywhere, Category = Rendering, meta = (DisplayName = "LOD 0 Distance Scale", ClampMin = "1.0", UIMin = "1.0")) + float Lod0Distribution = 1.f; + + /** Distribution multiplier applied for each LOD level. A larger value increases the distance exponentially between each LOD transition. */ + UPROPERTY(EditAnywhere, Category = Rendering, meta = (DisplayName = "LOD Distribution", ClampMin = "1.0", UIMin = "1.0")) + float LodDistribution = 2.f; + + /** The number of levels of geometry subdivision to apply before the LOD 0 from the source virtual texture. */ + UPROPERTY(EditAnywhere, Category = Rendering, meta = (DisplayName = "Subdivision LODs", ClampMin = "0", ClampMax = "8", UIMin = "0", UIMax = "8")) + int32 NumSubdivisionLods = 0; + + /** The number of levels of geometry reduction to apply after the Max LOD from the source virtual texture. */ + UPROPERTY(EditAnywhere, Category = Rendering, meta = (DisplayName = "Tail LODs", ClampMin = "0", ClampMax = "8", UIMin = "0", UIMax = "8")) + int32 NumTailLods = 0; + /** The number of Lod levels that we calculate occlusion volumes for. A higher number gives finer occlusion at the cost of more queries. */ - UPROPERTY(EditAnywhere, Category = Rendering, meta = (ClampMin = "0", ClampMax = "5")) + UPROPERTY(EditAnywhere, Category = Rendering, meta = (DisplayName = "Occlusion LODs", ClampMin = "0", ClampMax = "5")) int32 NumOcclusionLods = 0; - /** LOD scale. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = LOD, meta = (ClampMin = "0.01")) - float LODDistanceScale = 1.f; + /** The number of Lods stored in BuiltOcclusionData. This can be less then NumOcclusionLods if NumOcclusionLods is greater than the number of mips in MinMaxTexture. */ + UPROPERTY() + int32 NumBuiltOcclusionLods = 0; /** The MinMax height values stored for occlusion. */ UPROPERTY() TArray BuiltOcclusionData; - /** The number of Lods stored in BuiltOcclusionData. This can be less then NumOcclusionLods if NumOcclusionLods is greater than the number of mips in MinMaxTexture. */ - UPROPERTY() - int32 NumBuiltOcclusionLods = 0; - public: + /** */ + ARuntimeVirtualTextureVolume* GetVirtualTextureVolume() const; + /** */ + FTransform GetVirtualTextureTransform() const; /** */ class URuntimeVirtualTexture* GetVirtualTexture() const; /** */ @@ -45,11 +65,19 @@ public: /** */ virtual UMaterialInterface* GetMaterial(int32 Index) const override { return Material; } /** */ - float GetLODDistanceScale() const { return LODDistanceScale; } + float GetLod0ScreenSize() const { return Lod0ScreenSize; } + /** */ + float GetLod0Distribution() const { return Lod0Distribution; } + /** */ + float GetLodDistribution() const { return LodDistribution; } + /** */ + int32 GetNumSubdivisionLods() const { return NumSubdivisionLods; } + /** */ + int32 GetNumTailLods() const { return NumTailLods; } + /** */ + int32 GetNumOcclusionLods() const { return NumBuiltOcclusionLods; } /** */ TArray const& GetOcclusionData() const { return BuiltOcclusionData; } - /** */ - int32 GetNumOcclusionLods() const { return NumBuiltOcclusionLods; } protected: #if WITH_EDITOR diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Public/VirtualHeightfieldMeshSceneProxy.h b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Public/VirtualHeightfieldMeshSceneProxy.h index 61c697c764c1..6024a28a3fa4 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Public/VirtualHeightfieldMeshSceneProxy.h +++ b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Public/VirtualHeightfieldMeshSceneProxy.h @@ -39,13 +39,20 @@ public: uint32 NumQuadsPerTileSide; + FVector UVToWorldScale; + FMatrix UVToLocal; FMatrix UVToWorld; FMatrix WorldToUV; FMatrix WorldToUVTransposeAdjoint; class FVirtualHeightfieldMeshVertexFactory* VertexFactory; - float LODScale; + float Lod0ScreenSize; + float Lod0Distribution; + float LodDistribution; + + int32 NumSubdivisionLODs; + int32 NumTailLods; TArray OcclusionData; int32 NumOcclusionLods; diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Public/VirtualHeightfieldMeshVertexFactory.h b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Public/VirtualHeightfieldMeshVertexFactory.h index 6f5433c62a4b..424b05e10a35 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Public/VirtualHeightfieldMeshVertexFactory.h +++ b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMesh/Public/VirtualHeightfieldMeshVertexFactory.h @@ -24,8 +24,12 @@ struct FVirtualHeightfieldMeshUserData : public FOneFrameResource { FRHIShaderResourceView* InstanceBufferSRV; FRHITexture* HeightPhysicalTexture; - FVector2D PageTableSize; - FMatrix LocalToWorld; + FVector4 PageTableSize; + float MaxLod; + FMatrix VirtualHeightfieldToLocal; + FMatrix VirtualHeightfieldToWorld; + FVector LodViewOrigin; + FVector4 LodDistances; }; /** @@ -67,8 +71,6 @@ public: static void ModifyCompilationEnvironment(const FVertexFactoryShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment); static void ValidateCompiledResult(const FVertexFactoryType* Type, EShaderPlatform Platform, const FShaderParameterMap& ParameterMap, TArray& OutErrors); - static FVertexFactoryShaderParameters* ConstructShaderParameters(EShaderFrequency ShaderFrequency); - inline const FUniformBufferRHIRef GetVirtualHeightfieldMeshVertexFactoryUniformBuffer() const { return UniformBuffer; diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMeshEditor/Private/VirtualHeightfieldMeshDetailsCustomization.cpp b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMeshEditor/Private/VirtualHeightfieldMeshDetailsCustomization.cpp index e57cde2916ec..00038215191e 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMeshEditor/Private/VirtualHeightfieldMeshDetailsCustomization.cpp +++ b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMeshEditor/Private/VirtualHeightfieldMeshDetailsCustomization.cpp @@ -8,6 +8,7 @@ #include "ScopedTransaction.h" #include "SResetToDefaultMenu.h" #include "VirtualHeightfieldMeshComponent.h" +#include "VT/RuntimeVirtualTextureVolume.h" #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SWrapBox.h" @@ -38,8 +39,44 @@ void FVirtualHeightfieldMeshComponentDetailsCustomization::CustomizeDetails(IDet return; } -// IDetailCategoryBuilder& VirtualTextureCategory = DetailBuilder.EditCategory("Heightmap", FText::GetEmpty()); + IDetailCategoryBuilder& HeightfieldCategory = DetailBuilder.EditCategory("Heightfield", FText::GetEmpty()); + HeightfieldCategory + .AddCustomRow(LOCTEXT("Button_SetBounds", "Set Bounds")) + .NameContent() + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(LOCTEXT("Button_SetBounds", "Set Bounds")) + .ToolTipText(LOCTEXT("Button_SetBounds_Tooltip", "Copy the bounds from the virtual texture volume.")) + ] + .ValueContent() + .MaxDesiredWidth(125.f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ContentPadding(2) + .Text(LOCTEXT("Button_SetBounds", "Set Bounds")) + .OnClicked(this, &FVirtualHeightfieldMeshComponentDetailsCustomization::SetBounds) + ]; +} + +FReply FVirtualHeightfieldMeshComponentDetailsCustomization::SetBounds() +{ + ARuntimeVirtualTextureVolume* VirtualTextureVolume = VirtualHeightfieldMeshComponent->GetVirtualTextureVolume(); + if (VirtualTextureVolume != nullptr) + { + const FScopedTransaction Transaction(LOCTEXT("Transaction_SetBounds", "Set VirtualHeightfieldMeshComponent Bounds")); + + AActor* Owner = VirtualHeightfieldMeshComponent->GetOwner(); + Owner->Modify(); + Owner->SetActorTransform(VirtualTextureVolume->GetTransform()); + Owner->PostEditMove(true); + + return FReply::Handled(); + } + return FReply::Unhandled(); } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMeshEditor/Private/VirtualHeightfieldMeshDetailsCustomization.h b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMeshEditor/Private/VirtualHeightfieldMeshDetailsCustomization.h index 1500c3e63664..d0fa6a50523c 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMeshEditor/Private/VirtualHeightfieldMeshDetailsCustomization.h +++ b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMeshEditor/Private/VirtualHeightfieldMeshDetailsCustomization.h @@ -16,6 +16,9 @@ public: protected: FVirtualHeightfieldMeshComponentDetailsCustomization(); + /** Callback for Set Bounds button */ + FReply SetBounds(); + //~ Begin IDetailCustomization Interface. virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; //~ End IDetailCustomization Interface. diff --git a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMeshEditor/VirtualHeightfieldMeshEditor.Build.cs b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMeshEditor/VirtualHeightfieldMeshEditor.Build.cs index c5d549ca04b5..633319b3f8d4 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMeshEditor/VirtualHeightfieldMeshEditor.Build.cs +++ b/Engine/Plugins/VirtualHeightfieldMesh/Source/VirtualHeightfieldMeshEditor/VirtualHeightfieldMeshEditor.Build.cs @@ -13,6 +13,7 @@ public class VirtualHeightfieldMeshEditor : ModuleRules "Engine", "Slate", "SlateCore", + "UnrealEd", "VirtualHeightfieldMesh", }); } diff --git a/Engine/Plugins/VirtualHeightfieldMesh/VirtualHeightfieldMesh.uplugin b/Engine/Plugins/VirtualHeightfieldMesh/VirtualHeightfieldMesh.uplugin index 1278e2b3a108..eb17a05aa549 100644 --- a/Engine/Plugins/VirtualHeightfieldMesh/VirtualHeightfieldMesh.uplugin +++ b/Engine/Plugins/VirtualHeightfieldMesh/VirtualHeightfieldMesh.uplugin @@ -11,7 +11,7 @@ "MarketplaceURL": "", "SupportURL": "", "EnabledByDefault": false, - "CanContainContent": false, + "CanContainContent": true, "IsBetaVersion": true, "Installed": false, "Modules": [ diff --git a/Engine/Plugins/VirtualProduction/RemoteControl/Source/RemoteControl/Private/RemoteControlModule.cpp b/Engine/Plugins/VirtualProduction/RemoteControl/Source/RemoteControl/Private/RemoteControlModule.cpp index cbc62c5ddc26..43cd876734d9 100644 --- a/Engine/Plugins/VirtualProduction/RemoteControl/Source/RemoteControl/Private/RemoteControlModule.cpp +++ b/Engine/Plugins/VirtualProduction/RemoteControl/Source/RemoteControl/Private/RemoteControlModule.cpp @@ -23,6 +23,7 @@ namespace RemoteControlUtil const FName NAME_DeprecatedFunction(TEXT("DeprecatedFunction")); const FName NAME_BlueprintGetter(TEXT("BlueprintGetter")); const FName NAME_BlueprintSetter(TEXT("BlueprintSetter")); + const FName NAME_AllowPrivateAccess(TEXT("AllowPrivateAccess")); bool CompareFunctionName(const FString& FunctionName, const FString& ScriptName) { @@ -67,9 +68,11 @@ namespace RemoteControlUtil // it doesn't have exposed getter/setter that should be used instead #if WITH_EDITOR (!InProperty->HasMetaData(RemoteControlUtil::NAME_BlueprintGetter) || !InProperty->HasMetaData(RemoteControlUtil::NAME_BlueprintSetter)) && + // it isn't private or protected, except if AllowPrivateAccess is true + (!InProperty->HasAnyPropertyFlags(CPF_NativeAccessSpecifierProtected | CPF_NativeAccessSpecifierPrivate) || InProperty->GetBoolMetaData(RemoteControlUtil::NAME_AllowPrivateAccess)) && #endif - // it isn't private or protected - !InProperty->HasAnyPropertyFlags(CPF_NativeAccessSpecifierProtected | CPF_NativeAccessSpecifierPrivate | CPF_DisableEditOnInstance) && + // it isn't blueprint private + !InProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance) && // and it's either blueprint visible if in game or editable if in editor and it isn't read only if the access type is write (bObjectInGamePackage ? InProperty->HasAnyPropertyFlags(CPF_BlueprintVisible) && (InAccessType == ERCAccess::READ_ACCESS || !InProperty->HasAnyPropertyFlags(CPF_BlueprintReadOnly)) : diff --git a/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorder/Private/Recorder/TakeRecorderParameters.cpp b/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorder/Private/Recorder/TakeRecorderParameters.cpp index 5bd169a022ec..2149add86e9a 100644 --- a/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorder/Private/Recorder/TakeRecorderParameters.cpp +++ b/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorder/Private/Recorder/TakeRecorderParameters.cpp @@ -7,6 +7,7 @@ FTakeRecorderUserParameters::FTakeRecorderUserParameters() , CountdownSeconds(0.f) , EngineTimeDilation(1.f) , bRemoveRedundantTracks(true) + , ReduceKeysTolerance(KINDA_SMALL_NUMBER) , bSaveRecordedAssets(false) , bAutoSerialize(false) { diff --git a/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorder/Public/Recorder/TakeRecorderParameters.h b/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorder/Public/Recorder/TakeRecorderParameters.h index c2d5ef121887..93f5b61052a7 100644 --- a/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorder/Public/Recorder/TakeRecorderParameters.h +++ b/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorder/Public/Recorder/TakeRecorderParameters.h @@ -31,6 +31,10 @@ struct FTakeRecorderUserParameters UPROPERTY(config, EditAnywhere, BlueprintReadWrite, Category="User Settings") bool bRemoveRedundantTracks; + /** Tolerance to use when reducing keys */ + UPROPERTY(config, EditAnywhere, BlueprintReadWrite, Category="User Settings") + float ReduceKeysTolerance; + /** Whether to save recorded level sequences and assets when done recording */ UPROPERTY(config, EditAnywhere, BlueprintReadWrite, Category = "User Settings") bool bSaveRecordedAssets; diff --git a/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorderSources/Private/TakeRecorderActorSource.cpp b/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorderSources/Private/TakeRecorderActorSource.cpp index 48975a6bd4cc..3f69399be0d8 100644 --- a/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorderSources/Private/TakeRecorderActorSource.cpp +++ b/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorderSources/Private/TakeRecorderActorSource.cpp @@ -1560,6 +1560,7 @@ FTrackRecorderSettings UTakeRecorderActorSource::GetTrackRecorderSettings() cons TrackRecorderSettings.bReduceKeys = bReduceKeys; TrackRecorderSettings.bRemoveRedundantTracks = Parameters.User.bRemoveRedundantTracks; TrackRecorderSettings.bSaveRecordedAssets = Parameters.User.bSaveRecordedAssets || GEditor == nullptr; + TrackRecorderSettings.ReduceKeysTolerance = Parameters.User.ReduceKeysTolerance; TrackRecorderSettings.DefaultTracks = Parameters.Project.DefaultTracks; diff --git a/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorderSources/Private/TakeRecorderMicrophoneAudioSource.cpp b/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorderSources/Private/TakeRecorderMicrophoneAudioSource.cpp index e0ac33864db9..f8b101c09678 100644 --- a/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorderSources/Private/TakeRecorderMicrophoneAudioSource.cpp +++ b/Engine/Plugins/VirtualProduction/Takes/Source/TakeRecorderSources/Private/TakeRecorderMicrophoneAudioSource.cpp @@ -20,6 +20,7 @@ #include "Misc/PackageName.h" #include "AssetData.h" #include "AssetRegistryModule.h" +#include "UObject/UObjectBaseUtility.h" UTakeRecorderMicrophoneAudioSourceSettings::UTakeRecorderMicrophoneAudioSourceSettings(const FObjectInitializer& ObjInit) : Super(ObjInit) @@ -169,6 +170,8 @@ void UTakeRecorderMicrophoneAudioSource::StopRecording(class ULevelSequence* InS for (auto RecordedSoundWave : RecordedSoundWaves) { + RecordedSoundWave->MarkPackageDirty(); + FAssetRegistryModule::AssetCreated(RecordedSoundWave); } diff --git a/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieScene3DAttachTrackRecorder.cpp b/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieScene3DAttachTrackRecorder.cpp index b47c15afdaa2..db3c907d4cce 100644 --- a/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieScene3DAttachTrackRecorder.cpp +++ b/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieScene3DAttachTrackRecorder.cpp @@ -22,11 +22,11 @@ void UMovieScene3DAttachTrackRecorder::RecordSampleImpl(const FQualifiedFrameTim AActor* ActorToRecord = Cast(ObjectToRecord.Get()); if (ActorToRecord) { + FFrameRate TickResolution = MovieScene->GetTickResolution(); + FFrameNumber CurrentFrame = CurrentTime.ConvertTo(TickResolution).FloorToFrame(); + if (MovieSceneSection.IsValid()) { - FFrameRate TickResolution = MovieSceneSection->GetTypedOuter()->GetTickResolution(); - FFrameNumber CurrentFrame = CurrentTime.ConvertTo(TickResolution).FloorToFrame(); - MovieSceneSection->SetEndFrame(CurrentFrame); } @@ -52,10 +52,12 @@ void UMovieScene3DAttachTrackRecorder::RecordSampleImpl(const FQualifiedFrameTim MovieSceneSection->AttachSocketName = SocketName; MovieSceneSection->AttachComponentName = ComponentName; - // Newly created attach sections should match the parent movie scene. This is effectively an infinite attach section, - // but clamped to the bounds of the parent movie scene MovieSceneSection->TimecodeSource = MovieScene->TimecodeSource; - MovieSceneSection->SetRange(MovieScene->GetPlaybackRange()); + MovieSceneSection->SetRange(TRange(CurrentFrame, CurrentFrame)); + + FMovieSceneSequenceID SequenceID; + SequenceID = OwningTakeRecorderSource->GetLevelSequenceID(AttachedToActor); + MovieSceneSection->SetConstraintId(Guid, SequenceID); } ActorAttachedTo = AttachedToActor; diff --git a/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieScene3DTransformTrackRecorder.cpp b/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieScene3DTransformTrackRecorder.cpp index f712b09a2064..9f53682a8122 100644 --- a/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieScene3DTransformTrackRecorder.cpp +++ b/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieScene3DTransformTrackRecorder.cpp @@ -239,6 +239,7 @@ void UMovieScene3DTransformTrackRecorder::FinalizeTrackImpl() { FKeyDataOptimizationParams Params; Params.bAutoSetInterpolation = true; + Params.Tolerance = TrackRecorderSettings.ReduceKeysTolerance; for (FMovieSceneFloatChannel* Channel : FloatChannels) { Channel->Optimize(Params); diff --git a/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieSceneAnimationTrackRecorder.cpp b/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieSceneAnimationTrackRecorder.cpp index fdd4d9514916..c27936113df3 100644 --- a/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieSceneAnimationTrackRecorder.cpp +++ b/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieSceneAnimationTrackRecorder.cpp @@ -14,6 +14,7 @@ #include "Engine/TimecodeProvider.h" #include "Engine/Engine.h" #include "LevelSequence.h" +#include "UObject/UObjectBaseUtility.h" DEFINE_LOG_CATEGORY(AnimationSerialization); @@ -60,6 +61,8 @@ void UMovieSceneAnimationTrackRecorder::CreateAnimationAssetAndSequence(const AA AnimSequence = TakesUtils::MakeNewAsset(AnimationDirectory.Path, AnimationAssetName); if (AnimSequence.IsValid()) { + AnimSequence.Get()->MarkPackageDirty(); + FAssetRegistryModule::AssetCreated(AnimSequence.Get()); // Assign the skeleton we're recording to the newly created Animation Sequence. diff --git a/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieSceneTrackPropertyRecorder.cpp b/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieSceneTrackPropertyRecorder.cpp index c3ff07f5a35a..00525caa8c4e 100644 --- a/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieSceneTrackPropertyRecorder.cpp +++ b/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Private/MovieSceneTrackPropertyRecorder.cpp @@ -87,7 +87,7 @@ void FMovieSceneTrackPropertyRecorder::AddKeyToSection(UMovieSceneSection* } template <> -void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection) +void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection, float ReduceKeysTolerance) { // Reduce keys intentionally left blank } @@ -263,7 +263,7 @@ void FMovieSceneTrackPropertyRecorder::AddKeyToSection(UMovieSceneSection } template <> -void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection) +void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection, float ReduceKeysTolerance) { // Reduce keys intentionally left blank } @@ -417,10 +417,11 @@ void FMovieSceneTrackPropertyRecorder::AddKeyToSection(UMovieSceneSection } template <> -void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection) +void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection, float ReduceKeysTolerance) { FKeyDataOptimizationParams Params; Params.bAutoSetInterpolation = true; + Params.Tolerance = ReduceKeysTolerance; UE::MovieScene::Optimize(InSection->GetChannelProxy().GetChannel(0), Params); } @@ -580,12 +581,13 @@ void FMovieSceneTrackPropertyRecorder::AddKeyToSection(UMovieSceneSectio } template <> -void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection) +void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection, float ReduceKeysTolerance) { TArrayView FloatChannels = InSection->GetChannelProxy().GetChannels(); FKeyDataOptimizationParams Params; Params.bAutoSetInterpolation = true; + Params.Tolerance = ReduceKeysTolerance; UE::MovieScene::Optimize(FloatChannels[0], Params); UE::MovieScene::Optimize(FloatChannels[1], Params); UE::MovieScene::Optimize(FloatChannels[2], Params); @@ -780,12 +782,13 @@ void FMovieSceneTrackPropertyRecorder::AddKeyToSection(UMovieSceneSecti } template <> -void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection) +void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection, float ReduceKeysTolerance) { TArrayView FloatChannels = InSection->GetChannelProxy().GetChannels(); FKeyDataOptimizationParams Params; Params.bAutoSetInterpolation = true; + Params.Tolerance = ReduceKeysTolerance; UE::MovieScene::Optimize(FloatChannels[0], Params); UE::MovieScene::Optimize(FloatChannels[1], Params); UE::MovieScene::Optimize(FloatChannels[2], Params); @@ -958,7 +961,7 @@ void FMovieSceneTrackPropertyRecorder::AddKeyToSection(UMovieSceneSection } template <> -void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection) +void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection, float ReduceKeysTolerance) { // Reduce keys intentionally left blank } @@ -1124,7 +1127,7 @@ void FMovieSceneTrackPropertyRecorder::AddKeyToSection(UMovieSceneSecti } template <> -void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection) +void FMovieSceneTrackPropertyRecorder::ReduceKeys(UMovieSceneSection* InSection, float ReduceKeysTolerance) { // Reduce keys intentionally left blank } diff --git a/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Public/TrackRecorders/IMovieSceneTrackRecorderHost.h b/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Public/TrackRecorders/IMovieSceneTrackRecorderHost.h index c72b613bc6b2..881837158b24 100644 --- a/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Public/TrackRecorders/IMovieSceneTrackRecorderHost.h +++ b/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Public/TrackRecorders/IMovieSceneTrackRecorderHost.h @@ -49,6 +49,8 @@ struct FTrackRecorderSettings bool bReduceKeys; bool bSaveRecordedAssets; + float ReduceKeysTolerance; + TArray DefaultTracks; static bool IsDefaultPropertyTrack(UObject* InObjectToRecord, const FString& InPropertyPath, const TArray& DefaultTracks) diff --git a/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Public/TrackRecorders/MovieSceneTrackPropertyRecorder.h b/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Public/TrackRecorders/MovieSceneTrackPropertyRecorder.h index f99aee3862bb..d4c7ad09f91c 100644 --- a/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Public/TrackRecorders/MovieSceneTrackPropertyRecorder.h +++ b/Engine/Plugins/VirtualProduction/Takes/Source/TakeTrackRecorders/Public/TrackRecorders/MovieSceneTrackPropertyRecorder.h @@ -173,7 +173,7 @@ public: if (TrackRecorderSettings.bReduceKeys) { - ReduceKeys(MovieSceneSection.Get()); + ReduceKeys(MovieSceneSection.Get(), TrackRecorderSettings.ReduceKeysTolerance); } if (TrackRecorderSettings.bRemoveRedundantTracks) @@ -285,7 +285,7 @@ private: void AddKeyToSection(UMovieSceneSection* InSection, const FPropertyKey& InKey); /** Helper function, specialized by type, used to reduce keys */ - void ReduceKeys(UMovieSceneSection* InSection); + void ReduceKeys(UMovieSceneSection* InSection, float ReduceKeysTolerance); /** Get the default value of the track - if there's one key, the value of that key. Otherwise, the default value of the track. */ PropertyType GetDefaultValue(UMovieSceneSection* InSection); diff --git a/Engine/Plugins/VirtualProduction/Takes/Source/TakesCore/Public/TakeRecorderSources.h b/Engine/Plugins/VirtualProduction/Takes/Source/TakesCore/Public/TakeRecorderSources.h index dbb4a7f92623..ff5ddf78cf83 100644 --- a/Engine/Plugins/VirtualProduction/Takes/Source/TakesCore/Public/TakeRecorderSources.h +++ b/Engine/Plugins/VirtualProduction/Takes/Source/TakesCore/Public/TakeRecorderSources.h @@ -51,7 +51,7 @@ public: * @param InSourceType The class type of the source to add * @return An instance of the specified source type */ - UFUNCTION(BlueprintCallable, Category = "Take Recorder") + UFUNCTION(BlueprintCallable, Category = "Take Recorder", meta = (DeterminesOutputType = "InSourceType")) UTakeRecorderSource* AddSource(TSubclassOf InSourceType); /** diff --git a/Engine/Plugins/VirtualProduction/TimedDataMonitor/Source/TimedDataMonitor/Private/TimedDataMonitorCalibration.cpp b/Engine/Plugins/VirtualProduction/TimedDataMonitor/Source/TimedDataMonitor/Private/TimedDataMonitorCalibration.cpp index 542a1e562aaf..228307075d80 100644 --- a/Engine/Plugins/VirtualProduction/TimedDataMonitor/Source/TimedDataMonitor/Private/TimedDataMonitorCalibration.cpp +++ b/Engine/Plugins/VirtualProduction/TimedDataMonitor/Source/TimedDataMonitor/Private/TimedDataMonitorCalibration.cpp @@ -11,7 +11,7 @@ #include "HAL/PlatformTime.h" #include "Misc/App.h" #include "Misc/QualifiedFrameTime.h" - +#include "Stats/Stats.h" /** The algo for Calibration and TimeCorrection will use those data for their examples and comments. */ // EvaluationTime == 50. @@ -399,6 +399,8 @@ void FTimedDataMonitorCalibration::CalibrateWithTimecode(const FTimedDataMonitor bool FTimedDataMonitorCalibration::RunCalibrateWithTimecode_CheckForReset(float) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FTimedDataMonitorCalibration_RunCalibrateWithTimecode_CheckForReset); + if (CalibrationParameters.bUseStandardDeviation && CalibrationParameters.bResetStatisticsBeforeUsingStandardDeviation) { GEngine->GetEngineSubsystem()->ResetAllBufferStats(); @@ -417,6 +419,8 @@ bool FTimedDataMonitorCalibration::RunCalibrateWithTimecode_CheckForReset(float) bool FTimedDataMonitorCalibration::RunCalibrateWithTimecode(float) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FTimedDataMonitorCalibration_RunCalibrateWithTimecode); + FTimedDataMonitorCalibrationResult Result = CalibrateWithTimecode(CalibrationParameters); bool bCallcallback = true; if (Result.ReturnCode == ETimedDataMonitorCalibrationReturnCode::Retry_IncreaseBufferSize diff --git a/Engine/Shaders/Private/BasePassPixelShader.usf b/Engine/Shaders/Private/BasePassPixelShader.usf index 918f737cc436..6b90d7b1e0c3 100644 --- a/Engine/Shaders/Private/BasePassPixelShader.usf +++ b/Engine/Shaders/Private/BasePassPixelShader.usf @@ -256,7 +256,7 @@ float3 GetTranslucencyVolumeLighting( #endif -#if SIMPLE_FORWARD_SHADING +#if SIMPLE_FORWARD_SHADING || PLATFORM_FORCE_SIMPLE_SKY_DIFFUSE #define GetEffectiveSkySHDiffuse GetSkySHDiffuseSimple #else #define GetEffectiveSkySHDiffuse GetSkySHDiffuse diff --git a/Engine/Shaders/Private/CapsuleShadowShaders.usf b/Engine/Shaders/Private/CapsuleShadowShaders.usf index 4feb8bfb212d..6b818db91d19 100644 --- a/Engine/Shaders/Private/CapsuleShadowShaders.usf +++ b/Engine/Shaders/Private/CapsuleShadowShaders.usf @@ -10,6 +10,7 @@ #include "DistanceFieldLightingShared.ush" #include "SHCommon.ush" #include "VolumetricLightmapShared.ush" +#include "ReflectionEnvironmentShared.ush" #ifndef THREADGROUP_SIZEX #define THREADGROUP_SIZEX 1 @@ -103,9 +104,9 @@ void ComputeLightDirectionFromVolumetricLightmapCS( { FTwoBandSHVectorRGB SkyIrradianceSH; // See ComputeSkyEnvMapDiffuseIrradianceCS for the coefficient and sign adaptation. N need to rescale the coefficients, direction is preserve. - SkyIrradianceSH.R.V = View.SkyIrradianceEnvironmentMap[0].wyzx * float4(1.0f, -1.0f, 1.0f, -1.0f); - SkyIrradianceSH.G.V = View.SkyIrradianceEnvironmentMap[1].wyzx * float4(1.0f, -1.0f, 1.0f, -1.0f); - SkyIrradianceSH.B.V = View.SkyIrradianceEnvironmentMap[2].wyzx * float4(1.0f, -1.0f, 1.0f, -1.0f); + SkyIrradianceSH.R.V = SkyIrradianceEnvironmentMap[0].wyzx * float4(1.0f, -1.0f, 1.0f, -1.0f); + SkyIrradianceSH.G.V = SkyIrradianceEnvironmentMap[1].wyzx * float4(1.0f, -1.0f, 1.0f, -1.0f); + SkyIrradianceSH.B.V = SkyIrradianceEnvironmentMap[2].wyzx * float4(1.0f, -1.0f, 1.0f, -1.0f); LightDirection = GetMaximumDirection(GetLuminance(SkyIrradianceSH)); LightAngle = CapsuleIndirectConeAngle; } diff --git a/Engine/Shaders/Private/ComposeSeparateTranslucency.usf b/Engine/Shaders/Private/ComposeSeparateTranslucency.usf index 3be31c0237c3..e1c110b7d7dc 100644 --- a/Engine/Shaders/Private/ComposeSeparateTranslucency.usf +++ b/Engine/Shaders/Private/ComposeSeparateTranslucency.usf @@ -22,8 +22,10 @@ void MainPS( float4 SceneColorSample = SceneColor.SampleLevel(SceneColorSampler, BufferUV, 0); + float2 SeparateTranslucencyBufferUV = clamp(BufferUV, SeparateTranslucencyBilinearUVMinMax.xy, SeparateTranslucencyBilinearUVMinMax.zw); + float4 SeparateTranslucencySample = SeparateTranslucency.SampleLevel(SeparateTranslucencySampler, SeparateTranslucencyBufferUV, 0); float4 SeparateModulationSample = SeparateModulation.SampleLevel(SeparateModulationSampler, SeparateTranslucencyBufferUV, 0); diff --git a/Engine/Shaders/Private/DeferredLightPixelShaders.usf b/Engine/Shaders/Private/DeferredLightPixelShaders.usf index ea0414114799..969c54341315 100644 --- a/Engine/Shaders/Private/DeferredLightPixelShaders.usf +++ b/Engine/Shaders/Private/DeferredLightPixelShaders.usf @@ -87,6 +87,7 @@ FDeferredLightData SetupLightDataForStandardDeferred() LightData.SpecularScale = DeferredLightUniforms.SpecularScale; LightData.ContactShadowLength = abs(DeferredLightUniforms.ContactShadowLength); LightData.ContactShadowLengthInWS = DeferredLightUniforms.ContactShadowLength < 0.0f; + LightData.ContactShadowNonShadowCastingIntensity = DeferredLightUniforms.ContactShadowNonShadowCastingIntensity; LightData.DistanceFadeMAD = DeferredLightUniforms.DistanceFadeMAD; LightData.ShadowMapChannelMask = DeferredLightUniforms.ShadowMapChannelMask; LightData.ShadowedBits = DeferredLightUniforms.ShadowedBits; @@ -294,6 +295,7 @@ void DeferredLightPixelMain( HairScreenSpaceData.GBuffer.CustomData = float4(HairDualScatteringRoughnessOverride, 0, Sample.Backlit, 0); HairScreenSpaceData.GBuffer.IndirectIrradiance = 0; HairScreenSpaceData.GBuffer.PrecomputedShadowFactors = 1; + HairScreenSpaceData.GBuffer.PerObjectGBufferData = 0; const float3 WorldPosition = mul(float4(ScreenPosition * HairScreenSpaceData.GBuffer.Depth, HairScreenSpaceData.GBuffer.Depth, 1), View.ScreenToWorld).xyz; const float3 CameraVector = normalize(WorldPosition - View.WorldCameraOrigin); diff --git a/Engine/Shaders/Private/DeferredLightingCommon.ush b/Engine/Shaders/Private/DeferredLightingCommon.ush index b7d8aa374bda..e8d6d69c895c 100644 --- a/Engine/Shaders/Private/DeferredLightingCommon.ush +++ b/Engine/Shaders/Private/DeferredLightingCommon.ush @@ -32,6 +32,8 @@ struct FDeferredLightData float SourceLength; float SpecularScale; float ContactShadowLength; + /** Intensity of non-shadow-casting contact shadows */ + float ContactShadowNonShadowCastingIntensity; float2 DistanceFadeMAD; float4 ShadowMapChannelMask; /** Whether ContactShadowLength is in World Space or in Screen Space. */ @@ -72,10 +74,11 @@ float DistanceFromCameraFade(float SceneDepth, FDeferredLightData LightData, flo return Fade * Fade; } +// Returns distance along ray that the first hit occurred, or negative on miss +// Sets bOutHitCastDynamicShadow if the hit point is marked as a dynamic shadow caster float ShadowRayCast( float3 RayOriginTranslatedWorld, float3 RayDirection, float RayLength, - int NumSteps, float StepOffset -) + int NumSteps, float StepOffset, out bool bOutHitCastContactShadow ) { float4 RayStartClip = mul( float4( RayOriginTranslatedWorld, 1 ), View.TranslatedWorldToClip ); float4 RayDirClip = mul( float4( RayDirection * RayLength, 0 ), View.TranslatedWorldToClip ); @@ -115,13 +118,22 @@ float ShadowRayCast( SampleTime += Step; } - float Shadow = FirstHitTime > 0.0 ? 1.0 : 0.0; + float HitDistance = -1.0; + bOutHitCastContactShadow = false; + if ( FirstHitTime > 0.0 ) + { + // Ignore hits that come from non-shadow-casting pixels + float3 SampleUVz = RayStartUVz + RayStepUVz * FirstHitTime; + FGBufferData SampleGBuffer = GetGBufferData( SampleUVz.xy ); + bOutHitCastContactShadow = CastContactShadow( SampleGBuffer ); - // Off screen masking - float2 Vignette = max(6.0 * abs(RayStartScreen.xy + RayStepScreen.xy * FirstHitTime) - 5.0, 0.0); - Shadow *= saturate( 1.0 - dot( Vignette, Vignette ) ); - - return 1 - Shadow; + // Off screen masking + float3 HitUVz = RayStartUVz + RayStepUVz * FirstHitTime; + bool bValidUV = all( 0.0 < HitUVz.xy && HitUVz.xy < 1.0 ); + HitDistance = bValidUV ? ( FirstHitTime * RayLength ) : -1.0; + } + + return HitDistance; } #ifndef SUPPORT_CONTACT_SHADOWS @@ -196,23 +208,45 @@ void GetShadowTerms(FGBufferData GBuffer, FDeferredLightData LightData, float3 W if (ContactShadowLength > 0.0) { float StepOffset = Dither - 0.5; - float ContactShadow = ShadowRayCast( WorldPosition + View.PreViewTranslation, L, ContactShadowLength, 8, StepOffset ); - - Shadow.SurfaceShadow *= ContactShadow; - - FLATTEN - if( GBuffer.ShadingModelID == SHADINGMODELID_HAIR || GBuffer.ShadingModelID == SHADINGMODELID_EYE ) + bool bHitCastContactShadow = false; + float HitDistance = ShadowRayCast( WorldPosition + View.PreViewTranslation, L, ContactShadowLength, 8, StepOffset, bHitCastContactShadow ); + + if ( HitDistance > 0.0 ) { - // If hair transmittance is enabled, sharp shadowing should already be handled by - // the dedicated deep shadow maps. Thus no need for contact shadow - const bool bUseComplexTransmittance = (LightData.HairTransmittance.ScatteringComponent & HAIR_COMPONENT_MULTISCATTER) > 0; - if (!bUseComplexTransmittance) + float ContactShadowOcclusion = bHitCastContactShadow ? 1.0 : LightData.ContactShadowNonShadowCastingIntensity; + + BRANCH + if ( ContactShadowOcclusion > 0.0 && IsSubsurfaceModel( GBuffer.ShadingModelID ) ) + { + // Reduce the intensity of the shadow similar to the subsurface approximation used by the shadow maps path + // Note that this is imperfect as we don't really have the "nearest occluder to the light", but this should at least + // ensure that we don't darken-out the subsurface term with the contact shadows + float Opacity = GBuffer.CustomData.a; + float Density = SubsurfaceDensityFromOpacity( Opacity ); + ContactShadowOcclusion *= 1.0 - saturate( exp( -Density * HitDistance ) ); + } + + float ContactShadow = 1.0 - ContactShadowOcclusion; + + Shadow.SurfaceShadow *= ContactShadow; + + FLATTEN + if( GBuffer.ShadingModelID == SHADINGMODELID_HAIR || GBuffer.ShadingModelID == SHADINGMODELID_EYE ) + { + // If hair transmittance is enabled, sharp shadowing should already be handled by + // the dedicated deep shadow maps. Thus no need for contact shadow + const bool bUseComplexTransmittance = (LightData.HairTransmittance.ScatteringComponent & HAIR_COMPONENT_MULTISCATTER) > 0; + if (!bUseComplexTransmittance) + { + Shadow.TransmissionShadow *= ContactShadow; + } + } + else { Shadow.TransmissionShadow *= ContactShadow; } } - else - Shadow.TransmissionShadow *= ContactShadow * 0.5 + 0.5; + } #endif diff --git a/Engine/Shaders/Private/DeferredShadingCommon.ush b/Engine/Shaders/Private/DeferredShadingCommon.ush index 83d83b2cd422..4c5736eaead3 100644 --- a/Engine/Shaders/Private/DeferredShadingCommon.ush +++ b/Engine/Shaders/Private/DeferredShadingCommon.ush @@ -197,6 +197,12 @@ float3 EncodeSubsurfaceProfile(float SubsurfaceProfile) return float3(SubsurfaceProfile, 0, 0); } +// Derive density from a heuristic using opacity, tweaked for useful falloff ranges and to give a linear depth falloff with opacity +float SubsurfaceDensityFromOpacity(float Opacity) +{ + return (-0.05f * log(1.0f - min(Opacity, 0.999f))); +} + float EncodeIndirectIrradiance(float IndirectIrradiance) { float L = IndirectIrradiance; @@ -338,7 +344,7 @@ struct FGBufferData uint ShadingModelID; // 0..255 uint SelectiveOutputMask; - // 0..1, 2 bits, use HasDistanceFieldRepresentation(GBuffer) or HasDynamicIndirectShadowCasterRepresentation(GBuffer) to extract + // 0..1, 2 bits, use CastContactShadow(GBuffer) or HasDynamicIndirectShadowCasterRepresentation(GBuffer) to extract float PerObjectGBufferData; // in world units float CustomDepth; @@ -358,7 +364,7 @@ struct FGBufferData float StoredMetallic; }; -bool HasDistanceFieldRepresentation(FGBufferData GBufferData) +bool CastContactShadow(FGBufferData GBufferData) { uint PackedAlpha = (uint)(GBufferData.PerObjectGBufferData * 3.999f); return PackedAlpha & 1; diff --git a/Engine/Shaders/Private/LightmapCommon.ush b/Engine/Shaders/Private/LightmapCommon.ush index 7356b1c11061..c6ee48b5b747 100644 --- a/Engine/Shaders/Private/LightmapCommon.ush +++ b/Engine/Shaders/Private/LightmapCommon.ush @@ -208,7 +208,7 @@ half4 GetPrecomputedShadowMasks(VTPageTableResult LightmapVTPageTableResult, FVe // This is necessary because objects inside a light's influence that were determined to be completely shadowed won't be rendered with STATICLIGHTING_TEXTUREMASK==1 return 0; - #else + #elif ALLOW_STATIC_LIGHTING float DirectionalLightShadowing = 1.0f; @@ -233,6 +233,10 @@ half4 GetPrecomputedShadowMasks(VTPageTableResult LightmapVTPageTableResult, FVe // Directional light is always packed into the first static shadowmap channel, so output the per-primitive directional light shadowing there if requested return half4(DirectionalLightShadowing, 1, 1, 1); + #else + + return half4(1, 1, 1, 1); + #endif } diff --git a/Engine/Shaders/Private/MaterialTemplate.ush b/Engine/Shaders/Private/MaterialTemplate.ush index 6254efc59c45..91c1f05660ed 100644 --- a/Engine/Shaders/Private/MaterialTemplate.ush +++ b/Engine/Shaders/Private/MaterialTemplate.ush @@ -351,7 +351,7 @@ struct FMaterialPixelParameters float CloudSampleAltitude; float CloudSampleAltitudeInLayer; float CloudSampleNormAltitudeInLayer; - float VolumeSampleConservativeDensity; + float3 VolumeSampleConservativeDensity; #endif }; @@ -1304,12 +1304,12 @@ float MaterialExpressionCloudSampleNormAltitudeInLayer(FMaterialPixelParameters #endif } -float MaterialExpressionVolumeSampleConservativeDensity(FMaterialPixelParameters Parameters) +float3 MaterialExpressionVolumeSampleConservativeDensity(FMaterialPixelParameters Parameters) { #if CLOUD_LAYER_PIXEL_SHADER return Parameters.VolumeSampleConservativeDensity; #else - return 0.0f; + return float3(0.0f, 0.0f, 0.0f); #endif } diff --git a/Engine/Shaders/Private/PathTracing/PathTracing.usf b/Engine/Shaders/Private/PathTracing/PathTracing.usf index d0c45dd42751..2543515d7da6 100644 --- a/Engine/Shaders/Private/PathTracing/PathTracing.usf +++ b/Engine/Shaders/Private/PathTracing/PathTracing.usf @@ -75,6 +75,7 @@ RAY_TRACING_ENTRY_RAYGEN(PathTracingMainRG) const uint InitialInstanceInclusionMask = RAY_TRACING_MASK_ALL; const bool bInitialDisableSkyLightContribution = false; const bool bIgnoreTranslucentMaterials = false; + const uint2 RenderTargetPos = LaunchIndex.xy + View.ViewRectMin.xy; FMaterialClosestHitPayload Payload = TraceMaterialRay( TLAS, @@ -82,6 +83,7 @@ RAY_TRACING_ENTRY_RAYGEN(PathTracingMainRG) InitialInstanceInclusionMask, Ray, RayCone, + RenderTargetPos, bInitialDisableSkyLightContribution, bIgnoreTranslucentMaterials); RayCounter++; @@ -183,6 +185,7 @@ RAY_TRACING_ENTRY_RAYGEN(PathTracingMainRG) TLAS, RayFlags, InstanceInclusionMask, + RenderTargetPos, LightRay); RayCounter++; @@ -288,12 +291,12 @@ RAY_TRACING_ENTRY_RAYGEN(PathTracingMainRG) InstanceInclusionMask, Ray, RayCone, + RenderTargetPos, bDisableSkyLightContribution, bIgnoreTranslucentMaterials); RayCounter++; } - uint2 RenderTargetPos = LaunchIndex.xy + View.ViewRectMin.xy; RadianceRT[RenderTargetPos].rgb += Irradiance; SampleCountRT[RenderTargetPos] += 1; PixelPositionRT[RenderTargetPos] = Pixel.y * BufferSize.x + Pixel.x; diff --git a/Engine/Shaders/Private/PostProcessMobile.usf b/Engine/Shaders/Private/PostProcessMobile.usf index 9b24bed80775..6adba307383c 100644 --- a/Engine/Shaders/Private/PostProcessMobile.usf +++ b/Engine/Shaders/Private/PostProcessMobile.usf @@ -1017,6 +1017,10 @@ void SunMergeVS_ES2( float4 SunColorVignetteIntensity; float3 BloomColor; +Texture2D BloomDirtMaskTexture; +SamplerState BloomDirtMaskSampler; +float4 BloomDirtMaskTint; + void SunMergePS_ES2( float4 InUVVignette : TEXCOORD0, float4 InUVs[7] : TEXCOORD1, @@ -1040,14 +1044,16 @@ void SunMergePS_ES2( OutColor.rgb = PostprocessInput2.Sample(PostprocessInput2Sampler, InUVVignette.xy); + half3 BloomDirtMaskColor = BloomDirtMaskTexture.Sample(BloomDirtMaskSampler, InUVVignette.xy).rgb * BloomDirtMaskTint.rgb; + // Have 5 layers on mobile. - float Scale3 = 1.0/5.0; + half Scale3 = 1.0/5.0; // scale existing color first OutColor.rgb *= Scale3; // add scaled bloom separately to prevent overflow before scaling - OutColor.rgb += (Bloom2 * Scale3 * BloomColor); + OutColor.rgb += (Bloom2 * Scale3 * (BloomColor + BloomDirtMaskColor)); #else OutColor.rgb = half3(0.0, 0.0, 0.0); diff --git a/Engine/Shaders/Private/PostProcessTonemap.usf b/Engine/Shaders/Private/PostProcessTonemap.usf index e74f37b6031d..59a162bc2432 100644 --- a/Engine/Shaders/Private/PostProcessTonemap.usf +++ b/Engine/Shaders/Private/PostProcessTonemap.usf @@ -497,9 +497,8 @@ float4 TonemapCommonPS( #if USE_GAMMA_ONLY - #if FEATURE_LEVEL >= FEATURE_LEVEL_ES3_1 // On mobile ExposureScale applied in base pass PS - SceneColor.rgb *= ExposureScale; - #endif + SceneColor.rgb *= ExposureScale; + OutColor.rgb = pow(SceneColor.rgb, InverseGamma.x); #else @@ -524,10 +523,6 @@ float4 TonemapCommonPS( //float2 DirtViewportUV = (SvPosition.xy - float2(Output_ViewportMin)) * Output_ViewportSizeInverse; - float2 DirtLensUV = ConvertScreenViewportSpaceToLensViewportSpace(ScreenPos) * float2(1.0f, -1.0f); - - float3 BloomDirtMaskColor = Texture2DSample(BloomDirtMaskTexture, BloomDirtMaskSampler, DirtLensUV * .5f + .5f).rgb * BloomDirtMaskTint.rgb; - #if DEBUG_TONEMAPPER if (all(DebugTile == int2(-1, -1))) { @@ -543,7 +538,16 @@ float4 TonemapCommonPS( } #endif //DEBUG_TONEMAPPER - LinearColor += CombinedBloom.rgb * (ColorScale1.rgb + BloomDirtMaskColor); + #if FEATURE_LEVEL == FEATURE_LEVEL_ES3_1 + // Support sunshaft and vignette for mobile, and we have applied the BloomIntensity and the BloomDirtMask at the sun merge pass. + LinearColor = LinearColor.rgb * CombinedBloom.a + CombinedBloom.rgb; + #else + float2 DirtLensUV = ConvertScreenViewportSpaceToLensViewportSpace(ScreenPos) * float2(1.0f, -1.0f); + + float3 BloomDirtMaskColor = Texture2DSample(BloomDirtMaskTexture, BloomDirtMaskSampler, DirtLensUV * .5f + .5f).rgb * BloomDirtMaskTint.rgb; + + LinearColor += CombinedBloom.rgb * (ColorScale1.rgb + BloomDirtMaskColor); + #endif #endif #if NO_EYEADAPTATION_EXPOSURE_FIX == 1 diff --git a/Engine/Shaders/Private/RayTracing/GenerateCulledLightListCS.usf b/Engine/Shaders/Private/RayTracing/GenerateCulledLightListCS.usf new file mode 100644 index 000000000000..63e0ff14ceb2 --- /dev/null +++ b/Engine/Shaders/Private/RayTracing/GenerateCulledLightListCS.usf @@ -0,0 +1,141 @@ +#include "../Common.ush" +#include "RayTracingLightCullingCommon.ush" + +#define CACHED_LIGHT_COUNT 16 + +// inputs +float3 WorldPos; +uint NumLightsToUse; +uint CellCount; +float CellScale; + + +StructuredBuffer RankedLights; + +// outputs +RWBuffer LightIndices; +RWStructuredBuffer LightCullingVolume; + + + +groupshared uint LightCount; +groupshared uint PackedLights[4]; +groupshared uint LightCache[CACHED_LIGHT_COUNT]; + + +void ComputeCoords(out float3 Min, out float3 Max, int3 Cell) +{ + const uint CellOffset = CellCount / 2; + + float3 Center = Cell - float3(CellOffset, CellOffset, CellOffset) + 0.5f; + + // determine which sector + float3 SignVal = sign(Center); + + // compute bounds as logarithmically increasing from center (with minimum cell size of 2) + Min = abs(floor(Center)); + Max = abs(ceil(Center)); + Min = Min > 0.0 ? pow(2.0, Min + 1.0) : 0.0; + Max = Max > 0.0 ? pow(2.0, Max + 1.0) : 0.0; + + // Extend range of last cell + Max = (Cell == 0 || Cell == CellCount - 1) ? 1.0e7 : Max; + + // return to sector + Min *= SignVal * CellScale; + Max *= SignVal * CellScale; +} + + + +[numthreads(THREADGROUP_SIZE, 1, 1)] +void GenerateCulledLightListCS( + uint GroupIndex : SV_GroupIndex, + uint3 GroupId : SV_GroupID, + uint3 ThreadId : SV_GroupThreadID +) +{ + + if (ThreadId.x == 0) + { + LightCount = 0; + PackedLights[0] = 0; + PackedLights[1] = 0; + PackedLights[2] = 0; + PackedLights[3] = 0; + } + + GroupMemoryBarrierWithGroupSync(); + + float3 Min, Max; + ComputeCoords(Min, Max, GroupId); + float3 CellCenter = (Min + Max) * 0.5 + WorldPos; + float3 CellExtents = (Max - Min) * 0.5; + + uint LightIndexBufferOffset = (GroupId.x + CellCount * (GroupId.y + (GroupId.z * CellCount))) * NumLightsToUse; + + for (uint LightIndex = ThreadId.x; LightIndex < NumLightsToUse; LightIndex += THREADGROUP_SIZE) + { + float4 Light = RankedLights[LightIndex]; + float RadiusSquared = ComputeSquaredDistanceFromBoxToPoint(CellCenter, CellExtents, Light.xyz); + if (RadiusSquared < (Light.w*Light.w)) + { + uint MyLightIndex; + InterlockedAdd(LightCount, 1, MyLightIndex); + if (MyLightIndex < CACHED_LIGHT_COUNT) + { + LightCache[MyLightIndex] = LightIndex; + } + else + { + LightIndices[MyLightIndex + LightIndexBufferOffset] = LightIndex; + } + } + } + + GroupMemoryBarrierWithGroupSync(); + + // use packed light cell if few lights + if (LightCount <= FCulledLightList::PackedLightsMax) + { + for (uint Index = ThreadId.x; Index < LightCount; Index += THREADGROUP_SIZE) + { + uint Shift = (Index%FCulledLightList::PackedLightsPerComponent)*FCulledLightList::PackedLightBits; + uint MaskedLightIndex = LightCache[Index] & FCulledLightList::PackedLightMask; + uint ElemIndex = Index / FCulledLightList::PackedLightsPerComponent; + InterlockedAdd(PackedLights[ElemIndex], MaskedLightIndex << Shift); + } + + GroupMemoryBarrierWithGroupSync(); + + if (ThreadId.x == 0) + { + // set packed bit + PackedLights[0] |= 1 << 31; + // store count + PackedLights[3] |= (LightCount & 0x3FF) << (2 * FCulledLightList::PackedLightBits); + + + uint Address = (GroupId.x + CellCount * (GroupId.y + (GroupId.z * CellCount))); + LightCullingVolume[Address] = uint4(PackedLights[0], PackedLights[1], PackedLights[2], PackedLights[3]); + } + } + else + { + // write the cached lights + if (ThreadId.x < CACHED_LIGHT_COUNT && ThreadId.x < LightCount) + { + LightIndices[ThreadId.x + LightIndexBufferOffset] = LightCache[ThreadId.x]; + } + + if (ThreadId.x == 0) + { + // unset packed bit + LightCount &= ~(1<<31); + + uint Address = (GroupId.x + CellCount * (GroupId.y + (GroupId.z * CellCount))); + LightCullingVolume[Address] = uint4(LightCount, LightIndexBufferOffset, 0, 0); + } + } +} + diff --git a/Engine/Shaders/Private/RayTracing/RayTracingAmbientOcclusionRGS.usf b/Engine/Shaders/Private/RayTracing/RayTracingAmbientOcclusionRGS.usf index 6cead2b3e202..eca355331ada 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingAmbientOcclusionRGS.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingAmbientOcclusionRGS.usf @@ -106,6 +106,7 @@ RAY_TRACING_ENTRY_RAYGEN(AmbientOcclusionRGS) TLAS, RayFlags, InstanceInclusionMask, + PixelCoord, Ray); RayCount += 1; diff --git a/Engine/Shaders/Private/RayTracing/RayTracingBuiltInShaders.usf b/Engine/Shaders/Private/RayTracing/RayTracingBuiltInShaders.usf index 855fb4f0c09d..958fa967d2bf 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingBuiltInShaders.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingBuiltInShaders.usf @@ -41,6 +41,7 @@ RAY_TRACING_ENTRY_RAYGEN(OcclusionMainRG) TLAS, RayFlags, InstanceInclusionMask, + uint2(RayIndex & 0xFFFF, RayIndex >> 16), Ray); OcclusionOutput[RayIndex] = MinimalPayload.IsHit() ? ~0 : 0; diff --git a/Engine/Shaders/Private/RayTracing/RayTracingCommon.ush b/Engine/Shaders/Private/RayTracing/RayTracingCommon.ush index 5ea3c87c99de..2d5d443d84ef 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingCommon.ush +++ b/Engine/Shaders/Private/RayTracing/RayTracingCommon.ush @@ -19,6 +19,51 @@ #include "/Platform/Private/RayTracing.ush" #endif // OVERRIDE_RAYTRACING_USH +// Certain DXR system value intrinsics are explicitly disallowed in Unreal Engine entirely. +// Supported cross-platform system value intrinsics: +// RayGen Intersection AnyHit ClosestHit Miss Callable +// uint3 DispatchRaysIndex() YES - - - - - +// uint3 DispatchRaysDimensions() YES - - - - - +// float3 WorldRayOrigin() - YES* YES YES YES - +// float3 WorldRayDirection() - YES* YES YES YES - +// float RayTMin() - - - - - - +// float RayTCurrent() - YES* YES YES YES - +// float RayFlags() - - - - - - +// uint InstanceIndex() - YES* YES YES - - +// uint InstanceID() - YES* YES YES - - +// uint GeometryIndex() - - - - - - +// uint PrimitiveIndex() - YES* YES YES - - +// float3 ObjectRayOrigin() - - - - - - +// float3 ObjectRayDirection() - - - - - - +// float3x4 ObjectToWorld3x4() - - - - - - +// float4x3 ObjectToWorld4x3() - - - - - - +// float3x4 WorldToObject3x4() - - - - - - +// float4x3 WorldToObject4x3() - - - - - - +// uint HitKind() - - YES YES - - +// * NOTE: Intersection shaders are only supported in DXR. + +#define ALLOW_ALL_DXR_INTRINSICS 1 + +#if !ALLOW_ALL_DXR_INTRINSICS +#if !RAYGENSHADER +// These intrinsics are allowed by DXR in all shader types, but we only support them in RayGen. +// If values are required, they must be explicitly passed through the payload structure. +#define DispatchRaysIndex() DispatchRaysIndex_is_only_supported_in_raygen_shaders +#define DispatchRaysDimensions() DispatchRaysDimensions_is_only_supported_in_raygen_shaders +#endif // !RAYGENSHADER + +// These DXR intrinsics are disallowed for better cross-platform compatibility and performance. +#define RayTMin() RayTMin_is_not_supported +#define RayFlags() RayFlags_is_not_supported +#define GeometryIndex() GeometryIndex_is_not_supported +#define ObjectRayOrigin() ObjectRayOrigin_is_not_supported +#define ObjectRayDirection() ObjectRayDirection_is_not_supported +#define ObjectToWorld3x4() ObjectToWorld3x4_is_not_supported +#define ObjectToWorld4x3() ObjectToWorld4x3_is_not_supported +#define WorldToObject3x4() WorldToObject3x4_is_not_supported +#define WorldToObject4x3() WorldToObject4x3_is_not_supported +#endif // !ALLOW_ALL_DXR_INTRINSICS + // Define generic wrappers for ray tracing shader entry points, if not already overriden #ifndef RAY_TRACING_ENTRY_RAYGEN @@ -165,7 +210,9 @@ struct FMaterialClosestHitPayload : FMinimalPayload // Unpacked Packed // offset bytes // float FMinimalPayload::HitT // 0 4 32bits +#if USE_RAYTRACED_TEXTURE_RAYCONE_LOD FRayCone RayCone; // 4 8 64bits +#endif // USE_RAYTRACED_TEXTURE_RAYCONE_LOD float3 Radiance; // 8 6 48bits float3 WorldNormal; // 24 6 48bits float3 BaseColor; // 36 6 48bits @@ -203,8 +250,14 @@ struct FMaterialClosestHitPayload : FMinimalPayload void SetIgnoreTranslucentMaterials() { Flags |= RAY_TRACING_PAYLOAD_FLAG_IGNORE_TRANSLUCENT; } bool IsIgnoreTranslucentMaterials() { return (Flags & RAY_TRACING_PAYLOAD_FLAG_IGNORE_TRANSLUCENT) != 0; } - + +#if USE_RAYTRACED_TEXTURE_RAYCONE_LOD + FRayCone GetRayCone() { return RayCone; } void SetRayCone(FRayCone NewRayCone) { RayCone = NewRayCone; } +#else // USE_RAYTRACED_TEXTURE_RAYCONE_LOD + FRayCone GetRayCone() { return (FRayCone)0; } + void SetRayCone(FRayCone NewRayCone) {} +#endif // USE_RAYTRACED_TEXTURE_RAYCONE_LOD }; // WorldNormal is the vector towards which the ray position will be offseted. @@ -259,15 +312,20 @@ RayDesc CreatePrimaryRay(float2 UV) struct FPackedMaterialClosestHitPayload : FMinimalPayload { // float FMinimalPayload::HitT // 4 bytes +#if USE_RAYTRACED_TEXTURE_RAYCONE_LOD FRayCone RayCone; // 8 bytes +#endif // USE_RAYTRACED_TEXTURE_RAYCONE_LOD uint RadianceAndNormal[3]; // 12 bytes uint BaseColorAndOpacity[2]; // 8 bytes uint MetallicAndSpecularAndRoughness; // 4 bytes uint IorAndShadingModelIDAndBlendingModeAndFlagsAndPrimitiveLightingChannelMask; // 4 bytes uint PackedIndirectIrradiance[2]; // 8 bytes - uint PackedCustomData; // 4 bytes - uint WorldTangentAndAnisotropy[2]; // 8 bytes - // 60 bytes total + uint PackedCustomData; // 4 bytes -- HitGroup IN: pixel coords (16 bit packed), OUT: material custom data + uint WorldTangentAndAnisotropy[2]; // 8 bytes + // 52 to 60 bytes total + + void SetPixelCoordInCustomData(uint2 PixelCoord) { PackedCustomData = (PixelCoord.x & 0xFFFF) | (PixelCoord.y << 16); } + uint2 GetPixelCoordFromCustomData() { return uint2(PackedCustomData & 0xFFFF, PackedCustomData >> 16); } void SetMinimalPayloadMode() { IorAndShadingModelIDAndBlendingModeAndFlagsAndPrimitiveLightingChannelMask |= RAY_TRACING_PAYLOAD_FLAG_MINIMAL_PAYLOAD << 24; } bool IsMinimalPayloadMode() { return ((IorAndShadingModelIDAndBlendingModeAndFlagsAndPrimitiveLightingChannelMask >> 24) & RAY_TRACING_PAYLOAD_FLAG_MINIMAL_PAYLOAD) != 0; } @@ -278,7 +336,13 @@ struct FPackedMaterialClosestHitPayload : FMinimalPayload void SetIgnoreTranslucentMaterials() { IorAndShadingModelIDAndBlendingModeAndFlagsAndPrimitiveLightingChannelMask |= RAY_TRACING_PAYLOAD_FLAG_IGNORE_TRANSLUCENT << 24; } bool IsIgnoreTranslucentMaterials() { return ((IorAndShadingModelIDAndBlendingModeAndFlagsAndPrimitiveLightingChannelMask >> 24) & RAY_TRACING_PAYLOAD_FLAG_IGNORE_TRANSLUCENT) != 0; } - void SetRayCone(FRayCone InRayCone) { RayCone = InRayCone; } +#if USE_RAYTRACED_TEXTURE_RAYCONE_LOD + FRayCone GetRayCone() { return RayCone; } + void SetRayCone(FRayCone NewRayCone) { RayCone = NewRayCone; } +#else // USE_RAYTRACED_TEXTURE_RAYCONE_LOD + FRayCone GetRayCone() { return (FRayCone)0; } + void SetRayCone(FRayCone NewRayCone) {} +#endif // USE_RAYTRACED_TEXTURE_RAYCONE_LOD void SetRadiance(in float3 InRadiance) { @@ -370,7 +434,9 @@ FPackedMaterialClosestHitPayload PackRayTracingPayload(FMaterialClosestHitPayloa { FPackedMaterialClosestHitPayload Output = (FPackedMaterialClosestHitPayload)0; Output.HitT = Input.HitT; +#if USE_RAYTRACED_TEXTURE_RAYCONE_LOD Output.RayCone = RayCone; +#endif // USE_RAYTRACED_TEXTURE_RAYCONE_LOD Output.RadianceAndNormal[0] = f32tof16(Input.Radiance.x); Output.RadianceAndNormal[0] |= f32tof16(Input.Radiance.y) << 16; Output.RadianceAndNormal[1] = f32tof16(Input.Radiance.z); @@ -407,7 +473,9 @@ FMaterialClosestHitPayload UnpackRayTracingPayload(FPackedMaterialClosestHitPayl FMaterialClosestHitPayload Output = (FMaterialClosestHitPayload)0; Output.HitT = Input.HitT; +#if USE_RAYTRACED_TEXTURE_RAYCONE_LOD Output.RayCone = Input.RayCone; +#endif // USE_RAYTRACED_TEXTURE_RAYCONE_LOD Output.Radiance = Input.GetRadiance(); Output.WorldNormal = Input.GetWorldNormal(); Output.BaseColor = Input.GetBaseColor(); @@ -447,6 +515,7 @@ void TraceVisibilityRayPacked( in RaytracingAccelerationStructure TLAS, in uint RayFlags, in uint InstanceInclusionMask, + in uint2 PixelCoord, in RayDesc Ray) { const uint RayContributionToHitGroupIndex = RAY_TRACING_SHADER_SLOT_SHADOW; @@ -457,6 +526,8 @@ void TraceVisibilityRayPacked( PackedPayload.SetMinimalPayloadMode(); PackedPayload.HitT = 0; + PackedPayload.SetPixelCoordInCustomData(PixelCoord); + // Trace the ray TraceRay( @@ -474,11 +545,13 @@ FMinimalPayload TraceVisibilityRay( in RaytracingAccelerationStructure TLAS, in uint RayFlags, in uint InstanceInclusionMask, + in uint2 PixelCoord, in RayDesc Ray) { FPackedMaterialClosestHitPayload PackedPayload = (FPackedMaterialClosestHitPayload)0; PackedPayload.SetIgnoreTranslucentMaterials(); - TraceVisibilityRayPacked(PackedPayload, TLAS, RayFlags, InstanceInclusionMask, Ray); + + TraceVisibilityRayPacked(PackedPayload, TLAS, RayFlags, InstanceInclusionMask, PixelCoord, Ray); // Unpack the payload @@ -500,6 +573,7 @@ void TraceMaterialRayPacked( in RayDesc Ray, // Payload Inputs inout FRayCone RayCone, + in uint2 PixelCoord, in bool bEnableSkyLightContribution) { const uint RayContributionToHitGroupIndex = RAY_TRACING_SHADER_SLOT_MATERIAL; @@ -509,6 +583,7 @@ void TraceMaterialRayPacked( // Set payload inputs Payload.SetRayCone(RayCone); + Payload.SetPixelCoordInCustomData(PixelCoord); if (bEnableSkyLightContribution) { @@ -516,6 +591,7 @@ void TraceMaterialRayPacked( Payload.SetEnableSkyLightContribution(); } + // Trace the ray Payload.HitT = 0; @@ -529,7 +605,7 @@ void TraceMaterialRayPacked( Ray, Payload); - RayCone = Payload.RayCone; + RayCone = Payload.GetRayCone(); } FMaterialClosestHitPayload TraceMaterialRay( @@ -539,6 +615,7 @@ FMaterialClosestHitPayload TraceMaterialRay( in RayDesc Ray, // Payload Inputs inout FRayCone RayCone, + in uint2 PixelCoord, in bool bEnableSkyLightContribution, in bool bIgnoreTranslucentMaterials) { @@ -551,6 +628,7 @@ FMaterialClosestHitPayload TraceMaterialRay( // Set payload inputs PackedPayload.SetRayCone(RayCone); + PackedPayload.SetPixelCoordInCustomData(PixelCoord); if (bEnableSkyLightContribution) { @@ -563,6 +641,10 @@ FMaterialClosestHitPayload TraceMaterialRay( PackedPayload.SetIgnoreTranslucentMaterials(); } +#if RAYGENSHADER + PackedPayload.SetPixelCoordInCustomData(DispatchRaysIndex().xy); +#endif // RAYGENSHADER + // Trace the ray TraceRay( @@ -579,7 +661,7 @@ FMaterialClosestHitPayload TraceMaterialRay( FMaterialClosestHitPayload Payload = UnpackRayTracingPayload(PackedPayload, Ray); - RayCone = Payload.RayCone; + RayCone = Payload.GetRayCone(); return Payload; } diff --git a/Engine/Shaders/Private/RayTracing/RayTracingCreateGatherPointsRGS.usf b/Engine/Shaders/Private/RayTracing/RayTracingCreateGatherPointsRGS.usf index 9c1853d39745..63f788d5edbb 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingCreateGatherPointsRGS.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingCreateGatherPointsRGS.usf @@ -304,6 +304,7 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingCreateGatherPointsRGS) InstanceInclusionMask, Ray, RayCone, + PixelCoord, bEnableSkyLightContribution, bIgnoreTranslucentMaterials); @@ -371,6 +372,7 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingCreateGatherPointsRGS) TLAS, NeeRayFlags, NeeInstanceInclusionMask, + PixelCoord, LightRay); // No hit indicates successful next-event connection diff --git a/Engine/Shaders/Private/RayTracing/RayTracingDebug.usf b/Engine/Shaders/Private/RayTracing/RayTracingDebug.usf index 297a755a0acc..4a23df2abb65 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingDebug.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingDebug.usf @@ -35,6 +35,7 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingDebugMainRGS) InstanceInclusionMask, Ray, RayCone, + PixelCoord, bEnableSkyLightContribution, bIgnoreTranslucentMaterials); diff --git a/Engine/Shaders/Private/RayTracing/RayTracingDeferredReflections.usf b/Engine/Shaders/Private/RayTracing/RayTracingDeferredReflections.usf index 2747d2d2c4eb..d92761261e10 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingDeferredReflections.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingDeferredReflections.usf @@ -82,7 +82,7 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingDeferredReflectionsRGS) const uint2 TileBasePixelPos = uint2(TileIndex % NumTiles.x, TileIndex / NumTiles.x) * TileSize; const uint2 PixelPos = TileBasePixelPos + uint2(RayIndexInTile % TileSize.x, RayIndexInTile / TileSize.x); - ReflectionRay = GenerateDeferredReflectionRay(PixelPos, ReflectionMaxNormalBias, GlossyReflections==1); + ReflectionRay = GenerateDeferredReflectionRay(PixelPos, ReflectionMaxNormalBias, ReflectionMaxRoughness, GlossyReflections==1); RayBuffer[DispatchThreadId] = ReflectionRay; // Store the ray to be used in shading phase } #else @@ -105,8 +105,7 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingDeferredReflectionsRGS) FRayIntersectionBookmark Bookmark = (FRayIntersectionBookmark)0; FGBufferData GBuffer = GetGBufferDataFromSceneTexturesLoad(PixelPos); - float RoughnessFade = GetRoughnessFade(GBuffer.Roughness, ReflectionMaxRoughness); - bool bIsValidPixel = RoughnessFade > 0; + bool bIsValidPixel = ReflectionRay.Validity > 0; if (bIsValidPixel) { TraceDeferredMaterialGatherRay(TLAS, @@ -138,7 +137,10 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingDeferredReflectionsRGS) } float4 ResultColor = (float4)0; - float ResultDistance = DENOISER_MISS_HIT_DISTANCE; + // Unify miss condition with RayTracingReflections.usf + //float ResultDistance = DENOISER_MISS_HIT_DISTANCE; + //float ResultDistance = DENOISER_INVALID_HIT_DISTANCE; + float ResultDistance = 1.0e20; if (DeferredMaterialPayload.SortKey < RAY_TRACING_DEFERRED_MATERIAL_KEY_RAY_MISS) { @@ -152,6 +154,7 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingDeferredReflectionsRGS) FPackedMaterialClosestHitPayload PackedPayload = (FPackedMaterialClosestHitPayload)0; PackedPayload.SetEnableSkyLightContribution(); + PackedPayload.SetPixelCoordInCustomData(PixelPos); TraceDeferredMaterialShadingRay( TLAS, @@ -193,7 +196,7 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingDeferredReflectionsRGS) ResultColor.rgb); ResultColor.rgb *= View.PreExposure; - ResultColor.a = 1; + ResultColor.a = ReflectionRay.Validity; ResultDistance = PackedPayload.HitT; } diff --git a/Engine/Shaders/Private/RayTracing/RayTracingDeferredReflections.ush b/Engine/Shaders/Private/RayTracing/RayTracingDeferredReflections.ush index 6878a2b7b8a9..ff2b18277ef7 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingDeferredReflections.ush +++ b/Engine/Shaders/Private/RayTracing/RayTracingDeferredReflections.ush @@ -13,7 +13,7 @@ struct FSortedReflectionRay float3 Origin; uint PixelCoordinates; float3 Direction; - uint DebugSortKey; + float Validity; // Only technically need 8 bits, the rest could be repurposed }; uint PackPixelCoordinates(uint2 PixelCoordinates) @@ -29,7 +29,7 @@ uint2 UnpackPixelCoordinates(uint PixelCoordinates) PixelCoordinates >> 16); } -FSortedReflectionRay GenerateDeferredReflectionRay(const uint2 PixelPos, float ReflectionMaxNormalBias, bool bGlossyReflections) +FSortedReflectionRay GenerateDeferredReflectionRay(const uint2 PixelPos, float ReflectionMaxNormalBias, float MaxRoughness, bool bGlossyReflections) { const float2 ScreenPos = ViewportUVToScreenPos((PixelPos.xy - View.ViewRectMin.xy) * View.ViewSizeAndInvSize.zw); const float2 UV = PixelPos.xy * View.BufferSizeAndInvSize.zw; @@ -46,7 +46,15 @@ FSortedReflectionRay GenerateDeferredReflectionRay(const uint2 PixelPos, float R #if GBUFFER_HAS_TANGENT ModifyGGXAnisotropicNormalRoughness(GBuffer.WorldTangent, GBuffer.Anisotropy, GBuffer.Roughness, GBuffer.WorldNormal, V); #endif - + + // Use the same clearcoat approximation as SSR: simply blend base and clear coat roughness + if (GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT) + { + const float ClearCoat = GBuffer.CustomData.x; + const float ClearCoatRoughness = GBuffer.CustomData.y; + GBuffer.Roughness = lerp(GBuffer.Roughness, ClearCoatRoughness, ClearCoat); + } + float2 E = Rand1SPPDenoiserInput(PixelPos); const bool bOutputForDenoiser = true; // #todo: pass this in via constants when denoiser is on @@ -78,5 +86,7 @@ FSortedReflectionRay GenerateDeferredReflectionRay(const uint2 PixelPos, float R Ray.PixelCoordinates = PackPixelCoordinates(PixelPos); + Ray.Validity = GetRoughnessFade(GBuffer.Roughness, MaxRoughness); + return Ray; } diff --git a/Engine/Shaders/Private/RayTracing/RayTracingDirectionalLight.ush b/Engine/Shaders/Private/RayTracing/RayTracingDirectionalLight.ush index dc4cd33f7fe4..bfae00ca072c 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingDirectionalLight.ush +++ b/Engine/Shaders/Private/RayTracing/RayTracingDirectionalLight.ush @@ -33,6 +33,7 @@ float2 ToConcentricMap(float2 RectangularCoords) void GenerateDirectionalLightOcclusionRay( FLightShaderParameters LightParameters, + float TraceDistance, float3 WorldPosition, float3 WorldNormal, float2 RandSample, @@ -64,5 +65,5 @@ void GenerateDirectionalLightOcclusionRay( RayOrigin = WorldPosition; RayDirection = normalize(LightDirection); RayTMin = 0.0; - RayTMax = 1.0e27; + RayTMax = (TraceDistance > 0) ? TraceDistance : 1.0e27; } diff --git a/Engine/Shaders/Private/RayTracing/RayTracingFinalGatherRGS.usf b/Engine/Shaders/Private/RayTracing/RayTracingFinalGatherRGS.usf index 0889723f438b..9dff8c92153f 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingFinalGatherRGS.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingFinalGatherRGS.usf @@ -93,7 +93,18 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingFinalGatherRGS) // Define rejection criteria and evaluate bool bShouldReject[MAXIMUM_GATHER_POINTS_PER_PIXEL]; - FRejectionCriteria RejectionCriteria = CreateRejectionCriteria(WorldPosition, FinalGatherDistance); + + // Create ray differential based on rejection distance (in pixels) + UV = (float2(PixelCoord) + 0.5 + float2(0.0, FinalGatherDistance * UpscaleFactor)) * InvBufferSize; + RayDesc Ray = CreatePrimaryRay(UV); + + // Intersect ray differential with normal to find world-space distance + float d = -dot(WorldNormal, WorldPosition); + float T = -(d + dot(WorldNormal, Ray.Origin)) / dot(WorldNormal, Ray.Direction); + float3 WorldDeltaPosition = Ray.Origin + T * Ray.Direction; + float RejectionDistance = length(WorldDeltaPosition - WorldPosition); + + FRejectionCriteria RejectionCriteria = CreateRejectionCriteria(WorldPosition, RejectionDistance); CreateRejectionMask(RejectionCriteria, GatherPointsBuffer, GatherPointsIndex, 0, GatherPointsResolution.x * GatherPointsResolution.y, SamplesPerPixel, bShouldReject); float3 ExitantRadiance = 0.0; @@ -143,6 +154,7 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingFinalGatherRGS) TLAS, RayFlags, InstanceInclusionMask, + PixelCoord, Ray); // No hit indicates successful next-event connection diff --git a/Engine/Shaders/Private/RayTracing/RayTracingGlobalIlluminationRGS.usf b/Engine/Shaders/Private/RayTracing/RayTracingGlobalIlluminationRGS.usf index bd9674b20588..49b15f73ce62 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingGlobalIlluminationRGS.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingGlobalIlluminationRGS.usf @@ -171,6 +171,7 @@ RAY_TRACING_ENTRY_RAYGEN(GlobalIlluminationRGS) InstanceInclusionMask, Ray, RayCone, + PixelCoord, bEnableSkyLightContribution, bIgnoreTranslucentMaterials); @@ -257,6 +258,7 @@ RAY_TRACING_ENTRY_RAYGEN(GlobalIlluminationRGS) TLAS, NeeRayFlags, NeeInstanceInclusionMask, + PixelCoord, LightRay); // No hit indicates successful next-event connection diff --git a/Engine/Shaders/Private/RayTracing/RayTracingLightCullingCommon.ush b/Engine/Shaders/Private/RayTracing/RayTracingLightCullingCommon.ush new file mode 100644 index 000000000000..249db77df042 --- /dev/null +++ b/Engine/Shaders/Private/RayTracing/RayTracingLightCullingCommon.ush @@ -0,0 +1,110 @@ + + +/*============================================================================================= + RayTracingLightCullingCommon.ush: Common functions for culling lights. +===============================================================================================*/ + +#pragma once + +#define CULL_VOLUME_STRUCTURED_BUFFER 1 + + +// *************** LIGHT CULLING ********************************* +// Use view-centered, world-aligned volume to store references to lights impacting cells +// Volume cells store light indices directly if space permits, otherwise indirect into +// buffer of light indices + +struct FCulledLightList +{ + uint4 LightCellData; + + static const uint PackedLightBits = 10; + static const uint PackedLightsPerComponent = 3; + static const uint PackedLightMask = (1 << PackedLightBits) - 1; + static const uint PackedLightsMax = 11; + + static FCulledLightList Create(float3 WorldPos) + { + FCulledLightList Result = (FCulledLightList)0; + + // volume is centered on the viewer + float3 Position = WorldPos - View.WorldViewOrigin; + + const float Scale = 100.0f; + const int Dim = RaytracingLightsDataPacked.CellCount; + + Position /= RaytracingLightsDataPacked.CellScale; + + // symmetric about the viewer + float3 Region = sign(Position); + Position = abs(Position); + + // logarithmic steps with the closest cells being 2x2x2 m + Position = max(Position, 2.0f); + Position = min(log2(Position) - 1.0f, (Dim/2 - 1)); + + Position = floor(Position); + + // move the the edge to the center + Position += 0.5f; + + // map it back to quadrants + Position *= Region; + + // remap [-Dim/2, Dim/2] to [0, Dim] + Position += (Dim / 2.0f); + + // keep it inside the volume since we're using Load rather than a sampler + Position = min(Position, (Dim - 0.5f)); + Position = max(Position, 0.0f); + + int3 Coord = Position; + + uint Address = (Coord.z * RaytracingLightsDataPacked.CellCount + Coord.y) * RaytracingLightsDataPacked.CellCount + Coord.x; + Result.LightCellData = RaytracingLightsDataPacked.LightCullingVolume[Address]; + + + return Result; + } + + uint NumLights() + { + const bool bPacked = (LightCellData[0] & (1 << 31)) > 0; + + return bPacked ? (LightCellData[3] >> (PackedLightBits * 2)) & PackedLightMask : LightCellData.x; + } + + uint GetLightIndex(int LightNum, out uint Valid) + { + uint LightIndex = 0; + Valid = 0; + + const uint LightCount = NumLights(); + + const bool bPacked = (LightCellData[0] & (1 << 31)) > 0; + if (bPacked) + { + // packed lights + uint Shift = (LightNum % PackedLightsPerComponent) * PackedLightBits; + uint PackedLightIndices = LightCellData[LightNum / PackedLightsPerComponent]; + uint UnpackedLightIndex = (PackedLightIndices >> Shift) & PackedLightMask; + + if (LightNum < LightCount) + { + Valid = 1; + LightIndex = UnpackedLightIndex; + } + } + else + { + // non-packed lights + if (LightNum < LightCount) + { + Valid = 1; + LightIndex = RaytracingLightsDataPacked.LightIndices[LightCellData.y + LightNum]; + } + } + return LightIndex; + } + +}; diff --git a/Engine/Shaders/Private/RayTracing/RayTracingLightingCommon.ush b/Engine/Shaders/Private/RayTracing/RayTracingLightingCommon.ush index 061477596d24..82146369f827 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingLightingCommon.ush +++ b/Engine/Shaders/Private/RayTracing/RayTracingLightingCommon.ush @@ -18,6 +18,7 @@ #include "RayTracingSpotLight.ush" #include "RayTracingPointLight.ush" #include "RayTracingSkyLightEvaluation.ush" +#include "RayTracingLightCullingCommon.ush" // Light types: should match SceneTypes.h until there is a common header #define LIGHT_TYPE_DIRECTIONAL 0 @@ -132,12 +133,13 @@ FDeferredLightData GetRayTracingDeferredLightData(int LightIndex, LightData.SpecularScale = RayTracingLightData.SpecularScale; LightData.RectLightBarnCosAngle = RayTracingLightData.RectLightBarnCosAngle; LightData.RectLightBarnLength = RayTracingLightData.RectLightBarnLength; - LightData.ContactShadowLength = 0.0; LightData.DistanceFadeMAD = RayTracingLightData.DistanceFadeMAD; LightData.ShadowMapChannelMask = float4(0, 0, 0, 0); LightData.ShadowedBits = 0; // Not lit dynamic shadows + LightData.ContactShadowLength = 0.0; LightData.ContactShadowLengthInWS = false; + LightData.ContactShadowNonShadowCastingIntensity = 0.0f; LightData.bRadialLight = (LightType != LIGHT_TYPE_DIRECTIONAL); LightData.bSpotLight = (LightType == LIGHT_TYPE_SPOT); @@ -220,8 +222,10 @@ void TraceShadowRayMissShaderLighting( in uint InstanceInclusionMask, in RaytracingAccelerationStructure TLAS, in uint MissShaderIndex, + in uint2 PixelCoord, inout FPackedMaterialClosestHitPayload PackedPayload) { + PackedPayload.SetPixelCoordInCustomData(PixelCoord); TraceRay ( TLAS, @@ -262,8 +266,12 @@ float3 SampleAreaLightDirection( if (LightType == LIGHT_TYPE_DIRECTIONAL) { + float ShadowSourceAngleFactor = LightData.RectLightBarnCosAngle; + LightParameters.SourceRadius *= ShadowSourceAngleFactor; + float TraceDistance = 0.0; GenerateDirectionalLightOcclusionRay( LightParameters, + TraceDistance, WorldPosition, WorldNormal, RandSample, /* out */ RayOrigin, @@ -325,208 +333,6 @@ float3 SampleAreaLightDirection( return ShadowRayDirection; } -uint CullDirectLighting( - in uint BaseLightIndex, - in float3 WorldPosition, - in float3 WorldNormal) -{ - uint LightCullMask = 0; - - uint MaxLightIndex = min(BaseLightIndex + 32, RaytracingLightsDataPacked.Count); - - for (uint LightIndex = BaseLightIndex; LightIndex < MaxLightIndex; LightIndex++) - { - uint Lit = 1; - - int LightProfileIndex = -1; - int RectLightTextureIndex = -1; - uint LightType = 0; - FDeferredLightData LightData = GetRayTracingDeferredLightData(LightIndex, LightProfileIndex, RectLightTextureIndex, LightType); - - float3 ShadowRayDirection; - // ToLight should not be normalized because its length is used to compute the shadow ray TMax - float3 ToLight = LightData.Position - WorldPosition; - float LightMask = 1.0; - - if (LightType == LIGHT_TYPE_DIRECTIONAL) - { - ShadowRayDirection = LightData.Direction; - ToLight = LightData.Direction * 100000.0f; - } - else - { - LightMask = GetLocalLightAttenuation(WorldPosition, LightData, ToLight, ShadowRayDirection); - - // Skip the light sample that does not contribute anything due to attenuation. - if (LightMask <= 0.0) - { - Lit = 0; - } - } - - // Skip the light sample pointing backwards - if (dot(WorldNormal, normalize(ToLight)) <= 0) - { - Lit = 0; - } - - LightCullMask |= Lit << (LightIndex- BaseLightIndex); - } - - return LightCullMask; -} - -float3 ComputeDirectLightingCulled( - in uint LightCullMask, - in uint BaseLightIndex, - in float3 WorldPosition, - in float3 ViewDirection, - in FRayCone RayCone, - in RaytracingAccelerationStructure TLAS, - inout FPackedMaterialClosestHitPayload Payload, - in RandomSequence RandSequence, - in uint ReflectedShadowsType, - in float ShadowMaxNormalBias) -{ - float3 DirectLighting = float3(0.0, 0.0, 0.0); - - float AmbientOcclusion = 1.0; - - FGBufferData GBufferData = GetGBufferDataFromPayload(Payload); - FRectTexture RectTexture = GetRayTracingRectTextureData(); - -#if DIM_MISS_SHADER_LIGHTING - // Repurpose some fields in the material payload to pass parameters into lighting miss shader. - float3 OldRadiance = Payload.GetRadiance(); - float3 OldIndirectIrradiance = Payload.GetIndirectIrradiance(); - Payload.SetRadiance(float3(0, 0, 0)); - Payload.SetIndirectIrradiance(ViewDirection); -#endif - - uint LightIndex = BaseLightIndex; - - while (WaveActiveAnyTrue(LightCullMask)) - { - const bool Active = LightCullMask; - - if (Active) - { - if ((LightCullMask & 0x1) == 0) - { - uint Shift = LightCullMask ? firstbitlow(LightCullMask) : 32; - LightIndex += Shift; - LightCullMask >>= Shift; - } - } - else - { - LightIndex = 0; - } - - int LightProfileIndex = -1; - int RectLightTextureIndex = -1; - uint LightType = 0; - FDeferredLightData LightData = GetRayTracingDeferredLightData(LightIndex, LightProfileIndex, RectLightTextureIndex, LightType); - -#if USE_SOURCE_TEXTURE_ARRAY - RectTexture.SourceTextureIndex = RectLightTextureIndex; -#endif // USE_SOURCE_TEXTURE_ARRAY - - float LightProfileMultiplier = 1.0; - - if (LightProfileIndex >= 0) - { - LightProfileMultiplier = ComputeRayTracingLightProfileMultiplier(WorldPosition, LightData.Position, LightData.Direction, LightProfileIndex); - } - - float3 ShadowRayDirection; - // ToLight should not be normalized because its length is used to compute the shadow ray TMax - float3 ToLight = LightData.Position - WorldPosition; - float LightMask = 1.0; - - if (LightType == LIGHT_TYPE_DIRECTIONAL) - { - ShadowRayDirection = LightData.Direction; - ToLight = LightData.Direction * 100000.0f; - } - else - { - LightMask = GetLocalLightAttenuation(WorldPosition, LightData, ToLight, ShadowRayDirection); - } - - // When shading in the miss shader, always cast a ray - bool EvaluateShadows = (ReflectedShadowsType > 0) || (DIM_MISS_SHADER_LIGHTING != 0); - - if (EvaluateShadows) - { - if (ReflectedShadowsType == 2) - { - ShadowRayDirection = SampleAreaLightDirection(LightData, WorldPosition, Payload.GetWorldNormal(), LightType, RandSequence); - } - - // Force a miss when the thread is inactive or lighting in the miss shader while unshadowed - bool ForceMiss = !Active || (DIM_MISS_SHADER_LIGHTING != 0 && ReflectedShadowsType == 0); - - RayDesc ShadowRay; - ShadowRay.Origin = WorldPosition; - ShadowRay.Direction = ShadowRayDirection; - ShadowRay.TMin = 1e-4f; - ShadowRay.TMax = ForceMiss ? ShadowRay.TMin : length(ToLight); - ApplyPositionBias(ShadowRay, Payload.GetWorldNormal(), ShadowMaxNormalBias); - - uint RayFlags = RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER; - const uint InstanceInclusionMask = ForceMiss ? 0 : RAY_TRACING_MASK_SHADOW; - -#if !ENABLE_TWO_SIDED_GEOMETRY - RayFlags |= RAY_FLAG_CULL_BACK_FACING_TRIANGLES; -#endif // !ENABLE_TWO_SIDED_GEOMETRY - -#if DIM_MISS_SHADER_LIGHTING - - // Light index is packed into HitT as this component is only accessed by closest hit or miss shaders. - // Since closest hit execution is disabled using a ray flag, it is safe to pack custom data here. - Payload.HitT = asfloat(LightIndex); - - // use the bound miss shader lighting evaluation if lighting, else nothing - uint MissShaderIndex = Active ? RAY_TRACING_MISS_SHADER_SLOT_LIGHTING : RAY_TRACING_MISS_SHADER_SLOT_DEFAULT; - TraceShadowRayMissShaderLighting(ShadowRay, RayFlags, InstanceInclusionMask, TLAS, MissShaderIndex, Payload); - -#else // DIM_MISS_SHADER_LIGHTING - - FMinimalPayload ShadowRayPayload = TraceVisibilityRay( - TLAS, - RayFlags, - InstanceInclusionMask, - ShadowRay); - AmbientOcclusion = ShadowRayPayload.IsMiss() && Active; - -#endif // DIM_MISS_SHADER_LIGHTING - } - -#if !DIM_MISS_SHADER_LIGHTING - // Light in RGS - if (Active) - { - float SurfaceShadow = 1.0f; - float4 LightAttenuation = 1.0f; - float3 LightContribution = GetDynamicLighting(WorldPosition, ViewDirection, GBufferData, AmbientOcclusion, GBufferData.ShadingModelID, LightData, LightAttenuation, 0.5, uint2(0, 0), RectTexture, SurfaceShadow).xyz; - DirectLighting += LightContribution * LightProfileMultiplier; - } -#endif // !DIM_MISS_SHADER_LIGHTING - - LightIndex += 1; - LightCullMask >>= 1; - } - -#if DIM_MISS_SHADER_LIGHTING - DirectLighting = Payload.GetRadiance(); - Payload.SetRadiance(OldRadiance); - Payload.SetIndirectIrradiance(OldIndirectIrradiance); -#endif // DIM_MISS_SHADER_LIGHTING - - return DirectLighting; -} - float3 ComputeIndirectLighting( in float3 WorldPosition, in float3 ViewDirection, @@ -583,10 +389,11 @@ float3 ComputeIndirectLighting( return IndirectLighting; } -float3 ComputeDirectLightingMonolithic( +float3 ComputeDirectLighting( in float3 WorldPosition, in float3 ViewDirection, in FRayCone RayCone, + in uint2 PixelCoord, in RaytracingAccelerationStructure TLAS, inout FPackedMaterialClosestHitPayload Payload, in RandomSequence RandSequence, @@ -595,9 +402,24 @@ float3 ComputeDirectLightingMonolithic( { float3 DirectLighting = (float3)0; - for (uint LightIndex = 0; LightIndex < RaytracingLightsDataPacked.Count; LightIndex++) + +#if DIM_MISS_SHADER_LIGHTING + // Repurpose some fields in the material payload to pass parameters into lighting miss shader. + float3 OldRadiance = Payload.GetRadiance(); + float3 OldIndirectIrradiance = Payload.GetIndirectIrradiance(); + Payload.SetRadiance(float3(0, 0, 0)); + Payload.SetIndirectIrradiance(ViewDirection); +#endif + + FCulledLightList CullData = FCulledLightList::Create(WorldPosition); + const uint LightCount = CullData.NumLights(); + + for (uint Index = 0; WaveActiveAnyTrue(Index < LightCount); Index++) { - uint Lit = 1; + uint Lit = 0; + uint LightIndex = 0; + + LightIndex = CullData.GetLightIndex(Index, Lit); int LightProfileIndex = -1; int RectLightTextureIndex = -1; @@ -654,15 +476,28 @@ float3 ComputeDirectLightingMonolithic( uint RayFlags = RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER; const uint InstanceInclusionMask = (ApplyShadow) ? RAY_TRACING_MASK_SHADOW : 0; - #if !ENABLE_TWO_SIDED_GEOMETRY +#if !ENABLE_TWO_SIDED_GEOMETRY RayFlags |= RAY_FLAG_CULL_BACK_FACING_TRIANGLES; - #endif // !ENABLE_TWO_SIDED_GEOMETRY +#endif // !ENABLE_TWO_SIDED_GEOMETRY + +#if DIM_MISS_SHADER_LIGHTING + + // Light index is packed into HitT as this component is only accessed by closest hit or miss shaders. + // Since closest hit execution is disabled using a ray flag, it is safe to pack custom data here. + Payload.HitT = asfloat(LightIndex); + + // use the bound miss shader lighting evaluation if lighting, else nothing + uint MissShaderIndex = Lit ? RAY_TRACING_MISS_SHADER_SLOT_LIGHTING : RAY_TRACING_MISS_SHADER_SLOT_DEFAULT; + TraceShadowRayMissShaderLighting(ShadowRay, RayFlags, InstanceInclusionMask, TLAS, MissShaderIndex, PixelCoord, Payload); + +#else // DIM_MISS_SHADER_LIGHTING TraceVisibilityRayPacked( Payload, TLAS, RayFlags, InstanceInclusionMask, + PixelCoord, ShadowRay); IsLightVisible = Payload.IsMiss() && Lit; @@ -670,9 +505,9 @@ float3 ComputeDirectLightingMonolithic( FRectTexture RectTexture = GetRayTracingRectTextureData(); - #if USE_SOURCE_TEXTURE_ARRAY +#if USE_SOURCE_TEXTURE_ARRAY RectTexture.SourceTextureIndex = RectLightTextureIndex; - #endif // USE_SOURCE_TEXTURE_ARRAY +#endif // USE_SOURCE_TEXTURE_ARRAY float LightProfileMultiplier = 1.0; @@ -688,11 +523,21 @@ float3 ComputeDirectLightingMonolithic( float3 LightContribution = GetDynamicLighting(WorldPosition, ViewDirection, GBufferData, 1.0, GBufferData.ShadingModelID, LightData, LightAttenuation, 0.5, uint2(0, 0), RectTexture, SurfaceShadow).xyz; DirectLighting += LightContribution * LightProfileMultiplier; +#endif } + +#if DIM_MISS_SHADER_LIGHTING + DirectLighting = Payload.GetRadiance(); + Payload.SetRadiance(OldRadiance); + Payload.SetIndirectIrradiance(OldIndirectIrradiance); +#endif // DIM_MISS_SHADER_LIGHTING + return DirectLighting; } + + void ComputeBottomLayerMaterialProperties(RayDesc Ray, inout FMaterialClosestHitPayload Payload) { // #dxr_todo: Remove me @@ -726,17 +571,7 @@ void AccumulateResults( // Save and restore original payload HitT, as it's modified during shadow ray tracing float OldHitT = Payload.HitT; -#if DIM_MISS_SHADER_LIGHTING - // cull lights in batches of 32 - for (uint BaseLightIndex = 0; BaseLightIndex < RaytracingLightsDataPacked.Count; BaseLightIndex += 32) - { - uint LightCullMask = 0; - LightCullMask = CullDirectLighting(BaseLightIndex, WorldPosition, Payload.GetWorldNormal()); - DirectLighting += ComputeDirectLightingCulled(LightCullMask, BaseLightIndex, WorldPosition, ViewDirection, RayCone, TLAS, Payload, RandSequence, ReflectedShadowsType, ShadowMaxNormalBias); - } -#else // DIM_MISS_SHADER_LIGHTING - DirectLighting = ComputeDirectLightingMonolithic(WorldPosition, ViewDirection, RayCone, TLAS, Payload, RandSequence, ReflectedShadowsType, ShadowMaxNormalBias); -#endif // DIM_MISS_SHADER_LIGHTING + DirectLighting = ComputeDirectLighting(WorldPosition, ViewDirection, RayCone, PixelCoord, TLAS, Payload, RandSequence, ReflectedShadowsType, ShadowMaxNormalBias); Payload.HitT = OldHitT; } @@ -795,6 +630,7 @@ FMaterialClosestHitPayload TraceRayAndAccumulateResults( InstanceInclusionMask, Ray, RayCone, + PixelCoord, bEnableSkyLightContribution); float3 WorldPosition = Ray.Origin + Ray.Direction * Payload.HitT; @@ -850,6 +686,7 @@ FMaterialClosestHitPayload TraceRayAndAccumulateBottomLayerResults( InstanceInclusionMask, Ray, RayCone, + PixelCoord, bEnableSkyLightContribution, bIgnoreTranslucentMaterials); diff --git a/Engine/Shaders/Private/RayTracing/RayTracingMaterialHitShaders.usf b/Engine/Shaders/Private/RayTracing/RayTracingMaterialHitShaders.usf index 0a75a63dfced..9e85fb10e6ac 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingMaterialHitShaders.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingMaterialHitShaders.usf @@ -211,11 +211,16 @@ void GetPrecomputedIndirectLightingAndSkyLight( void CalcInterpolants(in FRayCone RayCone, in FDefaultAttributes Attributes, out FVertexFactoryInterpolantsVSToPS Interpolants) { - FVertexFactoryInterpolantsVSToDS PerVertexInterpolants[3]; - float4 PerVertexClipSpacePositions[3]; // Used to fake SvPosition + FVertexFactoryInterpolantsVSToDS Interpolated = (FVertexFactoryInterpolantsVSToDS)0; + float3 WorldPositions[3]; float2 TexCoords[3]; + float3 Weights = float3( + 1 - Attributes.Barycentrics.x - Attributes.Barycentrics.y, + Attributes.Barycentrics.x, + Attributes.Barycentrics.y); + for (int i = 0; i < 3; i++) { FVertexFactoryInput Input = LoadVertexFactoryInputForHGS(PrimitiveIndex(), i); @@ -224,28 +229,15 @@ void CalcInterpolants(in FRayCone RayCone, in FDefaultAttributes Attributes, out float3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates); float4 WorldPositionExcludingWPO = VertexFactoryGetWorldPosition(Input, VFIntermediates); FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPositionExcludingWPO.xyz, TangentToLocal); - - WorldPositions[i] = WorldPositionExcludingWPO.xyz; - float4 WorldPosition = WorldPositionExcludingWPO + float4(GetMaterialWorldPositionOffset(VertexParameters), 0.0f); - - PerVertexInterpolants[i] = VertexFactoryGetInterpolantsVSToDS(Input, VFIntermediates, VertexParameters); - TexCoords[i] = VertexFactoryGetTextureCoordinateDS(PerVertexInterpolants[i]); - float4 RasterizedWorldPosition = VertexFactoryGetRasterizedWorldPosition(Input, VFIntermediates, WorldPosition); - PerVertexClipSpacePositions[i] = mul(RasterizedWorldPosition, ResolvedView.TranslatedWorldToClip); + FVertexFactoryInterpolantsVSToDS PerVertexInterpolants = VertexFactoryGetInterpolantsVSToDS(Input, VFIntermediates, VertexParameters); + Interpolated = VertexFactoryInterpolate(Interpolated, 1.0, PerVertexInterpolants, Weights[i]); + + WorldPositions[i] = WorldPositionExcludingWPO.xyz; + TexCoords[i] = VertexFactoryGetTextureCoordinateDS(PerVertexInterpolants); } - - FVertexFactoryInterpolantsVSToDS Interpolated = - VertexFactoryInterpolate( - VertexFactoryInterpolate( - PerVertexInterpolants[0], 1 - Attributes.Barycentrics.x - Attributes.Barycentrics.y, - PerVertexInterpolants[1], Attributes.Barycentrics.x), - 1.0f, - PerVertexInterpolants[2], Attributes.Barycentrics.y); - + Interpolants = VertexFactoryAssignInterpolants(Interpolated); - - float4 ClipSpacePosition = PerVertexClipSpacePositions[0] * (1 - Attributes.Barycentrics.x - Attributes.Barycentrics.y) + PerVertexClipSpacePositions[1] * Attributes.Barycentrics.x + PerVertexClipSpacePositions[2] * Attributes.Barycentrics.y; #if (NUM_TEX_COORD_INTERPOLATORS || USE_PARTICLE_SUBUVS) && !VERTEX_FACTORY_MODIFIES_TESSELLATION float2 TA = TexCoords[1] - TexCoords[0]; @@ -283,12 +275,14 @@ RAY_TRACING_ENTRY_CLOSEST_HIT(MaterialCHS, return; } + const uint2 PixelCoord = PackedPayload.GetPixelCoordFromCustomData(); + ResolvedView = ResolveView(); FVertexFactoryInterpolantsVSToPS Interpolants; - float4 SvPosition = float4(float2(DispatchRaysIndex().xy) + ResolvedView.ViewRectMin.xy, 0.0, 1.0); + float4 SvPosition = float4(PixelCoord.xy, 0.0, 1.0); - FRayCone PropagatedCone = PropagateRayCone(PackedPayload.RayCone, 0 /* surface curvature */, RayTCurrent()); + FRayCone PropagatedCone = PropagateRayCone(PackedPayload.GetRayCone(), 0 /* surface curvature */, RayTCurrent()); CalcInterpolants(PropagatedCone, Attributes, Interpolants); FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Interpolants, SvPosition); @@ -473,12 +467,15 @@ RAY_TRACING_ENTRY_ANY_HIT(MaterialAHS, #endif // MATERIALBLENDING_SOLID #if MATERIALBLENDING_MASKED + + const uint2 PixelCoord = PackedPayload.GetPixelCoordFromCustomData(); + ResolvedView = ResolveView(); FVertexFactoryInterpolantsVSToPS Interpolants; - float4 SvPosition = float4(float2(DispatchRaysIndex().xy) + ResolvedView.ViewRectMin.xy, 0.0, 1.0); - - CalcInterpolants(PackedPayload.RayCone, Attributes, Interpolants); + float4 SvPosition = float4(PixelCoord.xy, 0.0, 1.0); + + CalcInterpolants(PackedPayload.GetRayCone(), Attributes, Interpolants); FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Interpolants, SvPosition); FPixelMaterialInputs PixelMaterialInputs; @@ -517,4 +514,4 @@ RAY_TRACING_ENTRY_ANY_HIT(MaterialAHS, } #endif // USE_MATERIAL_ANY_HIT_SHADER -} +} diff --git a/Engine/Shaders/Private/RayTracing/RayTracingOcclusionRGS.usf b/Engine/Shaders/Private/RayTracing/RayTracingOcclusionRGS.usf index 01e1e05bb65c..a30c7cec1a57 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingOcclusionRGS.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingOcclusionRGS.usf @@ -38,6 +38,9 @@ uint SamplesPerPixel; float NormalBias; int4 LightScissor; int2 PixelOffset; +float TraceDistance; +float LODTransitionStart; +float LODTransitionEnd; #if USE_HAIR_LIGHTING #include "../HairStrands/HairStrandsRaytracing.ush" @@ -77,6 +80,7 @@ bool GenerateOcclusionRay( { GenerateDirectionalLightOcclusionRay( LightParameters, + TraceDistance, WorldPosition, WorldNormal, RandSample, /* out */ RayOrigin, @@ -142,7 +146,12 @@ bool GenerateOcclusionRay( return true; } -float ComputeDiffuseTransmission(float3 V, float3 P, float3 N, float MeanFreePath, float Eta, FLightShaderParameters LightParameters, float2 LightSample, inout RandomSequence RandSequence, uint RejectionRetryMax) +float ComputeDiffuseTransmission( + float3 V, float3 P, float3 N, + float MeanFreePath, float Eta, + FLightShaderParameters LightParameters, float2 LightSample, + inout RandomSequence RandSequence, + uint RejectionRetryMax, uint2 PixelCoord) { float TransmissionDistance = SSSS_MAX_TRANSMISSION_PROFILE_DISTANCE; float RcpMeanFreePath = rcp(MeanFreePath); @@ -173,6 +182,7 @@ float ComputeDiffuseTransmission(float3 V, float3 P, float3 N, float MeanFreePat TLAS, RayFlags, InstanceInclusionMask, + PixelCoord, RejectionTestRay); float RejectionDistancePdf = ScatterDistancePdf; @@ -190,6 +200,7 @@ float ComputeDiffuseTransmission(float3 V, float3 P, float3 N, float MeanFreePat TLAS, RayFlags, InstanceInclusionMask, + PixelCoord, RejectionTestRay); } if (RejectionTestPayload.IsHit()) return 0.0; @@ -209,6 +220,7 @@ float ComputeDiffuseTransmission(float3 V, float3 P, float3 N, float MeanFreePat TLAS, RayFlags, InstanceInclusionMask, + PixelCoord, SSSRay); if (SSSPayload.IsHit()) @@ -220,6 +232,7 @@ float ComputeDiffuseTransmission(float3 V, float3 P, float3 N, float MeanFreePat TLAS, RayFlags, InstanceInclusionMask, + PixelCoord, SSSRay); if (VisibilityPayload.IsMiss()) { @@ -260,6 +273,17 @@ float OcclusionToShadow(FOcclusionResult In, uint LocalSamplesPerPixel) return (LocalSamplesPerPixel > 0) ? In.Visibility / LocalSamplesPerPixel : In.Visibility; } +RayDesc Invert(in RayDesc Ray) +{ + RayDesc InvertedRay; + InvertedRay.TMin = Ray.TMin; + InvertedRay.TMax = Ray.TMax; + InvertedRay.Origin = Ray.Origin + Ray.Direction * InvertedRay.TMax; + InvertedRay.Direction = -Ray.Direction; + + return InvertedRay; +} + FOcclusionResult ComputeOcclusion( const uint2 PixelCoord, RandomSequence RandSequence, @@ -303,6 +327,12 @@ FOcclusionResult ComputeOcclusion( bApplyNormalCulling = false; } + // Disable normal culling for RectLights + if (LIGHT_TYPE == LIGHT_TYPE_RECT) + { + bApplyNormalCulling = false; + } + #if ENABLE_MULTIPLE_SAMPLES_PER_PIXEL LOOP for (uint SampleIndex = 0; SampleIndex < LocalSamplesPerPixel; ++SampleIndex) #else // ENABLE_MULTIPLE_SAMPLES_PER_PIXEL @@ -353,13 +383,13 @@ FOcclusionResult ComputeOcclusion( #endif BRANCH - if (!bIsValidRay) + if (!bIsValidRay && (DIM_DENOISER_OUTPUT == 0)) { + // The denoiser must still trace invalid rays to get the correct closest-hit distance continue; } else if (bApplyNormalCulling && dot(WorldNormal, Ray.Direction) <= 0.0) { - Out.ClosestRayDistance = 0.001; continue; } @@ -379,7 +409,7 @@ FOcclusionResult ComputeOcclusion( RayFlags |= RAY_FLAG_CULL_BACK_FACING_TRIANGLES; #endif - if (DIM_DENOISER_OUTPUT == 0 || ShadingModelID == SHADINGMODELID_HAIR) + if (ShadingModelID == SHADINGMODELID_HAIR) { // Denoiser mode 0 doesn't use depth, so take first hit RayFlags |= RAY_FLAG_SKIP_CLOSEST_HIT_SHADER | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH; @@ -389,6 +419,7 @@ FOcclusionResult ComputeOcclusion( TLAS, RayFlags, RaytracingMask, + PixelCoord, Ray); #if USE_HAIR_LIGHTING @@ -399,18 +430,55 @@ FOcclusionResult ComputeOcclusion( #endif Out.RayCount += 1.0; - Out.Visibility += MinimalPayload.IsMiss() ? 1.0 : 0.0; if (MinimalPayload.IsHit()) { + float HitT = MinimalPayload.HitT; + Out.ClosestRayDistance = (Out.ClosestRayDistance == DENOISER_INVALID_HIT_DISTANCE) || - (MinimalPayload.HitT < Out.ClosestRayDistance) ? MinimalPayload.HitT : Out.ClosestRayDistance; - Out.SumRayDistance += MinimalPayload.HitT; + (HitT < Out.ClosestRayDistance) ? HitT : Out.ClosestRayDistance; + Out.SumRayDistance += HitT; Out.HitCount += 1.0; + + if ((ShadingModelID == SHADINGMODELID_SUBSURFACE)) + { + // Potentially reverse the ray to support sub-surface casts + RayDesc InvertedRay = Invert(Ray); + FMinimalPayload SubsurfacePayload = TraceVisibilityRay( + TLAS, + RayFlags, + RaytracingMask, + PixelCoord, + InvertedRay); + + if (SubsurfacePayload.IsHit()) + { + + float3 HitPosition = InvertedRay.Origin + InvertedRay.Direction * SubsurfacePayload.HitT; + float Opacity = TransmissionProfileParams.ExtinctionScale; + float Density = -.05f * log(1 - min(Opacity, .999f)); + float ExtinctionCoefficient = Density; + float Thickness = length(HitPosition - WorldPosition); + float Transmission = saturate(exp(-ExtinctionCoefficient * Thickness)); + + if (Transmission == 1.0) + { + Out.ClosestRayDistance = (Out.ClosestRayDistance == DENOISER_INVALID_HIT_DISTANCE) ? DENOISER_MISS_HIT_DISTANCE : Out.ClosestRayDistance; + Out.SumRayDistance -= HitT; + Out.HitCount -= 1.0; + Out.TransmissionDistance += 1.0; + Out.Visibility += 1.0; + } + + Out.TransmissionDistance += Transmission; + } + } } else { Out.ClosestRayDistance = (Out.ClosestRayDistance == DENOISER_INVALID_HIT_DISTANCE) ? DENOISER_MISS_HIT_DISTANCE : Out.ClosestRayDistance; + Out.TransmissionDistance += 1.0; + Out.Visibility += 1.0; } } #if !ENABLE_MULTIPLE_SAMPLES_PER_PIXEL @@ -428,9 +496,20 @@ FOcclusionResult ComputeOcclusion( const float Eta = TransmissionProfileParams.OneOverIOR; const uint RejectionRetryMax = LocalSamplesPerPixel - 1; - Out.TransmissionDistance = ComputeDiffuseTransmission(V, WorldPosition, WorldNormal, MeanFreePath, Eta, LightParameters, LightSample, RandSequence, RejectionRetryMax); + Out.TransmissionDistance = ComputeDiffuseTransmission(V, WorldPosition, WorldNormal, MeanFreePath, Eta, LightParameters, LightSample, RandSequence, RejectionRetryMax, PixelCoord); } - else // if ((ShadingModelID == SHADINGMODELID_EYE) || (ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE) || (ShadingModelID == SHADINGMODELID_SUBSURFACE)) + else if (ShadingModelID == SHADINGMODELID_SUBSURFACE) + { + float Depth = length(WorldPosition - View.WorldCameraOrigin); + float Range = LODTransitionEnd - LODTransitionStart; + if (Depth > LODTransitionStart && Range > 0.0) + { + float Alpha = saturate((Depth - LODTransitionStart) / Range); + Out.Visibility = lerp(Out.Visibility, Out.TransmissionDistance, Alpha); + } + Out.TransmissionDistance = (LocalSamplesPerPixel > 0) ? Out.TransmissionDistance / LocalSamplesPerPixel : Out.TransmissionDistance; + } + else // if ((ShadingModelID == SHADINGMODELID_EYE) || (ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE)) { Out.TransmissionDistance = (LocalSamplesPerPixel > 0) ? Out.Visibility / LocalSamplesPerPixel : Out.Visibility; } @@ -472,6 +551,11 @@ RAY_TRACING_ENTRY_RAYGEN(OcclusionRGS) #if ENABLE_TRANSMISSION TransmissionProfileParams = GetTransmissionProfileParams(GBufferData); #endif + + if (ShadingModelID == SHADINGMODELID_SUBSURFACE) + { + TransmissionProfileParams.ExtinctionScale = GBufferData.CustomData.a; + } } // Mask out depth values that are infinitely far away @@ -574,7 +658,15 @@ RAY_TRACING_ENTRY_RAYGEN(OcclusionRGS) float FadedSSSShadow = lerp(1.0f, Square(SSSTransmission), ShadowFadeFraction); // the channel assignment is documented in ShadowRendering.cpp (look for Light Attenuation channel assignment) - float4 OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, FadedShadow, FadedSSSShadow)); + float4 OutColor; + if (LIGHT_TYPE == LIGHT_TYPE_DIRECTIONAL) + { + OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, 1.0, FadedSSSShadow)); + } + else + { + OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, FadedShadow, FadedSSSShadow)); + } RWOcclusionMaskUAV[PixelCoord] = OutColor; } diff --git a/Engine/Shaders/Private/RayTracing/RayTracingRectLight.ush b/Engine/Shaders/Private/RayTracing/RayTracingRectLight.ush index cae13f72684b..881971189525 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingRectLight.ush +++ b/Engine/Shaders/Private/RayTracing/RayTracingRectLight.ush @@ -37,10 +37,7 @@ bool GenerateRectLightOcclusionRay( float3 LightDirection = normalize(LightSamplePosition - WorldPosition); // Light-normal culling - if (dot(-LightDirection, -LightParameters.Direction) <= 0.0) - { - return false; - } + bool IsNormalCulled = dot(-LightDirection, -LightParameters.Direction) <= 0.0; // Apply normal perturbation when defining ray RayDirection = LightDirection; @@ -56,5 +53,5 @@ bool GenerateRectLightOcclusionRay( FSphericalRect SphericalRect = BuildSphericalRect(Rect); RayPdf = 1.0 / SphericalRect.SolidAngle; - return true; + return IsNormalCulled; } \ No newline at end of file diff --git a/Engine/Shaders/Private/RayTracing/RayTracingRectLightRGS.usf b/Engine/Shaders/Private/RayTracing/RayTracingRectLightRGS.usf index fbb2ecdc64fa..27ed51bb5ab3 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingRectLightRGS.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingRectLightRGS.usf @@ -274,6 +274,7 @@ RAY_TRACING_ENTRY_RAYGEN(RectLightRGS) TLAS, RayFlags, InstanceInclusionMask, + PixelCoord, Ray); if (MinimalPayload.IsHit()) diff --git a/Engine/Shaders/Private/RayTracing/RayTracingReflections.usf b/Engine/Shaders/Private/RayTracing/RayTracingReflections.usf index 87b37d6a3a98..e520ee724eb5 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingReflections.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingReflections.usf @@ -187,7 +187,7 @@ void FixSampleDirectionIfNeeded(float3 SmoothSurfaceNormal, inout float3 SampleD } // Volume rendering ala emission-absorption model along an arbitrary ray -float Transmit(in RayDesc TransmissionRay, inout RandomSequence RandSequence, inout float3 OutRadiance) +float Transmit(in RayDesc TransmissionRay, in uint2 PixelCoord, inout RandomSequence RandSequence, inout float3 OutRadiance) { float Transmission = 1.0; @@ -203,6 +203,7 @@ float Transmit(in RayDesc TransmissionRay, inout RandomSequence RandSequence, in InstanceInclusionMask, TransmissionRay, RayCone, + PixelCoord, bEnableSkyLightContribution, bIgnoreTranslucentMaterials); @@ -232,6 +233,7 @@ float Transmit(in RayDesc TransmissionRay, inout RandomSequence RandSequence, in RAY_TRACING_MASK_TRANSLUCENT, TransmissionRay, RayCone, + PixelCoord, bEnableSkyLightContribution, bIgnoreTranslucentMaterials); @@ -690,6 +692,7 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingReflectionsRGS) InstanceInclusionMask, TopLayerRay, RayCone, + PixelCoord, bEnableSkyLightContribution); // Trace separate ray for translucent objects @@ -700,7 +703,7 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingReflectionsRGS) RayDesc TransmissionRay = TopLayerRay; TransmissionRay.TMin = 0.01; TransmissionRay.TMax = PackedPayload.HitT; - Transmission = Transmit(TransmissionRay, RandSequence, TopLayerRadiance); + Transmission = Transmit(TransmissionRay, PixelCoord, RandSequence, TopLayerRadiance); } float3 RayHitWorldPos = TopLayerRay.Origin + PackedPayload.HitT * TopLayerRay.Direction; @@ -1057,7 +1060,7 @@ RAY_TRACING_ENTRY_RAYGEN(RayTracingReflectionsRGS) TransmissionRay.TMax = length(WorldPosition - View.WorldCameraOrigin); float3 Inscattering = 0.0; RandomSequence RandSequence; - float Transmission = Transmit(TransmissionRay, RandSequence, Inscattering); + float Transmission = Transmit(TransmissionRay, PixelCoord, RandSequence, Inscattering); //#dxr_todo: add Inscattering support (this is emission only so far) //#dxr_todo: depending on if there is a translucency pass the component might already be attenuated. Check for double contribution ReflectedColor.rgb *= Transmission; diff --git a/Engine/Shaders/Private/RayTracing/RayTracingReflectionsGenerateRaysCS.usf b/Engine/Shaders/Private/RayTracing/RayTracingReflectionsGenerateRaysCS.usf index db127e01400b..929d4debae6c 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingReflectionsGenerateRaysCS.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingReflectionsGenerateRaysCS.usf @@ -43,6 +43,7 @@ groupshared uint Bins[NUM_DIRECTION_BINS]; uint2 RayTracingResolution; uint2 TileAlignedResolution; float ReflectionMaxNormalBias; +float ReflectionMaxRoughness; int GlossyReflections; // Outputs @@ -76,7 +77,7 @@ void GenerateReflectionRaysCS( const uint2 TileBasePixelPos = uint2(TileIndex % NumTiles.x, TileIndex / NumTiles.x) * TileSize; const uint2 PixelPos = TileBasePixelPos + uint2(RayIndexInTile % TileSize.x, RayIndexInTile / TileSize.x); - FSortedReflectionRay Ray = GenerateDeferredReflectionRay(PixelPos, ReflectionMaxNormalBias, GlossyReflections==1); + FSortedReflectionRay Ray = GenerateDeferredReflectionRay(PixelPos, ReflectionMaxNormalBias, ReflectionMaxRoughness, GlossyReflections==1); uint Bin = RayDirectionToBin(Ray.Direction, uint2(NUM_DIRECTION_BINS_X,NUM_DIRECTION_BINS_Y)); if (any(PixelPos >= RayTracingResolution)) @@ -129,8 +130,6 @@ void GenerateReflectionRaysCS( GroupMemoryBarrierWithGroupSync(); - Ray.DebugSortKey = Bin; - uint StoreIndex = GroupId * (TileSize.x*TileSize.y) + Bins[Bin] + SlotForThisThread; RayBuffer[StoreIndex] = Ray; diff --git a/Engine/Shaders/Private/RayTracing/RayTracingSkyLightEvaluation.ush b/Engine/Shaders/Private/RayTracing/RayTracingSkyLightEvaluation.ush index bb187f2a2567..924a1d616844 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingSkyLightEvaluation.ush +++ b/Engine/Shaders/Private/RayTracing/RayTracingSkyLightEvaluation.ush @@ -40,6 +40,7 @@ float3 EvaluateSkyLightRadiance(float3 Direction) // bDecoupleSampleGeneration - Indicates weather or not to use decoupled sample generation for generating visibility rays, requires that the visibility ray buffer has been populated by the GenerateSkyLightVisibilityRaysCS pass. void SkyLightEvaluate( in const uint2 SampleCoord, + in const uint2 PixelCoord, in const uint SamplesPerPixel, in const float3 WorldPosition, in const float3 WorldNormal, @@ -148,7 +149,7 @@ void SkyLightEvaluate( { if (bGBufferSampleOrigin) { - ApplyCameraRelativeDepthBias(Ray, SampleCoord, DeviceZ, CurrentWorldNormal, SkyLight.MaxNormalBias); + ApplyCameraRelativeDepthBias(Ray, PixelCoord, DeviceZ, CurrentWorldNormal, SkyLight.MaxNormalBias); } else { @@ -176,12 +177,13 @@ void SkyLightEvaluate( TLAS, RayFlags, InstanceInclusionMask, + PixelCoord, Ray); #if USE_HAIR_LIGHTING if (GBufferData.ShadingModelID != SHADINGMODELID_HAIR) { - MinimalPayload.HitT = TraverseHair(SampleCoord, Ray.Origin, Ray.Direction, MinimalPayload.HitT); + MinimalPayload.HitT = TraverseHair(PixelCoord, Ray.Origin, Ray.Direction, MinimalPayload.HitT); } #endif @@ -283,6 +285,7 @@ void SkyLightEvaluate( TLAS, RayFlagsLocal, InstanceInclusionMaskLocal, + PixelCoord, RayLocal); float3 P = RayLocal.Origin + RayLocal.Direction * MinimalPayloadLocal.HitT; @@ -294,3 +297,39 @@ void SkyLightEvaluate( DiffuseExitantRadiance += SkySHDiffuseIrradiance * ScatterThroughput * (1.0 - AmbientOcclusion); } } + + +// special case of pixel coord == sample coord +void SkyLightEvaluate( + in const uint2 SampleCoord, + in const uint SamplesPerPixel, + in const float3 WorldPosition, + in const float3 WorldNormal, + in const float3 ViewDirection, + in const FGBufferData GBufferData, + in const RaytracingAccelerationStructure TLAS, + in const bool bGBufferSampleOrigin, + in const float DeviceZ, + in const bool bDecoupleSampleGeneration, + inout float3 ExitantRadiance, + inout float3 DiffuseExitantRadiance, + inout float AmbientOcclusion, + inout float HitDistance) +{ + SkyLightEvaluate( + SampleCoord, + SampleCoord, + SamplesPerPixel, + WorldPosition, + WorldNormal, + ViewDirection, + GBufferData, + TLAS, + bGBufferSampleOrigin, + DeviceZ, + bDecoupleSampleGeneration, + ExitantRadiance, + DiffuseExitantRadiance, + AmbientOcclusion, + HitDistance); +} \ No newline at end of file diff --git a/Engine/Shaders/Private/RayTracing/RayTracingSkyLightRGS.usf b/Engine/Shaders/Private/RayTracing/RayTracingSkyLightRGS.usf index 34f5967d8bb0..3f32319f95c8 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingSkyLightRGS.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingSkyLightRGS.usf @@ -13,6 +13,8 @@ #include "RayTracingSkyLightEvaluation.ush" #include "RayTracingDeferredShadingCommon.ush" +uint UpscaleFactor; + RaytracingAccelerationStructure TLAS; RWTexture2D RWOcclusionMaskUAV; @@ -20,8 +22,8 @@ RWTexture2D RWRayDistanceUAV; RAY_TRACING_ENTRY_RAYGEN(SkyLightRGS) { - uint2 PixelCoord = DispatchRaysIndex().xy + View.ViewRectMin.xy; - uint LinearIndex = CalcLinearIndex(PixelCoord); + uint2 DispatchThreadId = DispatchRaysIndex().xy + View.ViewRectMin.xy; + uint2 PixelCoord = GetPixelCoord(DispatchThreadId, UpscaleFactor); // Get G-Buffer surface data float2 InvBufferSize = View.BufferSizeAndInvSize.zw; @@ -69,6 +71,7 @@ RAY_TRACING_ENTRY_RAYGEN(SkyLightRGS) float HitDistance; SkyLightEvaluate( + DispatchThreadId, PixelCoord, SamplesPerPixel, WorldPosition, @@ -93,6 +96,6 @@ RAY_TRACING_ENTRY_RAYGEN(SkyLightRGS) DiffuseExitantRadiance.rgb *= View.PreExposure; #endif - RWOcclusionMaskUAV[PixelCoord] = float4(ClampToHalfFloatRange(DiffuseExitantRadiance.rgb), AmbientOcclusion); - RWRayDistanceUAV[PixelCoord] = float2(HitDistance, SamplesPerPixel); + RWOcclusionMaskUAV[DispatchThreadId] = float4(ClampToHalfFloatRange(DiffuseExitantRadiance.rgb), AmbientOcclusion); + RWRayDistanceUAV[DispatchThreadId] = float2(HitDistance, SamplesPerPixel); } \ No newline at end of file diff --git a/Engine/Shaders/Private/RayTracing/RayTracingTest.usf b/Engine/Shaders/Private/RayTracing/RayTracingTest.usf index e4c144bd3554..bcbfd5543a67 100644 --- a/Engine/Shaders/Private/RayTracing/RayTracingTest.usf +++ b/Engine/Shaders/Private/RayTracing/RayTracingTest.usf @@ -29,6 +29,7 @@ RAY_TRACING_ENTRY_RAYGEN(TestMainRGS) TLAS, RayFlags, InstanceInclusionMask, + uint2(RayIndex & 0xFFFF, RayIndex >> 16), Ray); Output[RayIndex] = MinimalPayload.IsHit() ? ~0 : 0; diff --git a/Engine/Shaders/Private/ReflectionEnvironmentPixelShader.usf b/Engine/Shaders/Private/ReflectionEnvironmentPixelShader.usf index e66c5f7d7d65..131503a9fe5f 100644 --- a/Engine/Shaders/Private/ReflectionEnvironmentPixelShader.usf +++ b/Engine/Shaders/Private/ReflectionEnvironmentPixelShader.usf @@ -92,7 +92,7 @@ void RemapClearCoatDiffuseAndSpecularColor(FGBufferData GBuffer, float2 ScreenPo //BaseColor += Dither / 255.f; DiffuseColor = BaseColor - BaseColor * GBuffer.Metallic; - float Specular = lerp(1, RefractionScale, ClearCoat); + float Specular = lerp(GBuffer.Specular, RefractionScale, ClearCoat); SpecularColor = ComputeF0(Specular, BaseColor, GBuffer.Metallic); } } diff --git a/Engine/Shaders/Private/ReflectionEnvironmentShaders.usf b/Engine/Shaders/Private/ReflectionEnvironmentShaders.usf index 8fc287d15fe5..b71261ad859d 100644 --- a/Engine/Shaders/Private/ReflectionEnvironmentShaders.usf +++ b/Engine/Shaders/Private/ReflectionEnvironmentShaders.usf @@ -175,7 +175,7 @@ uint NumMips; #ifdef USE_COMPUTE int FaceThreadGroupSize; int2 ValidDispatchCoord; -RWTexture2DArray OutTextureMipColor; // All slices, i.e. faces, of a cube map mip level +RWTextureCube OutTextureMipColor; // All slices, i.e. faces, of a cube map mip level #endif #ifdef USE_COMPUTE @@ -314,6 +314,7 @@ void DownsamplePS_Mobile( } #ifdef USE_COMPUTE +int CubeFaceOffset; [numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)] void FilterCS(uint3 ThreadId : SV_DispatchThreadID) { @@ -322,7 +323,7 @@ void FilterCS(uint3 ThreadId : SV_DispatchThreadID) { return; } - const int SelectedCubeFace = int(ThreadId.x) / FaceThreadGroupSize; + const int SelectedCubeFace = CubeFaceOffset + int(ThreadId.x) / FaceThreadGroupSize; float2 ScaledUVs = ((float2(FaceCoord) + 0.5f) / float2(ValidDispatchCoord)) * 2.0f - 1.0f; float4 OutColor; #else // USE_COMPUTE @@ -538,7 +539,7 @@ void ComputeSkyEnvMapDiffuseIrradianceCS(uint3 ThreadId : SV_DispatchThreadID) #if 1 // For a 128x128 cubemap, sampling mip level 2 with only 8x8 samples matches closely the super sampled version. - const float3 SampleDirection = UniformSampleSphere(float2(ThreadId.xy) / float2(THREADGROUP_SIZE_X, THREADGROUP_SIZE_Y)).xyz; + const float3 SampleDirection = UniformSampleSphere((float2(ThreadId.xy)+0.5f) / float2(THREADGROUP_SIZE_X, THREADGROUP_SIZE_Y)).xyz; IrradianceSHShared[LinearIndex] = SampleSHRGB(SampleDirection, UniformSampleSolidAngle, MipIndex); #else @@ -659,6 +660,10 @@ void ComputeSkyEnvMapDiffuseIrradianceCS(uint3 ThreadId : SV_DispatchThreadID) #include "HeightFogCommon.ush" +#if PERMUTATION_DEPTHTEXTURE +Texture2D DepthTexture; +#endif + void RenderRealTimeReflectionHeightFogVS( in uint VertexId : SV_VertexID, out float4 Position : SV_POSITION, @@ -682,8 +687,13 @@ void RenderRealTimeReflectionHeightFogPS( float4 WorldVector = mul(float4((SVPos.xy / 128.0f) * float2(2.0, -2.0) + float2(1.0, -1.0), 0.0, 0), ResolvedView.ScreenToTranslatedWorld); - const float HeightFogFarDistance = 100000.0f * 10.0f; // 10 kilometers - float4 HeightFogInscatteringAndOpacity = CalculateHeightFog(normalize(ScreenVector)* HeightFogFarDistance); +#if PERMUTATION_DEPTHTEXTURE + float DeviceZ = DepthTexture.Load(int3(SVPos.xy, 0)); + const float HeightFogDistance = ConvertFromDeviceZ(DeviceZ); +#else + const float HeightFogDistance = 100000.0f * 10.0f; // 10 kilometers +#endif + float4 HeightFogInscatteringAndOpacity = CalculateHeightFog(normalize(ScreenVector)* HeightFogDistance); const float OutputPreExposure = (ResolvedView.RealTimeReflectionCapture ? ResolvedView.RealTimeReflectionCapturePreExposure : ResolvedView.PreExposure); HeightFogInscatteringAndOpacity.rgb *= OutputPreExposure; diff --git a/Engine/Shaders/Private/ReflectionEnvironmentShared.ush b/Engine/Shaders/Private/ReflectionEnvironmentShared.ush index 459f14d895c9..8d267f7a352b 100644 --- a/Engine/Shaders/Private/ReflectionEnvironmentShared.ush +++ b/Engine/Shaders/Private/ReflectionEnvironmentShared.ush @@ -6,6 +6,12 @@ #pragma once +#if (FEATURE_LEVEL <= FEATURE_LEVEL_ES3_1) +#define SkyIrradianceEnvironmentMap View.MobileSkyIrradianceEnvironmentMap +#else +#define SkyIrradianceEnvironmentMap View.SkyIrradianceEnvironmentMap +#endif + #define REFLECTION_CAPTURE_ROUGHEST_MIP 1 #define REFLECTION_CAPTURE_ROUGHNESS_MIP_SCALE 1.2 @@ -75,17 +81,17 @@ float3 GetSkySHDiffuse(float3 Normal) float4 NormalVector = float4(Normal, 1.0f); float3 Intermediate0, Intermediate1, Intermediate2; - Intermediate0.x = dot(View.SkyIrradianceEnvironmentMap[0], NormalVector); - Intermediate0.y = dot(View.SkyIrradianceEnvironmentMap[1], NormalVector); - Intermediate0.z = dot(View.SkyIrradianceEnvironmentMap[2], NormalVector); + Intermediate0.x = dot(SkyIrradianceEnvironmentMap[0], NormalVector); + Intermediate0.y = dot(SkyIrradianceEnvironmentMap[1], NormalVector); + Intermediate0.z = dot(SkyIrradianceEnvironmentMap[2], NormalVector); float4 vB = NormalVector.xyzz * NormalVector.yzzx; - Intermediate1.x = dot(View.SkyIrradianceEnvironmentMap[3], vB); - Intermediate1.y = dot(View.SkyIrradianceEnvironmentMap[4], vB); - Intermediate1.z = dot(View.SkyIrradianceEnvironmentMap[5], vB); + Intermediate1.x = dot(SkyIrradianceEnvironmentMap[3], vB); + Intermediate1.y = dot(SkyIrradianceEnvironmentMap[4], vB); + Intermediate1.z = dot(SkyIrradianceEnvironmentMap[5], vB); float vC = NormalVector.x * NormalVector.x - NormalVector.y * NormalVector.y; - Intermediate2 = View.SkyIrradianceEnvironmentMap[6].xyz * vC; + Intermediate2 = SkyIrradianceEnvironmentMap[6].xyz * vC; // max to not get negative colors return max(0, Intermediate0 + Intermediate1 + Intermediate2); @@ -101,9 +107,9 @@ float3 GetSkySHDiffuseSimple(float3 Normal) float4 NormalVector = float4(Normal, 1); float3 Intermediate0; - Intermediate0.x = dot(View.SkyIrradianceEnvironmentMap[0], NormalVector); - Intermediate0.y = dot(View.SkyIrradianceEnvironmentMap[1], NormalVector); - Intermediate0.z = dot(View.SkyIrradianceEnvironmentMap[2], NormalVector); + Intermediate0.x = dot(SkyIrradianceEnvironmentMap[0], NormalVector); + Intermediate0.y = dot(SkyIrradianceEnvironmentMap[1], NormalVector); + Intermediate0.z = dot(SkyIrradianceEnvironmentMap[2], NormalVector); // max to not get negative colors return max(0, Intermediate0); diff --git a/Engine/Shaders/Private/ScreenSpaceDenoise/SSDDefinitions.ush b/Engine/Shaders/Private/ScreenSpaceDenoise/SSDDefinitions.ush index ea8a19e2ac43..8a076db70b2f 100644 --- a/Engine/Shaders/Private/ScreenSpaceDenoise/SSDDefinitions.ush +++ b/Engine/Shaders/Private/ScreenSpaceDenoise/SSDDefinitions.ush @@ -56,7 +56,7 @@ // World bluring radius for a miss. #define WORLD_RADIUS_INVALID -1 -#define WORLD_RADIUS_MISS (INFINITE_FLOAT) +#define WORLD_RADIUS_MISS (100000) // World bluring radius for a miss. #define CONFUSION_FACTOR_INVALID -1 diff --git a/Engine/Shaders/Private/ScreenSpaceDenoise/SSDInjest.usf b/Engine/Shaders/Private/ScreenSpaceDenoise/SSDInjest.usf index dd889cb51f07..ca27e9211375 100644 --- a/Engine/Shaders/Private/ScreenSpaceDenoise/SSDInjest.usf +++ b/Engine/Shaders/Private/ScreenSpaceDenoise/SSDInjest.usf @@ -146,6 +146,11 @@ void MainCS( // No need to keep DispatchThreadId, while SceneBufferUV is arround at highest VGPR peak because center of the unique pixel to sample. uint2 OutputPixelPostion = BufferUVToBufferPixelCoord(SceneBufferUV); + #if DEBUG_OUTPUT + //DebugOutput[OutputPixelPostion] = float4(MultiplexedSamples.Array[0].SampleCount, 0, 0, 0); + DebugOutput[OutputPixelPostion] = float4(MultiplexedFrequencies.Array[0].WorldBluringRadius, 0, 0, 0); + #endif + BRANCH if (all(OutputPixelPostion < ViewportMax)) { diff --git a/Engine/Shaders/Private/ScreenSpaceDenoise/SSDSignalBufferEncoding.ush b/Engine/Shaders/Private/ScreenSpaceDenoise/SSDSignalBufferEncoding.ush index c8dd4cab28da..cdeed71556b7 100644 --- a/Engine/Shaders/Private/ScreenSpaceDenoise/SSDSignalBufferEncoding.ush +++ b/Engine/Shaders/Private/ScreenSpaceDenoise/SSDSignalBufferEncoding.ush @@ -118,7 +118,7 @@ void DecodeMultiplexedSignalsFromFloat4( { OutSampleFrequencies.Array[MultiplexId].ClosestHitDistance = DENOISER_INVALID_HIT_DISTANCE; } - else if (OutSamples.Array[MultiplexId].MissCount != 0) + else if (OutSamples.Array[MultiplexId].MissCount > 0.999) { OutSampleFrequencies.Array[MultiplexId].ClosestHitDistance = DENOISER_MISS_HIT_DISTANCE; } diff --git a/Engine/Shaders/Private/ScreenSpaceDenoise/SSDSpatialAccumulation.usf b/Engine/Shaders/Private/ScreenSpaceDenoise/SSDSpatialAccumulation.usf index eb918823161a..be1b42631da3 100644 --- a/Engine/Shaders/Private/ScreenSpaceDenoise/SSDSpatialAccumulation.usf +++ b/Engine/Shaders/Private/ScreenSpaceDenoise/SSDSpatialAccumulation.usf @@ -1241,7 +1241,7 @@ void MainCS( UNROLL_N(CONFIG_SIGNAL_BATCH_SIZE) for (uint MultiplexId = 0; MultiplexId < CONFIG_SIGNAL_BATCH_SIZE; MultiplexId++) { - float CurrentSampleCount = OutputSamples.Array[MultiplexId].SampleCount; + float CurrentSampleCount = RefSamples.Array[MultiplexId].SampleCount; float NewSampleCount = min(CurrentSampleCount, TARGETED_SAMPLE_COUNT); OutputSamples.Array[MultiplexId] = MulSignal(OutputSamples.Array[MultiplexId], CurrentSampleCount > 0 ? NewSampleCount / CurrentSampleCount : 0); @@ -1279,7 +1279,7 @@ void MainCS( OutputPixelPostion = ViewportMin + DispatchThreadId; #endif - #if 0 && CONFIG_SIGNAL_PROCESSING == SIGNAL_PROCESSING_SHADOW_VISIBILITY_MASK && DIM_STAGE == STAGE_RECONSTRUCTION + #if DEBUG_OUTPUT && CONFIG_SIGNAL_PROCESSING == SIGNAL_PROCESSING_SHADOW_VISIBILITY_MASK && DIM_STAGE == STAGE_RECONSTRUCTION DebugOutput[DispatchThreadId] = float4(OutputSamples.Array[0].SampleCount, 0, 0, 0); #endif @@ -1303,7 +1303,15 @@ void MainCS( float FadedSSSShadow = lerp(1.0f, Square(SSSTransmission), ShadowFadeFraction); // the channel assignment is documented in ShadowRendering.cpp (look for Light Attenuation channel assignment) - float4 OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, FadedShadow, FadedSSSShadow)); + float4 OutColor; + if (LightType[MultiplexId] == LIGHT_TYPE_DIRECTIONAL) + { + OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, 1.0, FadedSSSShadow)); + } + else + { + OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, FadedShadow, FadedSSSShadow)); + } if (MultiplexId == 0) SignalOutput_UAVs_0[OutputPixelPostion] = OutColor; diff --git a/Engine/Shaders/Private/ScreenSpaceDenoise/SSDSpatialKernel.ush b/Engine/Shaders/Private/ScreenSpaceDenoise/SSDSpatialKernel.ush index 74823746c0e4..31b60c9e1d7a 100644 --- a/Engine/Shaders/Private/ScreenSpaceDenoise/SSDSpatialKernel.ush +++ b/Engine/Shaders/Private/ScreenSpaceDenoise/SSDSpatialKernel.ush @@ -107,6 +107,9 @@ struct FSSDKernelConfig // Compile time whether sampling previous frame or current frame metadata. bool bPreviousFrameMetadata; + // Compile time whether reference metadata is current frame or previous frame. + bool bPreviousFrameRefMetadata; + // The sample should be accumulated starting from the further away. bool bDescOrder; @@ -218,6 +221,7 @@ FSSDKernelConfig CreateKernelConfig() KernelConfig.bUnroll = false; KernelConfig.bSampleKernelCenter = false; KernelConfig.bPreviousFrameMetadata = false; + KernelConfig.bPreviousFrameRefMetadata = false; KernelConfig.BilateralDistanceComputation = SIGNAL_WORLD_FREQUENCY_MIN_METADATA; KernelConfig.bDescOrder = false; KernelConfig.bNormalizeSample = false; @@ -426,7 +430,7 @@ FSSDSampleSceneInfos UncompressRefSceneMetadata(FSSDKernelConfig KernelConfig) float2 RefBufferUV = ComputeRefBufferUV(KernelConfig); float2 ScreenPos; - if (KernelConfig.bPreviousFrameMetadata) + if (KernelConfig.bPreviousFrameMetadata) // TODO(Denoiser): should be bPreviousFrameRefMetadata instead? { ScreenPos = RefBufferUV * PrevSceneBufferUVToScreenPosition.xy + PrevSceneBufferUVToScreenPosition.zw; } @@ -437,7 +441,7 @@ FSSDSampleSceneInfos UncompressRefSceneMetadata(FSSDKernelConfig KernelConfig) // Uncompress the reference scene metadata to keep a low VGPR pressure. return UncompressSampleSceneInfo( - KernelConfig.RefSceneMetadataLayout, /* bIsPrevFrame = */ false, + KernelConfig.RefSceneMetadataLayout, KernelConfig.bPreviousFrameRefMetadata, ScreenPos, KernelConfig.CompressedRefSceneMetadata); } diff --git a/Engine/Shaders/Private/ScreenSpaceDenoise/SSDTemporalAccumulation.usf b/Engine/Shaders/Private/ScreenSpaceDenoise/SSDTemporalAccumulation.usf index 2a81b2b2f048..0f2a3aed82bf 100644 --- a/Engine/Shaders/Private/ScreenSpaceDenoise/SSDTemporalAccumulation.usf +++ b/Engine/Shaders/Private/ScreenSpaceDenoise/SSDTemporalAccumulation.usf @@ -44,6 +44,8 @@ #define CONFIG_HISTORY_REJECTION HISTORY_REJECTION_VAR_BOUNDARIES + #define CONFIG_REJECTION_SAMPLE_SET SAMPLE_SET_3X3_PLUS + // Uses nearest to not leak informations on geometric edges by bilateral. This is OK because just blury greyscale details. // TODO(Denoiser): a bit hacky, need find better solution. // TODO(Denoiser): cause a regression on spot light for some reasons. @@ -111,6 +113,7 @@ #define CONFIG_BILATERAL_PRESET BILATERAL_PRESET_DIFFUSE #define CONFIG_HISTORY_BILATERAL_PRESET BILATERAL_PRESET_DIFFUSE + #define CONFIG_PATCH_PREV_SCENE_DEPTH 1 // Use pre transformed rejection buffer that contains pre transformed momment 1 & 2. #if 1 @@ -632,8 +635,8 @@ void TemporallyAccumulate( #if CONFIG_PATCH_PREV_SCENE_DEPTH { KernelConfig.RefBufferUV = HistoryBufferUV; - KernelConfig.RefSceneMetadataLayout = METADATA_BUFFER_LAYOUT_WORLD_POS_SHADINGMODEL; - //KernelConfig.RefSceneMetadataLayout = CONFIG_METADATA_BUFFER_LAYOUT; + KernelConfig.RefSceneMetadataLayout = CONFIG_METADATA_BUFFER_LAYOUT; + KernelConfig.bPreviousFrameRefMetadata = true; FSSDSampleSceneInfos PrevRefInfo = UncompressSampleSceneInfo( CONFIG_METADATA_BUFFER_LAYOUT, /* bIsPrevFrame = */ false, diff --git a/Engine/Shaders/Private/ShadingModels.ush b/Engine/Shaders/Private/ShadingModels.ush index 27780ecb2106..8774817fffe4 100644 --- a/Engine/Shaders/Private/ShadingModels.ush +++ b/Engine/Shaders/Private/ShadingModels.ush @@ -407,6 +407,8 @@ FDirectLighting ClearCoatBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, Context.NoH = saturate(dot(N, H)); Context.NoV = saturate(dot(N, V)); Context.NoL = saturate(dot(N, L)); + Context.VoL = saturate(dot(V, L)); + Context.VoH = saturate(dot(V, H)); bool bNewtonIteration = true; SphereMaxNoH(Context, AreaLight.SphereSinAlpha, bNewtonIteration); @@ -442,6 +444,7 @@ FDirectLighting ClearCoatBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, else { float a2 = Pow4(GBuffer.Roughness); + float Energy = EnergyNormalization(a2, Context.VoH, AreaLight); float D2 = 0; float Vis2 = 0; @@ -458,18 +461,18 @@ FDirectLighting ClearCoatBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, else #endif { - D2 = D_GGX(a2, Context.NoH); - Vis2 = Vis_SmithJointApprox(a2, BottomContext.NoV, BottomContext.NoL); + D2 = D_GGX(a2, BottomContext.NoH); + // NoL is chosen to provide better parity with DefaultLit when ClearCoat=0 + Vis2 = Vis_SmithJointApprox(a2, BottomContext.NoV, NoL); } // When refracting into a non-metallic substrate, the IOR ratio (Eta) between top and bottom interfaces approaches 1.0 and drives Fresnel reflectance to 0 float3 F = F_Schlick(GBuffer.SpecularColor, BottomContext.VoH); float3 RefractedF = lerp(0.0, F, GBuffer.Metallic); // Note: reusing D, V, and F from refracted context to save computation for when ClearCoat < 1 - float Energy = EnergyNormalization(a2, Context.VoH, AreaLight); - float3 CommonSpecular = (Energy * Falloff * NoL * D2 * Vis2) * AreaLight.FalloffColor; - float3 DefaultSpecular = F; - float3 RefractedSpecular = FresnelCoeff * Transmission * RefractedF; + float3 CommonSpecular = (Energy * Falloff * D2 * Vis2) * AreaLight.FalloffColor; + float3 DefaultSpecular = F * NoL; + float3 RefractedSpecular = FresnelCoeff * Transmission * RefractedF * BottomContext.NoL; Lighting.Specular += CommonSpecular * lerp(DefaultSpecular, RefractedSpecular, ClearCoat); } diff --git a/Engine/Shaders/Private/ShadowProjectionPixelShader.usf b/Engine/Shaders/Private/ShadowProjectionPixelShader.usf index d9151d5ce7aa..3aab49b2876b 100644 --- a/Engine/Shaders/Private/ShadowProjectionPixelShader.usf +++ b/Engine/Shaders/Private/ShadowProjectionPixelShader.usf @@ -264,8 +264,7 @@ void Main( if (bIsSubsurfaceCompatible && IsSubsurfaceModel(GBufferData.ShadingModelID)) { float Opacity = GBufferData.CustomData.a; - // Derive density from a heuristic using opacity, tweaked for useful falloff ranges and to give a linear depth falloff with opacity - float Density = -.05f * log(1 - min(Opacity, .999f)); + float Density = SubsurfaceDensityFromOpacity(Opacity); if( GBufferData.ShadingModelID == SHADINGMODELID_HAIR || GBufferData.ShadingModelID == SHADINGMODELID_EYE ) { Opacity = 1; diff --git a/Engine/Shaders/Private/TemporalAA/TAACommon.ush b/Engine/Shaders/Private/TemporalAA/TAACommon.ush index 16baa64ae7ef..805d1d976926 100644 --- a/Engine/Shaders/Private/TemporalAA/TAACommon.ush +++ b/Engine/Shaders/Private/TemporalAA/TAACommon.ush @@ -139,21 +139,6 @@ float2 RejectionInfo_UVViewportSizeInverse; float2 RejectionInfo_UVViewportBilinearMin; float2 RejectionInfo_UVViewportBilinearMax; -float2 OutputInfo_Extent; -float2 OutputInfo_ExtentInverse; -float2 OutputInfo_ScreenPosToViewportScale; -float2 OutputInfo_ScreenPosToViewportBias; -uint2 OutputInfo_ViewportMin; -uint2 OutputInfo_ViewportMax; -float2 OutputInfo_ViewportSize; -float2 OutputInfo_ViewportSizeInverse; -float2 OutputInfo_UVViewportMin; -float2 OutputInfo_UVViewportMax; -float2 OutputInfo_UVViewportSize; -float2 OutputInfo_UVViewportSizeInverse; -float2 OutputInfo_UVViewportBilinearMin; -float2 OutputInfo_UVViewportBilinearMax; - float2 HistoryInfo_Extent; float2 HistoryInfo_ExtentInverse; float2 HistoryInfo_ScreenPosToViewportScale; diff --git a/Engine/Shaders/Private/TemporalAA/TAAUpdateHistory.usf b/Engine/Shaders/Private/TemporalAA/TAAUpdateHistory.usf index 2480e4e8eac8..9bb67353e7da 100644 --- a/Engine/Shaders/Private/TemporalAA/TAAUpdateHistory.usf +++ b/Engine/Shaders/Private/TemporalAA/TAAUpdateHistory.usf @@ -127,7 +127,7 @@ void MainCS( #endif float2 ScreenVelocity = ScreenPos - PrevScreenPos; - taa_half OutputPixelVelocity = taa_half(length(ScreenVelocity * OutputInfo_ViewportSize)); + taa_half OutputPixelVelocity = taa_half(length(ScreenVelocity * HistoryInfo_ViewportSize)); // Fetch wehther the pixel is responsive AA or not. bool bIsResponsiveAAPixel = false; @@ -153,7 +153,6 @@ void MainCS( ISOLATE { taa_half InputToHistoryFactor = taa_half(HistoryInfo_ViewportSize.x * InputInfo_ViewportSizeInverse.x); - taa_half InputToOutputFactor = taa_half(OutputInfo_ViewportSize.x * InputInfo_ViewportSizeInverse.x); // Vector in pixel between pixel K -> O. taa_half2 dKO = taa_half2(PPCo - PPCk); @@ -370,7 +369,7 @@ void MainCS( PrevHistoryRejectionWeight = taa_half(0.0); } - taa_half DesiredCurrentContribution = max(Histeresis * InputPixelAlignement, taa_half(0.0)); // * Pow2(InputToOutputFactor); + taa_half DesiredCurrentContribution = max(Histeresis * InputPixelAlignement, taa_half(0.0)); // Determine whether the the prediction based rejection was confident enough. taa_half RejectionConfidentEnough = taa_half(1); // saturate(RejectionValidity * MAX_SAMPLE_COUNT - 3.0); @@ -667,50 +666,14 @@ void MainCS( // Output final scene color { - BRANCH - if (HistoryInfo_ViewportSize.x == OutputInfo_ViewportSize.x) - { - taa_half3 OutputColor = FinalOutputColor; + taa_half3 OutputColor = FinalOutputColor; - OutputColor = -min(-OutputColor, taa_half(0.0)); - OutputColor = min(OutputColor, taa_half(Max10BitsFloat)); + OutputColor = -min(-OutputColor, taa_half(0.0)); + OutputColor = min(OutputColor, taa_half(Max10BitsFloat)); - if (bIsValidhistoryPixel) - { - SceneColorOutput[LocalHistoryPixelPos] = OutputColor; - } - } - else + if (bIsValidhistoryPixel) { - taa_half HdrWeight = HdrWeight4(FinalOutputColor); - - SharedArray0[LocalGroupThreadIndex] = taa_half4(FinalOutputColor, 1.0) * (HdrWeight * FinalOutputValidity); - - GroupMemoryBarrierWithGroupSync(); - - taa_half3 OutputColor = FinalOutputColor * (FinalOutputValidity * HdrWeight); - taa_half OutputWeight = FinalOutputValidity * HdrWeight; - - UNROLL - for (uint i = 1; i < 4; i++) - { - uint ButterflySwap = i; - - taa_half4 RawLDS = SharedArray0[LocalGroupThreadIndex ^ ButterflySwap]; - - OutputColor += RawLDS.rgb; - OutputWeight += RawLDS.a; - } - - OutputColor *= SafeRcp(OutputWeight); - - OutputColor = -min(-OutputColor, taa_half(0.0)); - OutputColor = min(OutputColor, taa_half(Max10BitsFloat)); - - if (all(LocalHistoryPixelPos % 2 == 0) && bIsValidhistoryPixel) - { - SceneColorOutput[LocalHistoryPixelPos / 2] = OutputColor; - } + SceneColorOutput[LocalHistoryPixelPos] = OutputColor; } } } diff --git a/Engine/Shaders/Private/VolumetricCloud.usf b/Engine/Shaders/Private/VolumetricCloud.usf index 7b14431398c6..c9eb04a6d43b 100644 --- a/Engine/Shaders/Private/VolumetricCloud.usf +++ b/Engine/Shaders/Private/VolumetricCloud.usf @@ -40,6 +40,11 @@ float3 SampleAlbedo(in FPixelMaterialInputs PixelMaterialInputs) return saturate(GetMaterialBaseColor(PixelMaterialInputs)); } +float3 SampleAmbientOcclusion(in FPixelMaterialInputs PixelMaterialInputs) +{ + return GetMaterialAmbientOcclusion(PixelMaterialInputs); +} + #endif #endif @@ -58,7 +63,7 @@ bool ViewHierarchicalZBufferMinDepth(float4 ScreenUVMinMax, const float MaxZDept float4 Rect = saturate(ScreenUVMinMax); float4 RectPixels = Rect * RenderVolumetricCloudParameters.HZBSize.xyxy; - float2 RectSize = (RectPixels.zw - RectPixels.xy) * 0.5; // 0.5 for 4x4 + float2 RectSize = (RectPixels.zw - RectPixels.xy); float Level = max(ceil(log2(max(RectSize.x, RectSize.y))), RenderVolumetricCloudParameters.HZBUvFactor.z); // Check if we can drop one level lower @@ -208,7 +213,7 @@ void UpdateMaterialCloudParam(inout FMaterialPixelParameters MaterialParameters, MaterialParameters.CloudSampleNormAltitudeInLayer = saturate(MaterialParameters.CloudSampleAltitudeInLayer * CloudLayerParams.ToNormAltitude); const float DefaultConservativeDensity = 1.0f; - MaterialParameters.VolumeSampleConservativeDensity = DefaultConservativeDensity; // This should not be used when evaluating density. But it still is initialised. + MaterialParameters.VolumeSampleConservativeDensity = DefaultConservativeDensity; // Defaults to "medium is potentially present" in case it is not fed by the user. #if MATERIAL_VOLUMETRIC_ADVANCED_CONSERVATIVE_DENSITY MaterialParameters.VolumeSampleConservativeDensity = GetVolumetricAdvancedMaterialOutput6(MaterialParameters); // Evaluate conservative density #endif @@ -248,6 +253,9 @@ void UpdateMaterialCloudParam(inout FMaterialPixelParameters MaterialParameters, #ifndef MATERIAL_VOLUMETRIC_ADVANCED_GRAYSCALE_MATERIAL #define MATERIAL_VOLUMETRIC_ADVANCED_GRAYSCALE_MATERIAL 0 #endif +#ifndef MATERIAL_VOLUMETRIC_ADVANCED_OVERRIDE_AMBIENT_OCCLUSION +#define MATERIAL_VOLUMETRIC_ADVANCED_OVERRIDE_AMBIENT_OCCLUSION 0 +#endif #if CLOUD_SAMPLE_ATMOSPHERIC_LIGHT_SHADOWMAP @@ -469,8 +477,11 @@ bool TraceClouds( float3 DepthBufferWorldPos = SvPositionToWorld(float4(SvPosition.xy, DeviceZ, 1.0)); float TDepthBuffer = length(DepthBufferWorldPos - RayOrigin); - if (RenderVolumetricCloudParameters.IntersectWithOpaque > 0) + if (RenderVolumetricCloudParameters.OpaqueIntersectionMode >= 2) { + //float CloudSampleAltitudeKm = length(RayOriginKm - RenderVolumetricCloudParameters.CloudLayerCenterKm); + //const bool bIsOutsideCloudLayer = CloudSampleAltitudeKm < RenderVolumetricCloudParameters.BottomRadiusKm || CloudSampleAltitudeKm > RenderVolumetricCloudParameters.TopRadiusKm; + if (TDepthBuffer < TMin) { // If there is an opaque object in front of the cloud layer, we wantto try to keep tracing near the edges to help with reprojection and upsampling quality. @@ -482,17 +493,18 @@ bool TraceClouds( float4 CloudLayerFrontClipPosition = mul(float4((CloudLayerFrontPosition + ResolvedView.PreViewTranslation).xyz, 1.0f), ResolvedView.TranslatedWorldToClip); float CloudLayerFrontZDepth = (CloudLayerFrontClipPosition / CloudLayerFrontClipPosition.w).z; - const float DistanceInFrontOfLayerCullingTracing = 50000.0f; // 500 meters - float4 ScreenUVMinMax = saturate((SvPosition.xyxy + float4(-1.0f, -1.0f, 1.0f, 1.0f)) * RenderVolumetricCloudParameters.OutputSizeInvSize.zwzw); + const float2 BufferToTextureUVRatio = View.BufferSizeAndInvSize.xy * View.ViewSizeAndInvSize.zw; + const float TestedTileSize = 2.0f; + float4 ScreenUVMinMax = saturate((SvPosition.xyxy + TestedTileSize*float4(-1.0f, -1.0f, 1.0f, 1.0f)) * RenderVolumetricCloudParameters.OutputSizeInvSize.zwzw * BufferToTextureUVRatio.xyxy); if ( RenderVolumetricCloudParameters.HasValidHZB && ( // We are not conservatively being an opaque mesh (we are still going to trace around opaque mesh edges) - !ViewHierarchicalZBufferMinDepth(ScreenUVMinMax, CloudLayerFrontZDepth) || - // Under the cloud layer, at the intersection between opaque and to the cloud bottom layer, we do not want to see through opaque object. - // This is a bit arbitrary and assumes we are at the top of the planet but it helps a lot with quality. - ((CloudLayerFrontPosition.z - DepthBufferPosition.z) < DistanceInFrontOfLayerCullingTracing && CloudLayerFrontPosition.z > DepthBufferPosition.z) + !ViewHierarchicalZBufferMinDepth(ScreenUVMinMax, CloudLayerFrontZDepth) ) + + // In this case we always want to early exit + || !RenderVolumetricCloudParameters.HasValidHZB ) { // The sky is not visible when using conservative furthest depth so we do not trace cloud. @@ -502,6 +514,9 @@ bool TraceClouds( return false; } + + // We still need to do that in case after the conservative test in order to not see the tile when rendering at high quality and full resolution for instance. + TMax = RenderVolumetricCloudParameters.ClampRayTToDepthBufferPostHZB ? min(TMax, TDepthBuffer) : TMax; } else { @@ -653,7 +668,7 @@ bool TraceClouds( UpdateMaterialCloudParam(MaterialParameters, SampleWorldPosition, ResolvedView, CloudLayerParams); #if MATERIAL_VOLUMETRIC_ADVANCED_CONSERVATIVE_DENSITY - if (MaterialParameters.VolumeSampleConservativeDensity <= 0.0f) + if (MaterialParameters.VolumeSampleConservativeDensity.x <= 0.0f) { t += StepT; continue; // Conservative density is 0 so skip and go to the next sample @@ -706,10 +721,17 @@ bool TraceClouds( ParticipatingMediaContext PMC = SetupParticipatingMediaContext(Albedo, ExtinctionCoefficients, MsScattFactor, MsExtinFactor, AtmosphereTransmittanceToLight0, AtmosphereTransmittanceToLight1); - // We always apply the sky distant luminance as computed by the SkyAtmosphere component for a given altitude. TODO: this should be spatially varying accoring to height and sun angle. - // We also reduce the sky contibution at the bottom of the cloud using a cheap gradient that is super fast to apply and artist controlable. - float3 DistantLightLuminance = DistantSkyLightLuminance * saturate(RenderVolumetricCloudParameters.SkyLightCloudBottomVisibility + MaterialParameters.CloudSampleNormAltitudeInLayer); - + // We always apply the sky distant luminance as computed by the SkyAtmosphere component for a given altitude. + // TODO: this should be spatially varying accoring to height and sun angle. + float3 DistantLightLuminance = DistantSkyLightLuminance; + #if MATERIAL_VOLUMETRIC_ADVANCED_OVERRIDE_AMBIENT_OCCLUSION + // We reduce the sky contibution as specified by the user instead of the default behavior. + DistantLightLuminance *= SampleAmbientOcclusion(PixelMaterialInputs); + #else + // We reduce the sky contibution at the bottom of the cloud using a cheap gradient that is super fast to apply and artist controlable. + DistantLightLuminance *= saturate(RenderVolumetricCloudParameters.SkyLightCloudBottomVisibility + MaterialParameters.CloudSampleNormAltitudeInLayer); + #endif + ////////////////////////////// // Evaluate some data if there is medium causing scattering, e.g. shadow, ground lighting @@ -748,7 +770,7 @@ bool TraceClouds( UpdateMaterialCloudParam(MaterialParameters, SampleWorldPosition + float3(0.0f, 0.0f, -1.0f) * ShadowLengthTest * (CurrentNormT - 0.5 * DetlaNormT), ResolvedView, CloudLayerParams); PreviousNormT = CurrentNormT; #if MATERIAL_VOLUMETRIC_ADVANCED_CONSERVATIVE_DENSITY - if (MaterialParameters.VolumeSampleConservativeDensity <= 0.0f) + if (MaterialParameters.VolumeSampleConservativeDensity.x <= 0.0f) { continue; // Conservative density is 0 so skip and go to the next sample } @@ -765,7 +787,7 @@ bool TraceClouds( { UpdateMaterialCloudParam(MaterialParameters, SampleWorldPosition + float3(0.0f, 0.0f, -1.0f) * ShadowLengthTest * (ShadowT * InvShadowStepCount), ResolvedView, CloudLayerParams); #if MATERIAL_VOLUMETRIC_ADVANCED_CONSERVATIVE_DENSITY - if (MaterialParameters.VolumeSampleConservativeDensity <= 0.0f) + if (MaterialParameters.VolumeSampleConservativeDensity.x <= 0.0f) { continue; // Conservative density is 0 so skip and go to the next sample } @@ -840,7 +862,7 @@ bool TraceClouds( #endif #if MATERIAL_VOLUMETRIC_ADVANCED_CONSERVATIVE_DENSITY - if (MaterialParameters.VolumeSampleConservativeDensity <= 0.0f) + if (MaterialParameters.VolumeSampleConservativeDensity.x <= 0.0f) { continue; // Conservative density is 0 so skip and go to the next sample } @@ -894,7 +916,7 @@ bool TraceClouds( PreviousNormT = CurrentNormT; #if MATERIAL_VOLUMETRIC_ADVANCED_CONSERVATIVE_DENSITY - if (MaterialParameters.VolumeSampleConservativeDensity <= 0.0f) + if (MaterialParameters.VolumeSampleConservativeDensity.x <= 0.0f) { continue; // Conservative density is 0 so skip and go to the next sample } @@ -1108,7 +1130,7 @@ void MainPS( // This is the default depth when no cloud has been intersected. // It is better to limit depth to a close distance instead of max float to smooth out cloud edges when intersecting opaque meshes. // No visual issues have been noticed with reprojection+TAA so far. - const float NoCloudDepth = TMax * CENTIMETER_TO_KILOMETER; + const float NoCloudDepth = TMax; const float tAP = tAPWeightsSum==0.0f ? NoCloudDepth : tAPWeightedSum / max(0.0000000001f, tAPWeightsSum); float3 AbsoluteWorldPosition = RayOrigin + tAP * Raydir; @@ -1166,9 +1188,9 @@ void MainPS( OutColor0 = float4(Luminance * OutputPreExposure, GrayScaleTransmittance); OutColor1 = MaxHalfFloat; // Default to far away depth to be flat and not intersect with any geometry. - if (RenderVolumetricCloudParameters.IntersectWithOpaque > 0) + if (RenderVolumetricCloudParameters.OpaqueIntersectionMode >= 1) { - OutColor1 = (GrayScaleTransmittance > 0.99) ? NoCloudDepth : tAP * CENTIMETER_TO_KILOMETER; // using a small threshold on transmittance + OutColor1 = ((GrayScaleTransmittance > 0.99) ? NoCloudDepth : tAP) * CENTIMETER_TO_KILOMETER; // using a small threshold on transmittance } #if 0 @@ -1334,7 +1356,7 @@ void MainPS( UpdateMaterialCloudParam(MaterialParameters, WorldPosOnLayer + Light0Direction * SampleT, ResolvedView, CloudLayerParams); #if MATERIAL_VOLUMETRIC_ADVANCED_CONSERVATIVE_DENSITY - if (MaterialParameters.VolumeSampleConservativeDensity <= 0.0f) + if (MaterialParameters.VolumeSampleConservativeDensity.x <= 0.0f) { continue; // Conservative density is 0 so skip and go to the next sample } diff --git a/Engine/Shaders/Private/VolumetricFog.usf b/Engine/Shaders/Private/VolumetricFog.usf index 1f85a111c6e2..39a8b74d6755 100644 --- a/Engine/Shaders/Private/VolumetricFog.usf +++ b/Engine/Shaders/Private/VolumetricFog.usf @@ -237,6 +237,7 @@ void InjectShadowedLocalLightPS( LightData.SpecularScale = DeferredLightUniforms.SpecularScale; LightData.ContactShadowLength = abs(DeferredLightUniforms.ContactShadowLength); LightData.ContactShadowLengthInWS = DeferredLightUniforms.ContactShadowLength < 0.0f; + LightData.ContactShadowNonShadowCastingIntensity = DeferredLightUniforms.ContactShadowNonShadowCastingIntensity; LightData.DistanceFadeMAD = DeferredLightUniforms.DistanceFadeMAD; LightData.ShadowMapChannelMask = DeferredLightUniforms.ShadowMapChannelMask; LightData.ShadowedBits = DeferredLightUniforms.ShadowedBits; diff --git a/Engine/Shaders/Private/VolumetricRenderTarget.usf b/Engine/Shaders/Private/VolumetricRenderTarget.usf index 63c059ccd6f8..d19f8ad5d391 100644 --- a/Engine/Shaders/Private/VolumetricRenderTarget.usf +++ b/Engine/Shaders/Private/VolumetricRenderTarget.usf @@ -372,12 +372,10 @@ void ReconstructVolumetricRenderTargetPS( DepthBoxMinCross = min(DepthBoxMinCross, MiddlePointDepth); DepthBoxMaxCross = max(DepthBoxMaxCross, MiddlePointDepth); - //float DepthBoxMin = 0.5f * (DepthBoxMinPlus + DepthBoxMinCross); - //float DepthBoxMax = 0.5f * (DepthBoxMaxPlus + DepthBoxMaxCross); - //FilteredDepth = clamp(PrevDepth, DepthBoxMin, DepthBoxMax); // simple depth range clamp - // Doing some mean + clamp of depth as above does not have any justification really. - // So instead we simply take the min. - FilteredDepth = min(DepthBoxMinPlus, DepthBoxMinCross); + // This reconstruction must be done for now to get proper depth matching the particpating media extent on screen + float DepthBoxMin = 0.5f * (DepthBoxMinPlus + DepthBoxMinCross); + float DepthBoxMax = 0.5f * (DepthBoxMaxPlus + DepthBoxMaxCross); + FilteredDepth = clamp(PrevDepth, DepthBoxMin, DepthBoxMax); // simple depth range clamp float FinalTemporalFactor = TemporalFactor; #if 1 @@ -465,6 +463,12 @@ Texture2D VolumetricDepthTexture; uint4 VolumetricTextureValidCoordRect; float4 VolumetricTextureValidUvRect; +#if PERMUTATION_LINEARDEPTH +Texture2D WaterLinearDepthTexture; +float4 SceneWithoutSingleLayerWaterViewRect; +float2 FullResolutionToWaterBufferScale; +#endif + float4 SafeLoadVolumetricTexture(uint2 Coord) { return VolumetricTexture.Load(uint3(clamp(Coord, VolumetricTextureValidCoordRect.xy, VolumetricTextureValidCoordRect.zw), 0)); @@ -484,7 +488,7 @@ float SafeSampleVolumetricDepthTexture(float2 UV) float4 VolumetricTextureSizeAndInvSize; float UvOffsetScale; -float2 FullResolutionToVolumetricBufferResolution; +float2 FullResolutionToVolumetricBufferResolutionScale; #define DynamicResToFullResolutionCoord (View.ViewSizeAndInvSize.xy * View.BufferSizeAndInvSize.zw) @@ -515,20 +519,19 @@ void Sample( } - void ComposeVolumetricRTOverScenePS( in float4 SVPos : SV_POSITION, out float4 OutputRt0 : SV_Target0) { float2 CurResPixelCoord = SVPos.xy; float2 ScreenUV = CurResPixelCoord * View.BufferSizeAndInvSize.zw; - float2 VolumeUV = FullResolutionToVolumetricBufferResolution.x * (ScreenUV * View.BufferSizeAndInvSize.xy * VolumetricTextureSizeAndInvSize.zw); + float2 VolumeUV = FullResolutionToVolumetricBufferResolutionScale.x * (ScreenUV * View.BufferSizeAndInvSize.xy * VolumetricTextureSizeAndInvSize.zw); //Make the offset be independent of aspect ratio, resolution scale, downsampling const float2 FullResOffsetUVScale = float2(1.0f,View.BufferSizeAndInvSize.x * View.BufferSizeAndInvSize.w) // Aspect ratio correction * View.BufferSizeAndInvSize.zw // Pixel size - * FullResolutionToVolumetricBufferResolution.y; // Volumetric buffer downsample factor + * FullResolutionToVolumetricBufferResolutionScale.y; // Volumetric buffer downsample factor float2 Offset0 = (float2(Rand3DPCG16(int3(CurResPixelCoord, View.StateFrameIndexMod8 )).xy) * rcp(65536.0)) * 2.0f - 1.0f; float2 Offset1 = (float2(Rand3DPCG16(int3(CurResPixelCoord, View.StateFrameIndexMod8 + 8 )).xy) * rcp(65536.0)) * 2.0f - 1.0f; @@ -563,8 +566,27 @@ void ComposeVolumetricRTOverScenePS( float2 VolumeCoord = (VolumeUV - VolumetricTextureSizeAndInvSize.zw * 0.5f); // 0.5 is the scale between upsampled and fullres, we want to offset down one full res pixel VolumeCoord = (floor(VolumeCoord * VolumetricTextureSizeAndInvSize.xy) + 0.5f); // bottom left of the squere of texels to consider, at texel center +#if PERMUTATION_LINEARDEPTH + // Adapt the UV to the relative water buffer size + VolumeUV *= FullResolutionToWaterBufferScale.y; + // Offset the uv to the view buffer region and take into account dynamic resolution scaling. + float2 DepthScreenUV = SceneWithoutSingleLayerWaterViewRect.xy + VolumeUV * (View.ViewSizeAndInvSize.xy * View.BufferSizeAndInvSize.zw); + + const float UseWaterLinearDepthTexture = 100.0f; // This must match SINGLE_LAYER_WATER_DEPTH_SCALE from SingleLayerWaterCommon.ush and SingleLayerWaterComposite.usf + float PixelLinearDepth = WaterLinearDepthTexture.SampleLevel(LinearTextureSampler, DepthScreenUV, 0).r * UseWaterLinearDepthTexture; + + float3 WorldPosition = float4(SvPositionToWorld(float4(SVPos.xy, 0.5, 1.0)), 1.0).xyz; + WorldPosition = normalize(WorldPosition - View.WorldCameraOrigin) * PixelLinearDepth + View.WorldCameraOrigin; + + float4 ClipPosition = mul(float4(WorldPosition,1.0), View.WorldToClip); + ClipPosition /= ClipPosition.w; + + float3 ScreenWorldPosition = float4(SvPositionToWorld(float4(SVPos.xy, ClipPosition.z, 1.0)), 1.0).xyz; +#else float PixelDeviceZ = SampleDeviceZFromSceneTextures(ScreenUV); float3 ScreenWorldPosition = float4(SvPositionToWorld(float4(SVPos.xy, PixelDeviceZ, 1.0)), 1.0).xyz; +#endif + float PixelFrontDepthFromViewKm = length(View.WorldCameraOrigin - ScreenWorldPosition) * (1.0f / 100000.0f); // centimeter to kilometer #if PERMUTATION_UPSAMPLINGMODE==2 diff --git a/Engine/Shaders/Public/Platform.ush b/Engine/Shaders/Public/Platform.ush index e17f72f3cdc8..32a804e8fae7 100644 --- a/Engine/Shaders/Public/Platform.ush +++ b/Engine/Shaders/Public/Platform.ush @@ -835,3 +835,7 @@ uint2 UnpackUlongType(UlongType Value) #define INFINITE_FLOAT 1.#INF #endif #endif + +#ifndef RWTextureCube +#define RWTextureCube RWTexture2DArray +#endif diff --git a/Engine/Shaders/Public/Platform/Metal/MetalCommon.ush b/Engine/Shaders/Public/Platform/Metal/MetalCommon.ush index fbdd5994b02c..82238f9caf4f 100644 --- a/Engine/Shaders/Public/Platform/Metal/MetalCommon.ush +++ b/Engine/Shaders/Public/Platform/Metal/MetalCommon.ush @@ -5,7 +5,7 @@ =============================================================================*/ // Update this GUID to improve shader recompilation for Metal only shaders -// GUID = 184F8432-76C6-49CD-8D1A-EDD9AF96F9BA +// GUID = E3AC9C39-C9CD-48C1-8F24-DEDF572A4871 #pragma once diff --git a/Engine/Shaders/Public/ShaderVersion.ush b/Engine/Shaders/Public/ShaderVersion.ush index 6607fdff9457..7f189471a89b 100644 --- a/Engine/Shaders/Public/ShaderVersion.ush +++ b/Engine/Shaders/Public/ShaderVersion.ush @@ -3,4 +3,4 @@ // in Platform.ush (which should be included in any shader) it allows to invalidate the shader DDC. // // If you are merging streams and there is a conflict with this GUID you should make a new GUID rather than taking one or the other. -// GUID = 492AFE66-2258-455E-93CA-096015A3E2B7 +// GUID = 7CE7A4C3-6DB7-4C94-A510-BEC64474ECDA diff --git a/Engine/Shaders/Shared/RayTracingDefinitions.h b/Engine/Shaders/Shared/RayTracingDefinitions.h index 957c90e432a9..63ff9cdfe694 100644 --- a/Engine/Shaders/Shared/RayTracingDefinitions.h +++ b/Engine/Shaders/Shared/RayTracingDefinitions.h @@ -9,7 +9,7 @@ // Change this to force recompilation of all ray tracing shaders (use https://www.random.org/cgi-bin/randbyte?nbytes=4&format=h) // This avoids changing the global ShaderVersion.ush and forcing recompilation of all shaders in the engine (only RT shaders will be affected) -#define RAY_TRACING_SHADER_VERSION 0xaa8a1c7d +#define RAY_TRACING_SHADER_VERSION 0x7034a1ef #define RAY_TRACING_REGISTER_SPACE_LOCAL 0 // default register space for hit group (closest hit, any hit, intersection) shader resources #define RAY_TRACING_REGISTER_SPACE_GLOBAL 1 // register space for ray generation and miss shaders diff --git a/Engine/Source/Developer/Apple/MetalShaderFormat/Private/MetalShaderCompiler.cpp b/Engine/Source/Developer/Apple/MetalShaderFormat/Private/MetalShaderCompiler.cpp index 490f3d1a3d53..6b8821ef1c12 100644 --- a/Engine/Source/Developer/Apple/MetalShaderFormat/Private/MetalShaderCompiler.cpp +++ b/Engine/Source/Developer/Apple/MetalShaderFormat/Private/MetalShaderCompiler.cpp @@ -2231,35 +2231,35 @@ void CompileShader_Metal(const FShaderCompilerInput& _Input,FShaderCompilerOutpu } else if (Input.ShaderFormat == NAME_SF_METAL_MACES3_1) { - UE_CLOG(VersionEnum < 3, LogShaders, Warning, TEXT("Metal shader version must be Metal v2.0 or higher for format %s!"), VersionEnum, *Input.ShaderFormat.ToString()); + UE_CLOG(VersionEnum < 4, LogShaders, Warning, TEXT("Metal shader version must be Metal v2.1 or higher for format %s!"), VersionEnum, *Input.ShaderFormat.ToString()); AdditionalDefines.SetDefine(TEXT("METAL_PROFILE"), 1); - VersionEnum = VersionEnum >= 3 ? VersionEnum : 3; + VersionEnum = VersionEnum >= 4 ? VersionEnum : 4; MetalCompilerTarget = HCT_FeatureLevelES3_1; Semantics = EMetalGPUSemanticsImmediateDesktop; } else if (Input.ShaderFormat == NAME_SF_METAL_SM5_NOTESS) { - UE_CLOG(VersionEnum < 3, LogShaders, Warning, TEXT("Metal shader version must be Metal v2.0 or higher for format %s!"), VersionEnum, *Input.ShaderFormat.ToString()); + UE_CLOG(VersionEnum < 4, LogShaders, Warning, TEXT("Metal shader version must be Metal v2.1 or higher for format %s!"), VersionEnum, *Input.ShaderFormat.ToString()); AdditionalDefines.SetDefine(TEXT("METAL_SM5_NOTESS_PROFILE"), 1); AdditionalDefines.SetDefine(TEXT("USING_VERTEX_SHADER_LAYER"), 1); - VersionEnum = VersionEnum >= 3 ? VersionEnum : 3; + VersionEnum = VersionEnum >= 4 ? VersionEnum : 4; MetalCompilerTarget = HCT_FeatureLevelSM5; Semantics = EMetalGPUSemanticsImmediateDesktop; } else if (Input.ShaderFormat == NAME_SF_METAL_SM5) { - UE_CLOG(VersionEnum < 3, LogShaders, Warning, TEXT("Metal shader version must be Metal v2.0 or higher for format %s!"), VersionEnum, *Input.ShaderFormat.ToString()); + UE_CLOG(VersionEnum < 4, LogShaders, Warning, TEXT("Metal shader version must be Metal v2.1 or higher for format %s!"), VersionEnum, *Input.ShaderFormat.ToString()); AdditionalDefines.SetDefine(TEXT("METAL_SM5_PROFILE"), 1); AdditionalDefines.SetDefine(TEXT("USING_VERTEX_SHADER_LAYER"), 1); - VersionEnum = VersionEnum >= 3 ? VersionEnum : 3; + VersionEnum = VersionEnum >= 4 ? VersionEnum : 4; MetalCompilerTarget = HCT_FeatureLevelSM5; Semantics = EMetalGPUSemanticsImmediateDesktop; } else if (Input.ShaderFormat == NAME_SF_METAL_MRT_MAC) { - UE_CLOG(VersionEnum < 3, LogShaders, Warning, TEXT("Metal shader version must be Metal v2.0 or higher for format %s!"), VersionEnum, *Input.ShaderFormat.ToString()); + UE_CLOG(VersionEnum < 4, LogShaders, Warning, TEXT("Metal shader version must be Metal v2.1 or higher for format %s!"), VersionEnum, *Input.ShaderFormat.ToString()); AdditionalDefines.SetDefine(TEXT("METAL_MRT_PROFILE"), 1); - VersionEnum = VersionEnum >= 3 ? VersionEnum : 3; + VersionEnum = VersionEnum >= 4 ? VersionEnum : 4; MetalCompilerTarget = HCT_FeatureLevelSM5; Semantics = EMetalGPUSemanticsTBDRDesktop; } diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTools.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTools.cpp index 6ea93173af62..4be0fe1a66bf 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTools.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetTools.cpp @@ -98,6 +98,7 @@ #include "AssetTypeActions/AssetTypeActions_TextureCube.h" #include "AssetTypeActions/AssetTypeActions_VolumeTexture.h" #include "AssetTypeActions/AssetTypeActions_TextureRenderTargetCube.h" +#include "AssetTypeActions/AssetTypeActions_TextureRenderTargetVolume.h" #include "AssetTypeActions/AssetTypeActions_TextureLightProfile.h" #include "AssetTypeActions/AssetTypeActions_TouchInterface.h" #include "AssetTypeActions/AssetTypeActions_VectorFieldAnimated.h" @@ -277,6 +278,7 @@ UAssetToolsImpl::UAssetToolsImpl(const FObjectInitializer& ObjectInitializer) RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_TextureRenderTarget)); RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_TextureRenderTarget2D)); RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_TextureRenderTargetCube)); + RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_TextureRenderTargetVolume)); RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_TextureLightProfile)); RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_TouchInterface)); RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_VectorField)); diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_TextureRenderTarget.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_TextureRenderTarget.cpp index f72916d08240..0d56094dfff8 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_TextureRenderTarget.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_TextureRenderTarget.cpp @@ -5,8 +5,10 @@ #include "Engine/Texture2D.h" #include "EditorStyleSet.h" #include "Engine/TextureCube.h" +#include "Engine/VolumeTexture.h" #include "Engine/TextureRenderTarget2D.h" #include "Engine/TextureRenderTargetCube.h" +#include "Engine/TextureRenderTargetVolume.h" #include "AssetRegistryModule.h" #define LOCTEXT_NAMESPACE "AssetTypeActions" @@ -43,11 +45,16 @@ void FAssetTypeActions_TextureRenderTarget::ExecuteCreateStatic(TArray(Object); UTextureRenderTargetCube* TexRTCube = Cast(Object); + UTextureRenderTargetVolume* TexRTVolume = Cast(Object); if( TexRTCube ) { // create a static cube texture as well as its 6 faces NewObj = TexRTCube->ConstructTextureCube( CreatePackage(NULL,*PackageName), Name, Object->GetMaskedFlags() ); } + else if (TexRTVolume) + { + NewObj = TexRTVolume->ConstructTextureVolume(CreatePackage(NULL, *PackageName), Name, Object->GetMaskedFlags()); + } else if( TexRT ) { // create a static 2d texture diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_TextureRenderTargetVolume.h b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_TextureRenderTargetVolume.h new file mode 100644 index 000000000000..6395ec9d76ce --- /dev/null +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_TextureRenderTargetVolume.h @@ -0,0 +1,16 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AssetTypeActions/AssetTypeActions_TextureRenderTarget.h" +#include "Engine/TextureRenderTargetVolume.h" + +class FAssetTypeActions_TextureRenderTargetVolume : public FAssetTypeActions_TextureRenderTarget +{ +public: + // IAssetTypeActions Implementation + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_TextureRenderTargetVolume", "Volume Render Target"); } + virtual UClass* GetSupportedClass() const override { return UTextureRenderTargetVolume::StaticClass(); } + virtual bool CanFilter() override { return true; } +}; diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_World.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_World.cpp index 24813fc28788..9ec508fbc1dc 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_World.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_World.cpp @@ -14,24 +14,10 @@ void FAssetTypeActions_World::OpenAssetEditor( const TArray& InObjects UWorld* World = Cast(*ObjIt); if (World != nullptr && ensureMsgf(World->GetTypedOuter(), TEXT("World(%s) is not in a package and cannot be opened"), *World->GetFullName())) { - const FString WorldPathName = World->GetPathName(); - - // If there are any unsaved changes to the current level, see if the user wants to save those first. - bool bPromptUserToSave = true; - bool bSaveMapPackages = true; - bool bSaveContentPackages = true; - if (FEditorFileUtils::SaveDirtyPackages(bPromptUserToSave, bSaveMapPackages, bSaveContentPackages)) - { - // Saving dirty packages may have invalidated the current world pointer (if it was overwritten), so find the world again - World = FindObject(nullptr, *WorldPathName); - if (World != nullptr && ensureMsgf(World->GetTypedOuter(), TEXT("World(%s) is not in a package and cannot be opened"), *World->GetFullName())) - { - const FString FileToOpen = FPackageName::LongPackageNameToFilename(World->GetOutermost()->GetName(), FPackageName::GetMapPackageExtension()); - const bool bLoadAsTemplate = false; - const bool bShowProgress = true; - FEditorFileUtils::LoadMap(FileToOpen, bLoadAsTemplate, bShowProgress); - } - } + const FString FileToOpen = FPackageName::LongPackageNameToFilename(World->GetOutermost()->GetName(), FPackageName::GetMapPackageExtension()); + const bool bLoadAsTemplate = false; + const bool bShowProgress = true; + FEditorFileUtils::LoadMap(FileToOpen, bLoadAsTemplate, bShowProgress); // We can only edit one world at a time... so just break after the first valid world to load break; @@ -52,4 +38,19 @@ UThumbnailInfo* FAssetTypeActions_World::GetThumbnailInfo(UObject* Asset) const return ThumbnailInfo; } +bool FAssetTypeActions_World::CanLoadAssetForPreviewOrEdit(const FAssetData& InAssetData) +{ + if (!FEditorFileUtils::IsMapPackageAsset(InAssetData.ObjectPath.ToString())) + { + return false; + } + + // If there are any unsaved changes to the current level, see if the user wants to save those first + // If they do not wish to save, then we will bail out of opening this asset. + bool bPromptUserToSave = true; + bool bSaveMapPackages = true; + bool bSaveContentPackages = true; + return FEditorFileUtils::SaveDirtyPackages(bPromptUserToSave, bSaveMapPackages, bSaveContentPackages); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_World.h b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_World.h index 27b225013f13..8b9362232c30 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_World.h +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_World.h @@ -19,4 +19,5 @@ public: virtual uint32 GetCategories() override { return EAssetTypeCategories::Basic; } virtual bool CanLocalize() const override { return false; } virtual class UThumbnailInfo* GetThumbnailInfo(UObject* Asset) const override; + virtual bool CanLoadAssetForPreviewOrEdit(const FAssetData& InAssetData) override; }; diff --git a/Engine/Source/Developer/AssetTools/Private/AssetViewUtils.cpp b/Engine/Source/Developer/AssetTools/Private/AssetViewUtils.cpp index f3513d439449..bdb3b30fd3ba 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetViewUtils.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetViewUtils.cpp @@ -1551,8 +1551,10 @@ void ShowSyncDependenciesDialog(const TArray& InDependencies, TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) = 0; + /** Returns true if we should load this asset for previewing or editing. */ + virtual bool CanLoadAssetForPreviewOrEdit(const struct FAssetData& InAssetData) = 0; + /** Returns true if this class can be used as a filter in the content browser */ virtual bool CanFilter() = 0; diff --git a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendAnim.cpp b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendAnim.cpp index cfb571a92656..ba52f09432fb 100644 --- a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendAnim.cpp +++ b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendAnim.cpp @@ -7,6 +7,7 @@ #include "BlueprintCompilerCppBackendUtils.h" #include "Animation/AnimClassData.h" #include "Animation/AnimNodeBase.h" +#include "Animation/AnimBlueprintClassSubsystem.h" void FBackendHelperAnim::AddHeaders(FEmitterLocalContext& Context) { @@ -21,11 +22,22 @@ void FBackendHelperAnim::CreateAnimClassData(FEmitterLocalContext& Context) { if (UAnimBlueprintGeneratedClass* AnimClass = Cast(Context.GetCurrentlyGeneratedClass())) { - const FString LocalNativeName = Context.GenerateUniqueLocalName(); - Context.AddLine(FString::Printf(TEXT("UAnimClassData* %s = NewObject(InDynamicClass, TEXT(\"AnimClassData\"));"), *LocalNativeName)); - - UAnimClassData* AnimClassData = NewObject(GetTransientPackage(), TEXT("AnimClassData")); + UAnimClassData* AnimClassData = NewObject(AnimClass, TEXT("AnimClassData")); AnimClassData->CopyFrom(AnimClass); + FEmitDefaultValueHelper::HandleClassSubobject(Context, AnimClassData, FEmitterLocalContext::EClassSubobjectList::MiscConvertedSubobjects, true, false); + + const FString LocalNativeName = Context.ClassSubobjectsMap.FindChecked(AnimClassData); + + auto CreateClassSubobjects = [&Context, &AnimClassData](bool bCreate, bool bInitialize) + { + for(UAnimBlueprintClassSubsystem* Subsystem : AnimClassData->GetSubsystems()) + { + FEmitDefaultValueHelper::HandleClassSubobject(Context, Subsystem, FEmitterLocalContext::EClassSubobjectList::MiscConvertedSubobjects, bCreate, bInitialize); + } + }; + + CreateClassSubobjects(true, false); + CreateClassSubobjects(false, true); UObject* ObjectArchetype = AnimClassData->GetArchetype(); for (const FProperty* Property : TFieldRange(UAnimClassData::StaticClass())) @@ -37,8 +49,11 @@ void FBackendHelperAnim::CreateAnimClassData(FEmitterLocalContext& Context) , FEmitDefaultValueHelper::EPropertyGenerationControlFlags::AllowTransient); } - Context.AddLine(FString::Printf(TEXT("%s->ResolvePropertyPaths();"), *LocalNativeName)); Context.AddLine(FString::Printf(TEXT("InDynamicClass->%s = %s;"), GET_MEMBER_NAME_STRING_CHECKED(UDynamicClass, AnimClassImplementation), *LocalNativeName)); + Context.AddLine(FString::Printf(TEXT("%s->DynamicClassInitialization(InDynamicClass);"), *LocalNativeName)); + + // rename the temp anim class data out of the way + AnimClassData->Rename(nullptr, GetTransientPackage()); } } @@ -83,9 +98,9 @@ void FBackendHelperAnim::AddAnimNodeInitializationFunction(FEmitterLocalContext& // anim nodes constructed, finish anim node initialization: if (UAnimBlueprintGeneratedClass* AnimClass = Cast(Context.GetCurrentlyGeneratedClass())) { - for (int32 i = 0; i < AnimClass->EvaluateGraphExposedInputs.Num(); ++i) + for (int32 i = 0; i < AnimClass->GetExposedValueHandlers().Num(); ++i) { - if (AnimClass->EvaluateGraphExposedInputs[i].ValueHandlerNodeProperty == InProperty) + if (AnimClass->GetExposedValueHandlers()[i].ValueHandlerNodeProperty == InProperty) { const FString ClassName = FEmitHelper::GetCppName(Context.GetCurrentlyGeneratedClass()); const FString MemberName = FEmitHelper::GetCppName(const_cast(InProperty)); diff --git a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendValueHelper.cpp b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendValueHelper.cpp index d4da30fc4962..5c246d2246f2 100644 --- a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendValueHelper.cpp +++ b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendValueHelper.cpp @@ -869,6 +869,7 @@ protected: const FName ComponentCollisionProfileName = Component->BodyInstance.GetCollisionProfileName(); const FName ComponentArchetypeCollisionProfileName = ComponentArchetype->BodyInstance.GetCollisionProfileName(); const bool bIsArchetypeUsingCustomCollisionProfile = ComponentArchetypeCollisionProfileName == UCollisionProfile::CustomCollisionProfileName; + const bool bIsCollisionProfileDifferentFromArchetype = ComponentCollisionProfileName != ComponentArchetypeCollisionProfileName; // Initialize a new struct instance that matches the archetype (represents the default struct value inherited by the component template). FStructOnScope BodyInstanceToCompare(FBodyInstance::StaticStruct()); @@ -876,7 +877,7 @@ protected: // If the component template's collision profile setting differs from the default value (or is custom), set it using the API to load the modified collision profile. bool bResetCollisionProfileAtRuntime = false; - if (ComponentCollisionProfileName != ComponentArchetypeCollisionProfileName || bIsArchetypeUsingCustomCollisionProfile) + if (bIsCollisionProfileDifferentFromArchetype || bIsArchetypeUsingCustomCollisionProfile) { // This will initialize the struct's default value in the same manner as will occur at runtime, so we don't emit redundant initialization code to the ctor. ((FBodyInstance*)BodyInstanceToCompare.GetStructMemory())->SetCollisionProfileName(ComponentCollisionProfileName); @@ -893,7 +894,17 @@ protected: // we've emitted the code above to initialize the remainder of the BodyInstance struct value. It's ok to emit this call last for any non-custom collision profile overrides as well. if (bResetCollisionProfileAtRuntime) { - Context.AddLine(FString::Printf(TEXT("%s->SetCollisionProfileName(FName(TEXT(\"%s\")));"), *VariableName, *ComponentCollisionProfileName.ToString().ReplaceCharWithEscapedChar())); + if (bIsCollisionProfileDifferentFromArchetype) + { + Context.AddLine(FString::Printf(TEXT("%s->SetCollisionProfileName(FName(TEXT(\"%s\")));"), *VariableName, *ComponentCollisionProfileName.ToString().ReplaceCharWithEscapedChar())); + } + else + { + // In this case, the archetype and the instance are both using a custom profile, so SetCollisionProfileName() would return + // without doing anything since the profile name already matches the current setting. To get around that, we emit a direct + // call to LoadProfileData() instead to initialize transient values from the custom profile data emitted to the ctor above. + Context.AddLine(FString::Printf(TEXT("%s.LoadProfileData(false);"), *PathToMember)); + } } } else diff --git a/Engine/Source/Developer/BlueprintNativeCodeGen/Private/BlueprintNativeCodeGenModule.cpp b/Engine/Source/Developer/BlueprintNativeCodeGen/Private/BlueprintNativeCodeGenModule.cpp index 204969589912..c4e7e1a1c0a9 100644 --- a/Engine/Source/Developer/BlueprintNativeCodeGen/Private/BlueprintNativeCodeGenModule.cpp +++ b/Engine/Source/Developer/BlueprintNativeCodeGen/Private/BlueprintNativeCodeGenModule.cpp @@ -874,8 +874,12 @@ EReplacementResult FBlueprintNativeCodeGenModule::IsTargetedForReplacement(const } // DATA ONLY BP { + // @todo - Temp; remove once DOBPs based on UActorComponent can be successfully nativized. These were only recently + // added to DOBP consideration and they currently break nativization if data-only and excluded. Needs more investigation. + const bool bIsComponentBasedBPClass = BlueprintClass && BlueprintClass->IsChildOf(); + static const FBoolConfigValueHelper DontNativizeDataOnlyBP(TEXT("BlueprintNativizationSettings"), TEXT("bDontNativizeDataOnlyBP")); - if (DontNativizeDataOnlyBP && !bNativizeOnlySelectedBPs && Blueprint && FBlueprintEditorUtils::IsDataOnlyBlueprint(Blueprint)) + if (DontNativizeDataOnlyBP && !bNativizeOnlySelectedBPs && Blueprint && FBlueprintEditorUtils::IsDataOnlyBlueprint(Blueprint) && !bIsComponentBasedBPClass) { return true; } diff --git a/Engine/Source/Developer/DesktopPlatform/Private/PlatformInfo.cpp b/Engine/Source/Developer/DesktopPlatform/Private/PlatformInfo.cpp index 57976859341c..4c317faa0fef 100644 --- a/Engine/Source/Developer/DesktopPlatform/Private/PlatformInfo.cpp +++ b/Engine/Source/Developer/DesktopPlatform/Private/PlatformInfo.cpp @@ -16,6 +16,7 @@ namespace PlatformInfo { TArray AllPlatformGroupNames; TArray AllVanillaPlatformNames; + TMap PreviewPlatformMenuItems; namespace { @@ -103,6 +104,9 @@ FTargetPlatformInfo::FTargetPlatformInfo(const FString& InIniPlatformName, EBuil VanillaPlatformInfoArray.Add(this); AllVanillaPlatformNames.AddUnique(Name); } + + // add perview platform menu items from DDPI + PreviewPlatformMenuItems.Append(DataDrivenPlatformInfo->PreviewPlatformMenuItems); } @@ -172,6 +176,11 @@ const TArray& GetAllVanillaPlatformNames() return PlatformInfo::AllVanillaPlatformNames; } +const TMap& GetPreviewPlatformMenuItems() +{ + return PlatformInfo::PreviewPlatformMenuItems; +} + } // namespace PlatformInfo #endif diff --git a/Engine/Source/Developer/DesktopPlatform/Public/PlatformInfo.h b/Engine/Source/Developer/DesktopPlatform/Public/PlatformInfo.h index 67933619ec72..a953282e4825 100644 --- a/Engine/Source/Developer/DesktopPlatform/Public/PlatformInfo.h +++ b/Engine/Source/Developer/DesktopPlatform/Public/PlatformInfo.h @@ -39,7 +39,6 @@ namespace PlatformInfo CookFlavor, }; - /** Information about a given platform */ struct FTargetPlatformInfo { @@ -141,6 +140,13 @@ namespace PlatformInfo * @return An array of FNames. */ DESKTOPPLATFORM_API const TArray& GetAllVanillaPlatformNames(); + + /** + * Returns a map of all preview platform shader format names to their menu items + * Used to generate the editor preview platform menu + * @return the map of shader format names to the menu items + */ + DESKTOPPLATFORM_API const TMap& GetPreviewPlatformMenuItems(); } #endif diff --git a/Engine/Source/Developer/GameplayDebugger/Public/GameplayDebuggerPlayerManager.h b/Engine/Source/Developer/GameplayDebugger/Public/GameplayDebuggerPlayerManager.h index 7121906f8fd9..35d1d4312cf1 100644 --- a/Engine/Source/Developer/GameplayDebugger/Public/GameplayDebuggerPlayerManager.h +++ b/Engine/Source/Developer/GameplayDebugger/Public/GameplayDebuggerPlayerManager.h @@ -28,7 +28,7 @@ struct FGameplayDebuggerPlayerData }; UCLASS(NotBlueprintable, NotBlueprintType, notplaceable, noteditinlinenew, hidedropdown, Transient) -class AGameplayDebuggerPlayerManager : public AActor +class GAMEPLAYDEBUGGER_API AGameplayDebuggerPlayerManager : public AActor { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Developer/IOS/IOSTargetPlatform/Private/IOSTargetPlatform.cpp b/Engine/Source/Developer/IOS/IOSTargetPlatform/Private/IOSTargetPlatform.cpp index ebb9d733cc27..4c6cb2111835 100644 --- a/Engine/Source/Developer/IOS/IOSTargetPlatform/Private/IOSTargetPlatform.cpp +++ b/Engine/Source/Developer/IOS/IOSTargetPlatform/Private/IOSTargetPlatform.cpp @@ -551,6 +551,8 @@ static FName FormatRemap[] = FName(TEXT("BC5")), FName(TEXT("PVRTCN")), FName(TEXT("ASTC_NormalRG")), FName(TEXT("AutoDXT")), FName(TEXT("AutoPVRTC")), FName(TEXT("ASTC_RGBAuto")), FName(TEXT("BC4")), FName(TEXT("G8")), FName(TEXT("G8")), + FName(TEXT("BC6H")), FName(TEXT("PVRTC2")), FName(TEXT("ASTC_RGB")), + FName(TEXT("BC7")), FName(TEXT("AutoPVRTC")), FName(TEXT("ASTC_RGBAuto")) }; static FName NameBGRA8(TEXT("BGRA8")); static FName NameG8 = FName(TEXT("G8")); @@ -593,7 +595,7 @@ void FIOSTargetPlatform::GetTextureFormats( const UTexture* Texture, TArray< TAr { BlockSize = 1; } - GetDefaultTextureFormatNamePerLayer(TextureFormatNames, this, Texture, EngineSettings, false, false, BlockSize); + GetDefaultTextureFormatNamePerLayer(TextureFormatNames, this, Texture, EngineSettings, true, false, BlockSize); } // include the formats we want (use ASTC first so that it is preferred at runtime if they both exist and it's supported) diff --git a/Engine/Source/Developer/IoStoreUtilities/IoStoreUtilities.Build.cs b/Engine/Source/Developer/IoStoreUtilities/IoStoreUtilities.Build.cs index 32b23d6aa121..c169dd1d9685 100644 --- a/Engine/Source/Developer/IoStoreUtilities/IoStoreUtilities.Build.cs +++ b/Engine/Source/Developer/IoStoreUtilities/IoStoreUtilities.Build.cs @@ -14,6 +14,7 @@ public class IoStoreUtilities : ModuleRules "Core", "CoreUObject", "Projects", + "AssetRegistry", }); PrivateDependencyModuleNames.Add("PakFile"); PrivateDependencyModuleNames.Add("Json"); diff --git a/Engine/Source/Developer/IoStoreUtilities/Private/IoStoreUtilities.cpp b/Engine/Source/Developer/IoStoreUtilities/Private/IoStoreUtilities.cpp index 15dd045560d0..11b529236b23 100644 --- a/Engine/Source/Developer/IoStoreUtilities/Private/IoStoreUtilities.cpp +++ b/Engine/Source/Developer/IoStoreUtilities/Private/IoStoreUtilities.cpp @@ -27,6 +27,7 @@ #include "Serialization/LargeMemoryWriter.h" #include "Serialization/MemoryReader.h" #include "Serialization/AsyncLoading2.h" +#include "Serialization/ArrayReader.h" #include "UObject/Class.h" #include "UObject/NameBatchSerialization.h" #include "UObject/PackageFileSummary.h" @@ -40,6 +41,8 @@ #include "Async/AsyncFileHandle.h" #include "Async/Async.h" #include "RSA.h" +#include "Misc/AssetRegistryInterface.h" +#include "AssetRegistryState.h" //PRAGMA_DISABLE_OPTIMIZATION @@ -60,6 +63,12 @@ static const uint64 DefaultCompressionBlockSize = 64 << 10; static const uint64 DefaultCompressionBlockAlignment = 64 << 10; static const uint64 DefaultMemoryMappingAlignment = 16 << 10; +struct FReleasedPackages +{ + TSet PackageNames; + TMap PackageIdToName; +}; + struct FNamedAESKey { FString Name; @@ -640,7 +649,12 @@ struct FIoStoreArguments FKeyChain KeyChain; FKeyChain PatchKeyChain; FString DLCPluginPath; + FString DLCName; + FString BasedOnReleaseVersionPath; + FAssetRegistryState ReleaseAssetRegistry; + FReleasedPackages ReleasedPackages; bool bSign = false; + bool bRemapPluginContentToGame = false; bool ShouldCreateContainers() const { @@ -910,6 +924,7 @@ struct FPackage FPackageId GlobalPackageId; FString Region; // for localized packages FPackageId SourceGlobalPackageId; // for localized packages + FPackageId RedirectedPackageId; uint32 PackageFlags = 0; uint32 CookedHeaderSize = 0; int32 NameCount = 0; @@ -1702,7 +1717,7 @@ static FPackageObjectIndex FindAndVerifyGlobalImport( { if (bIsScript) { - UE_LOG(LogIoStore, Display, TEXT("For package '%s' (%d): Missing import script package '%s'. Editor only?"), + UE_LOG(LogIoStore, Display, TEXT("For package '%s' (0x%llX): Missing import script package '%s'. Editor only?"), *Package->Name.ToString(), Package->GlobalPackageId.ValueForDebugging(), *FullName); @@ -1716,14 +1731,14 @@ static FPackageObjectIndex FindAndVerifyGlobalImport( { if (bIsScript) { - UE_LOG(LogIoStore, Display, TEXT("For package '%s' (%d): Missing import script object '%s'. Editor only?"), + UE_LOG(LogIoStore, Display, TEXT("For package '%s' (0x%llX): Missing import script object '%s'. Editor only?"), *Package->Name.ToString(), Package->GlobalPackageId.ValueForDebugging(), *FullName); } else if (!DLCPrefix.Len() || FullName.StartsWith(DLCPrefix)) { - UE_LOG(LogIoStore, Display, TEXT("For package '%s' (%d): Missing import object '%s' due to missing public export. Editor only?"), + UE_LOG(LogIoStore, Display, TEXT("For package '%s' (0x%llX): Missing import object '%s' due to missing public export. Editor only?"), *Package->Name.ToString(), Package->GlobalPackageId.ValueForDebugging(), *FullName); @@ -1749,7 +1764,14 @@ static int32 FindExport( const FObjectExport* Export = ExportMap + LocalExportIndex; if (Export->OuterIndex.IsNull()) { - Package->Name.AppendString(FullName); + if (Package->RedirectedPackageId.IsValid()) + { + Package->SourcePackageName.AppendString(FullName); + } + else + { + Package->Name.AppendString(FullName); + } FullName.AppendChar(TEXT('/')); Export->ObjectName.AppendString(FullName); FullName.ToLowerInline(); @@ -1825,6 +1847,7 @@ FContainerTargetSpec* AddContainer( } FPackage* FindOrAddPackage( + const FIoStoreArguments& Arguments, const TCHAR* RelativeFileName, TArray& Packages, FPackageNameMap& PackageNameMap, @@ -1844,16 +1867,37 @@ FPackage* FindOrAddPackage( if (!Package) { FPackageId PackageId = FPackageId::FromName(PackageFName); - FPackage* FindById = PackageIdMap.FindRef(PackageId); - if (FindById) + if (FPackage* FindById = PackageIdMap.FindRef(PackageId)) { UE_LOG(LogIoStore, Fatal, TEXT("Package name hash collision \"%s\" and \"%s"), *FindById->Name.ToString(), *PackageFName.ToString()); } + if (const FName* ReleasedPackageName = Arguments.ReleasedPackages.PackageIdToName.Find(PackageId)) + { + UE_LOG(LogIoStore, Fatal, TEXT("Package name hash collision \"%s\" and \"%s"), *ReleasedPackageName->ToString(), *PackageFName.ToString()); + } + Package = new FPackage(); Package->Name = PackageFName; - Package->SourcePackageName = *RemapLocalizationPathIfNeeded(PackageName, &Package->Region); Package->GlobalPackageId = PackageId; + + if (Arguments.IsDLC() && Arguments.bRemapPluginContentToGame) + { + const int32 DLCNameLen = Arguments.DLCName.Len() + 1; + FString RedirectedPackageNameStr = TEXT("/Game"); + RedirectedPackageNameStr.AppendChars(*PackageName + DLCNameLen, PackageName.Len() - DLCNameLen); + FName RedirectedPackageName = FName(*RedirectedPackageNameStr); + + if (Arguments.ReleasedPackages.PackageNames.Contains(RedirectedPackageName)) + { + Package->SourcePackageName = RedirectedPackageName; + Package->RedirectedPackageId = FPackageId::FromName(RedirectedPackageName); + } + } + else + { + Package->SourcePackageName = *RemapLocalizationPathIfNeeded(PackageName, &Package->Region); + } Packages.Add(Package); PackageNameMap.Add(PackageFName, Package); @@ -1876,7 +1920,7 @@ static bool ConformLocalizedPackage( LocalizedPackage.ExportCount; UE_CLOG(SourcePackage.ExportCount != LocalizedPackage.ExportCount, LogIoStore, Verbose, - TEXT("For culture '%s': Localized package '%s' (%d) for source package '%s' (%d) - Has ExportCount %d vs. %d"), + TEXT("For culture '%s': Localized package '%s' (0x%llX) for source package '%s' (0x%llX) - Has ExportCount %d vs. %d"), *LocalizedPackage.Region, *LocalizedPackage.Name.ToString(), LocalizedPackage.GlobalPackageId.ValueForDebugging(), @@ -1956,7 +2000,7 @@ static bool ConformLocalizedPackage( if (!LocalizedExportStr || !SourceExportStr) { UE_LOG(LogIoStore, Error, - TEXT("Culture '%s': Localized package '%s' (%d) for source package '%s' (%d) - Has some bad data from an earlier phase."), + TEXT("Culture '%s': Localized package '%s' (0x%llX) for source package '%s' (0x%llX) - Has some bad data from an earlier phase."), *LocalizedPackage.Region, *LocalizedPackage.Name.ToString(), LocalizedPackage.GlobalPackageId.ValueForDebugging(), @@ -2030,7 +2074,7 @@ static bool ConformLocalizedPackage( if (FailReason.Len() > 0) { UE_LOG(LogIoStore, Warning, - TEXT("Culture '%s': Localized package '%s' (%d) for '%s' (%d) - %s"), + TEXT("Culture '%s': Localized package '%s' (0x%llX) for '%s' (0x%llX) - %s"), *LocalizedPackage.Region, *LocalizedPackage.Name.ToString(), LocalizedPackage.GlobalPackageId.ValueForDebugging(), @@ -2070,9 +2114,12 @@ static void AddPreloadDependencies( IOSTORE_CPU_SCOPE(PreLoadDependencies); UE_LOG(LogIoStore, Display, TEXT("Adding preload dependencies...")); + TSet ExternalPackageDependencies; TArray LocalizedPackages; for (FPackage* Package : Packages) { + ExternalPackageDependencies.Reset(); + // Convert PreloadDependencies to arcs for (int32 I = 0; I < Package->ExportCount; ++I) { @@ -2103,7 +2150,7 @@ static void AddPreloadDependencies( SourceToLocalizedPackageMap.MultiFind(Export->Package, LocalizedPackages); for (FPackage* LocalizedPackage : LocalizedPackages) { - UE_LOG(LogIoStore, Verbose, TEXT("For package '%s' (%d): Adding localized preload dependency '%s' in '%s'"), + UE_LOG(LogIoStore, Verbose, TEXT("For package '%s' (0x%llX): Adding localized preload dependency '%s' in '%s'"), *Package->Name.ToString(), Package->GlobalPackageId.ValueForDebugging(), *Export->ObjectName.ToString(), @@ -2114,11 +2161,19 @@ static void AddPreloadDependencies( } else { - const FObjectImport* Import = PackageAssetData.ObjectImports.GetData() + Package->ImportIndexOffset + Dep.ToImport(); - const bool bIsPackage = Import->OuterIndex.IsNull(); - if (bIsPackage) + const FObjectImport* ImportMap = PackageAssetData.ObjectImports.GetData() + Package->ImportIndexOffset; + const FObjectImport* Import = ImportMap + Dep.ToImport(); + while (!Import->OuterIndex.IsNull()) { - AddBaseGamePackageArc(ExportGraph, FPackageId::FromName(Import->ObjectName), *Package, I, PhaseTo); + Import = ImportMap + Import->OuterIndex.ToImport(); + } + + FPackageId PackageId = FPackageId::FromName(Import->ObjectName); + bool bIsAlreadyInSet = false; + ExternalPackageDependencies.Add(PackageId, &bIsAlreadyInSet); + if (!bIsAlreadyInSet) + { + AddBaseGamePackageArc(ExportGraph, PackageId, *Package, I, PhaseTo); } } } @@ -2369,6 +2424,7 @@ void FinalizePackageStoreContainerHeader(FContainerTargetSpec& ContainerTarget) FNameMapBuilder& NameMapBuilder = *ContainerTarget.NameMapBuilder; FCulturePackageMap& CulturePackageMap = ContainerTarget.Header.CulturePackageMap; TArray& PackageIds = ContainerTarget.Header.PackageIds; + TArray>& PackageRedirects = ContainerTarget.Header.PackageRedirects; int32 StoreTocSize = ContainerTarget.PackageCount * sizeof(FPackageStoreEntry); FLargeMemoryWriter StoreTocArchive(0, true); @@ -2407,6 +2463,12 @@ void FinalizePackageStoreContainerHeader(FContainerTargetSpec& ContainerTarget) CulturePackageMap.FindOrAdd(Package->Region).Emplace(Package->SourceGlobalPackageId, Package->GlobalPackageId); } + // Redirects + if (Package->RedirectedPackageId.IsValid()) + { + PackageRedirects.Add(MakeTuple(Package->RedirectedPackageId, Package->GlobalPackageId)); + } + // StoreEntries { uint64 ExportBundlesSize = TargetFile.HeaderSerialSize + Package->ExportsSerialSize; @@ -2440,20 +2502,26 @@ void FinalizePackageStoreContainerHeader(FContainerTargetSpec& ContainerTarget) PackageStoreArchive.Serialize(StoreDataArchive.GetData(), StoreDataArchive.TotalSize()); } -static void SerializeInitialLoad( +static void FinalizeInitialLoadMeta( FNameMapBuilder& GlobalNameMapBuilder, const FGlobalScriptObjects& GlobalScriptImports, FArchive& InitialLoadArchive) { - IOSTORE_CPU_SCOPE(SerializeInitialLoad); + IOSTORE_CPU_SCOPE(FinalizeInitialLoad); UE_LOG(LogIoStore, Display, TEXT("Finalizing initial load...")); int32 NumScriptObjects = GlobalScriptImports.Num(); InitialLoadArchive << NumScriptObjects; - for (const TPair& Pair : GlobalScriptImports) + TArray ScriptObjects; + GlobalScriptImports.GenerateValueArray(ScriptObjects ); + Algo::Sort(ScriptObjects, [](const FScriptObjectData& A, const FScriptObjectData& B) + { + return A.FullName < B.FullName; + }); + + for (const FScriptObjectData& ImportData : ScriptObjects) { - const FScriptObjectData& ImportData = Pair.Value; GlobalNameMapBuilder.MarkNameAsReferenced(ImportData.ObjectName); FScriptObjectEntry Entry; Entry.ObjectName = GlobalNameMapBuilder.MapName(ImportData.ObjectName).ToUnresolvedMinimalName(); @@ -2814,7 +2882,6 @@ static void FindScriptObjectsRecursive( }; static void CreateGlobalScriptObjects( - FNameMapBuilder& NameMapBuilder, FGlobalPackageData& GlobalPackageData, const ITargetPlatform* TargetPlatform) { @@ -2858,11 +2925,6 @@ static void CreateGlobalScriptObjects( FindScriptObjectsRecursive(GlobalPackageData, GlobalImportIndex, InnerObject, TargetPlatform, ExcludedObjectMarks); } } - - for (const TPair& Pair : GlobalPackageData.ScriptObjects) - { - NameMapBuilder.MarkNameAsReferenced(Pair.Value.ObjectName); - } } static void CreateGlobalImportsAndExports( @@ -3035,10 +3097,11 @@ static void ProcessLocalizedPackages( continue; } + check(!Package->RedirectedPackageId.IsValid()); if (Package->Name == Package->SourcePackageName) { UE_LOG(LogIoStore, Error, - TEXT("For culture '%s': Localized package '%s' (%d) should have a package name different from source name."), + TEXT("For culture '%s': Localized package '%s' (0x%llX) should have a package name different from source name."), *Package->Region, *Package->Name.ToString(), Package->GlobalPackageId.ValueForDebugging()) @@ -3050,7 +3113,7 @@ static void ProcessLocalizedPackages( { // no update or verification required UE_LOG(LogIoStore, Verbose, - TEXT("For culture '%s': Localized package '%s' (%d) is unique and does not override a source package."), + TEXT("For culture '%s': Localized package '%s' (0x%llX) is unique and does not override a source package."), *Package->Region, *Package->Name.ToString(), Package->GlobalPackageId.ValueForDebugging()); @@ -3065,7 +3128,7 @@ static void ProcessLocalizedPackages( if (Package->bIsLocalizedAndConformed) { - UE_LOG(LogIoStore, Verbose, TEXT("For culture '%s': Adding conformed localized package '%s' (%d) for '%s' (%d). ") + UE_LOG(LogIoStore, Verbose, TEXT("For culture '%s': Adding conformed localized package '%s' (0x%llX) for '%s' (0x%llX). ") TEXT("When loading the source package, it will be remapped to this localized package."), *Package->Region, *Package->Name.ToString(), @@ -3078,7 +3141,7 @@ static void ProcessLocalizedPackages( else { UE_LOG(LogIoStore, Display, - TEXT("For culture '%s': Localized package '%s' (%d) does not conform to source package '%s' (%d) due to mismatching public exports. ") + TEXT("For culture '%s': Localized package '%s' (0x%llX) does not conform to source package '%s' (0x%llX) due to mismatching public exports. ") TEXT("When loading the source package, it will never be remapped to this localized package."), *Package->Region, *Package->Name.ToString(), @@ -3100,7 +3163,7 @@ static void ProcessLocalizedPackages( OutSourceToLocalizedPackageMap.MultiFind(ImportedPackage, LocalizedPackages); for (FPackage* LocalizedPackage : LocalizedPackages) { - UE_LOG(LogIoStore, Verbose, TEXT("For package '%s' (%d): Adding localized imported package '%s' (%d)"), + UE_LOG(LogIoStore, Verbose, TEXT("For package '%s' (0x%llX): Adding localized imported package '%s' (0x%llX)"), *Package->Name.ToString(), Package->GlobalPackageId.ValueForDebugging(), *LocalizedPackage->Name.ToString(), @@ -3129,7 +3192,7 @@ static void ProcessLocalizedPackages( const FExportObjectData& SourceExportData = *GlobalPackageData.FindPublicExport(*SourceGlobalImportIndex); UE_LOG(LogIoStore, Verbose, - TEXT("For package '%s' (%d): Remap localized import %s to source import %s (in a conformed localized package)"), + TEXT("For package '%s' (0x%llX): Remap localized import %s to source import %s (in a conformed localized package)"), *Package->Name.ToString(), Package->GlobalPackageId.ValueForDebugging(), *Export->FullName, @@ -3138,7 +3201,7 @@ static void ProcessLocalizedPackages( else { UE_LOG(LogIoStore, Verbose, - TEXT("For package '%s' (%d): Skip remap for localized import %s") + TEXT("For package '%s' (0x%llX): Skip remap for localized import %s") TEXT(", either there is no source package or the localized package did not conform to it."), *Package->Name.ToString(), Package->GlobalPackageId.ValueForDebugging(), @@ -3279,11 +3342,11 @@ void InitializeContainerTargetsAndPackages( if (bIsMemoryMappedBulkData) { FString TmpFileName = FString(RelativeFileName.Len() - 8, GetData(RelativeFileName)) + TEXT(".ubulk"); - Package = FindOrAddPackage(*TmpFileName, Packages, PackageNameMap, PackageIdMap); + Package = FindOrAddPackage(Arguments, *TmpFileName, Packages, PackageNameMap, PackageIdMap); } else { - Package = FindOrAddPackage(*RelativeFileName, Packages, PackageNameMap, PackageIdMap); + Package = FindOrAddPackage(Arguments, *RelativeFileName, Packages, PackageNameMap, PackageIdMap); } if (Package) @@ -3606,7 +3669,7 @@ int32 CreateTarget(const FIoStoreArguments& Arguments, const FIoStoreWriterSetti FExportGraph ExportGraph(PackageAssetData.ObjectExports.Num(), PackageAssetData.PreloadDependencies.Num()); GlobalPackageData.Reserve(PackageAssetData.ObjectExports.Num()); - CreateGlobalScriptObjects(GlobalNameMapBuilder, GlobalPackageData, Arguments.TargetPlatform); + CreateGlobalScriptObjects(GlobalPackageData, Arguments.TargetPlatform); CreateGlobalImportsAndExports(Arguments, Packages, PackageIdMap, PackageAssetData, GlobalPackageData, ExportGraph); // Mapped import and exports are required before processing localization, and preload/postload arcs @@ -3678,18 +3741,6 @@ int32 CreateTarget(const FIoStoreArguments& Arguments, const FIoStoreWriterSetti } } - FLargeMemoryWriter InitialLoadArchive(0, true); - - { - IOSTORE_CPU_SCOPE(SerializeGlobalMetaData); - UE_LOG(LogIoStore, Display, TEXT("Serializing global meta data")); - - SerializeInitialLoad( - GlobalNameMapBuilder, - GlobalPackageData.ScriptObjects, - InitialLoadArchive); - } - UE_LOG(LogIoStore, Display, TEXT("Calculating hashes")); { IOSTORE_CPU_SCOPE(CalculateHashes); @@ -3973,9 +4024,19 @@ int32 CreateTarget(const FIoStoreArguments& Arguments, const FIoStoreWriterSetti FPlatformProcess::ReturnSynchEventToPool(TaskStartedEvent); } + uint64 InitialLoadSize = 0; if (GlobalIoStoreWriter) { - UE_LOG(LogIoStore, Display, TEXT("Saving initial load meta data to container file")); + FLargeMemoryWriter InitialLoadArchive(0, true); + FinalizeInitialLoadMeta( + GlobalNameMapBuilder, + GlobalPackageData.ScriptObjects, + InitialLoadArchive); + + InitialLoadSize = InitialLoadArchive.Tell(); + + UE_LOG(LogIoStore, Display, TEXT("Serializing global meta data")); + IOSTORE_CPU_SCOPE(SerializeInitialLoadMeta); FIoWriteOptions WriteOptions; WriteOptions.DebugName = TEXT("LoaderInitialLoadMeta"); const FIoStatus Status = GlobalIoStoreWriter->Append(CreateIoChunkId(0, 0, EIoChunkType::LoaderInitialLoadMeta), FIoBuffer(FIoBuffer::Wrap, InitialLoadArchive.GetData(), InitialLoadArchive.TotalSize()), WriteOptions); @@ -3997,6 +4058,7 @@ int32 CreateTarget(const FIoStoreArguments& Arguments, const FIoStoreWriterSetti TArray Hashes; SaveNameBatch(GlobalNameMapBuilder.GetNameMap(), /* out */ Names, /* out */ Hashes); + InitialLoadSize += Names.Num() + Hashes.Num(); GlobalNamesMB = Names.Num() >> 20; GlobalNameHashesMB = Hashes.Num() >> 20; @@ -4144,7 +4206,6 @@ int32 CreateTarget(const FIoStoreArguments& Arguments, const FIoStoreWriterSetti uint64 ImportedPackagesCount = 0; uint64 NoImportedPackagesCount = 0; uint64 PublicExportsCount = 0; - uint64 InitialLoadSize = InitialLoadArchive.Tell(); uint64 TotalExternalArcCount = 0; uint64 NameMapCount = 0; @@ -4371,17 +4432,7 @@ static bool ParsePakOrderFile(const TCHAR* FilePath, TMap& OutMap } uint64 Order = LineNumber; - FString OrderStr; - if (FParse::Token(OrderLinePtr, OrderStr, false)) - { - if (!OrderStr.IsNumeric()) - { - UE_LOG(LogIoStore, Error, TEXT("Invalid line in order file '%s'."), *OrderLine); - return false; - } - Order = FCString::Atoi64(*OrderStr); - } - + FName PackageFName(MoveTemp(PackageName)); if (!OutMap.Contains(PackageFName)) { @@ -4661,9 +4712,54 @@ int32 CreateIoStoreContainerFiles(const TCHAR* CmdLine) } } + if (FParse::Value(FCommandLine::Get(), TEXT("BasedOnReleaseVersionPath="), Arguments.BasedOnReleaseVersionPath)) + { + UE_LOG(LogIoStore, Display, TEXT("Based on release version path: '%s'"), *Arguments.BasedOnReleaseVersionPath); + } + if (FParse::Value(FCommandLine::Get(), TEXT("DLCFile="), Arguments.DLCPluginPath)) { + Arguments.DLCName = FPaths::GetBaseFilename(*Arguments.DLCPluginPath); + Arguments.bRemapPluginContentToGame = FParse::Param(FCommandLine::Get(), TEXT("RemapPluginContentToGame")); + UE_LOG(LogIoStore, Display, TEXT("DLC: '%s'"), *Arguments.DLCPluginPath); + UE_LOG(LogIoStore, Display, TEXT("Remapping plugin content to game: '%s'"), Arguments.bRemapPluginContentToGame ? TEXT("True") : TEXT("False")); + + if (Arguments.BasedOnReleaseVersionPath.IsEmpty()) + { + UE_LOG(LogIoStore, Error, TEXT("Based on release version path is needed for DLC")); + return -1; + } + + FString DevelopmentAssetRegistryPath = FPaths::Combine(Arguments.BasedOnReleaseVersionPath, TEXT("Metadata/DevelopmentAssetRegistry.bin")); + + bool bAssetRegistryLoaded = false; + FArrayReader SerializedAssetData; + if (FFileHelper::LoadFileToArray(SerializedAssetData, *DevelopmentAssetRegistryPath)) + { + FAssetRegistrySerializationOptions Options; + if (Arguments.ReleaseAssetRegistry.Serialize(SerializedAssetData, Options)) + { + UE_LOG(LogIoStore, Display, TEXT("Loaded asset registry '%s'"), *DevelopmentAssetRegistryPath); + bAssetRegistryLoaded = true; + + TArray PackageNames; + Arguments.ReleaseAssetRegistry.GetPackageNames(PackageNames); + Arguments.ReleasedPackages.PackageNames.Reserve(PackageNames.Num()); + Arguments.ReleasedPackages.PackageIdToName.Reserve(PackageNames.Num()); + + for (FName PackageName : PackageNames) + { + Arguments.ReleasedPackages.PackageNames.Add(PackageName); + Arguments.ReleasedPackages.PackageIdToName.Add(FPackageId::FromName(PackageName), PackageName); + } + } + } + + if (!bAssetRegistryLoaded) + { + UE_LOG(LogIoStore, Warning, TEXT("Failed to load Asset registry '%s'. Needed to verify DLC package names"), *DevelopmentAssetRegistryPath); + } } if (FParse::Value(FCommandLine::Get(), TEXT("CreateGlobalContainer="), Arguments.GlobalContainerPath)) @@ -4723,7 +4819,6 @@ int32 CreateIoStoreContainerFiles(const TCHAR* CmdLine) } UE_LOG(LogIoStore, Display, TEXT("Searching for cooked assets in folder '%s'"), *Arguments.CookedDir); - FCookedFileStatMap CookedFileStatMap; FCookedFileVisitor CookedFileVistor(Arguments.CookedFileStatMap, nullptr); IFileManager::Get().IterateDirectoryStatRecursively(*Arguments.CookedDir, CookedFileVistor); UE_LOG(LogIoStore, Display, TEXT("Found '%d' files"), Arguments.CookedFileStatMap.Num()); diff --git a/Engine/Source/Developer/LauncherServices/Private/Launcher/LauncherWorker.cpp b/Engine/Source/Developer/LauncherServices/Private/Launcher/LauncherWorker.cpp index 6c84ce40c39d..8297451d38ac 100644 --- a/Engine/Source/Developer/LauncherServices/Private/Launcher/LauncherWorker.cpp +++ b/Engine/Source/Developer/LauncherServices/Private/Launcher/LauncherWorker.cpp @@ -18,6 +18,7 @@ #include "Launcher/LauncherVerifyProfileTask.h" #include "PlatformInfo.h" #include "Misc/ConfigCacheIni.h" +#include "Profiles/LauncherProfile.h" #define LOCTEXT_NAMESPACE "LauncherWorker" @@ -260,7 +261,60 @@ static void AddDeviceToLaunchCommand(const FString& DeviceId, TSharedPtrGetTargetDeviceVariant(DeviceId); + FString Platform = DeviceProxy->GetTargetPlatformName(Variant); + + bool bCookedVulkan = false; + bool bCheckTargetedRHIs = false; + TArray TargetedShaderFormats; + + if (Platform.StartsWith(TEXT("Windows"))) + { + FConfigFile WindowsEngineSettings; + FConfigCacheIni::LoadLocalIniFile(WindowsEngineSettings, TEXT("Engine"), true, TEXT("Windows")); + + bCheckTargetedRHIs = true; + WindowsEngineSettings.GetArray(TEXT("/Script/WindowsTargetPlatform.WindowsTargetSettings"), TEXT("TargetedRHIs"), TargetedShaderFormats); + } + else if (Platform.StartsWith(TEXT("Linux"))) + { + FConfigFile LinuxEngineSettings; + FConfigCacheIni::LoadLocalIniFile(LinuxEngineSettings, TEXT("Engine"), true, TEXT("Linux")); + + bCheckTargetedRHIs = true; + LinuxEngineSettings.GetArray(TEXT("/Script/LinuxTargetPlatform.LinuxTargetSettings"), TEXT("TargetedRHIs"), TargetedShaderFormats); + } + else if (Platform.StartsWith(TEXT("Android"))) + { + FConfigFile AndroidEngineSettings; + FConfigCacheIni::LoadLocalIniFile(AndroidEngineSettings, TEXT("Engine"), true, TEXT("Android")); + + bool bAndroidSupportsVulkan, bAndroidSupportsVulkanSM5; + AndroidEngineSettings.GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bSupportsVulkan"), bAndroidSupportsVulkan); + AndroidEngineSettings.GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bSupportsVulkanSM5"), bAndroidSupportsVulkanSM5); + bCookedVulkan = bAndroidSupportsVulkan || bAndroidSupportsVulkanSM5; + bCheckTargetedRHIs = false; + } + + if (bCheckTargetedRHIs) + { + for (const FString& ShaderFormat : TargetedShaderFormats) + { + if (ShaderFormat.StartsWith(TEXT("SF_VULKAN_"))) + { + bCookedVulkan = true; + } + } + } + + if (bCookedVulkan) + { + RoleCommands += TEXT(" -vulkan"); + } + else + { + UE_LOG(LogLauncherProfile, Warning, TEXT("The editor is running on Vulkan, but Vulkan is not enabled for launch platform '%s'. Launching process with the default RHI."), *Platform); + } } } diff --git a/Engine/Source/Developer/Linux/LinuxTargetPlatform/Private/LinuxTargetPlatform.h b/Engine/Source/Developer/Linux/LinuxTargetPlatform/Private/LinuxTargetPlatform.h index 7fefd66f8415..e41507ef14ef 100644 --- a/Engine/Source/Developer/Linux/LinuxTargetPlatform/Private/LinuxTargetPlatform.h +++ b/Engine/Source/Developer/Linux/LinuxTargetPlatform/Private/LinuxTargetPlatform.h @@ -297,7 +297,7 @@ public: if (!TProperties::IsServerOnly()) { // just use the standard texture format name for this texture - GetDefaultTextureFormatNamePerLayer(OutFormats.AddDefaulted_GetRef(), this, InTexture, EngineSettings, false); + GetDefaultTextureFormatNamePerLayer(OutFormats.AddDefaulted_GetRef(), this, InTexture, EngineSettings, true); } } @@ -307,7 +307,7 @@ public: if (!TProperties::IsServerOnly()) { // just use the standard texture format name for this texture - GetAllDefaultTextureFormats(this, OutFormats, false); + GetAllDefaultTextureFormats(this, OutFormats, true); } } diff --git a/Engine/Source/Developer/Mac/MacTargetPlatform/Private/MacTargetPlatformModule.cpp b/Engine/Source/Developer/Mac/MacTargetPlatform/Private/MacTargetPlatformModule.cpp index 25fca15dda09..422382172ae7 100644 --- a/Engine/Source/Developer/Mac/MacTargetPlatform/Private/MacTargetPlatformModule.cpp +++ b/Engine/Source/Developer/Mac/MacTargetPlatform/Private/MacTargetPlatformModule.cpp @@ -48,7 +48,7 @@ public: int32 Value = 1; GConfig->GetInt(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("MaxShaderLanguageVersion"), Value, GEngineIni); - TargetSettings->MaxShaderLanguageVersion = FMath::Max(Value, 3); + TargetSettings->MaxShaderLanguageVersion = FMath::Max(Value, 4); if (!GConfig->GetBool(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("UseFastIntrinsics"), TargetSettings->UseFastIntrinsics, GEngineIni)) { diff --git a/Engine/Source/Developer/MaterialBaking/Private/ExportMaterialProxy.h b/Engine/Source/Developer/MaterialBaking/Private/ExportMaterialProxy.h index 46875d6cc927..1d5cf6e09f89 100644 --- a/Engine/Source/Developer/MaterialBaking/Private/ExportMaterialProxy.h +++ b/Engine/Source/Developer/MaterialBaking/Private/ExportMaterialProxy.h @@ -228,7 +228,7 @@ public: , PropertyToCompile(InPropertyToCompile) , bSynchronousCompilation(bInSynchronousCompilation) { - SetQualityLevelProperties(EMaterialQualityLevel::High, false, GMaxRHIFeatureLevel); + SetQualityLevelProperties(GMaxRHIFeatureLevel); Material = InMaterialInterface->GetMaterial(); ReferencedTextures = InMaterialInterface->GetReferencedTextures(); diff --git a/Engine/Source/Developer/MaterialBaking/Private/MaterialBakingModule.cpp b/Engine/Source/Developer/MaterialBaking/Private/MaterialBakingModule.cpp index 60924917ae29..67e9c4824f6d 100644 --- a/Engine/Source/Developer/MaterialBaking/Private/MaterialBakingModule.cpp +++ b/Engine/Source/Developer/MaterialBaking/Private/MaterialBakingModule.cpp @@ -504,9 +504,11 @@ void FMaterialBakingModule::BakeMaterials(const TArray& Material SortElement.RenderBatchArray.Empty(); FTexture2DRHIRef StagingBufferRef = StagingBufferPool.CreateStagingBuffer_RenderThread(RHICmdList, RenderTargetResource->GetSizeX(), RenderTargetResource->GetSizeY(), PerPropertyFormat[Property]); + FGPUFenceRHIRef GPUFence = RHICreateGPUFence(TEXT("MaterialBackingFence")); FResolveRect Rect(0, 0, RenderTargetResource->GetSizeX(), RenderTargetResource->GetSizeY()); RHICmdList.CopyToResolveTarget(RenderTargetResource->GetRenderTargetTexture(), StagingBufferRef, FResolveParams(Rect)); + RHICmdList.WriteGPUFence(GPUFence); // Prepare a lambda for final processing that will be executed asynchronously NumTasks++; @@ -568,13 +570,13 @@ void FMaterialBakingModule::BakeMaterials(const TArray& Material // Generate a texture reading command that will be executed once it reaches the end of the pipeline PipelineContext[PipelineIndex].ReadCommand = - [FinalProcessing_AnyThread, StagingBufferRef = MoveTemp(StagingBufferRef)](FRHICommandListImmediate& RHICmdList) mutable + [FinalProcessing_AnyThread, StagingBufferRef = MoveTemp(StagingBufferRef), GPUFence = MoveTemp(GPUFence)](FRHICommandListImmediate& RHICmdList) mutable { TRACE_CPUPROFILER_EVENT_SCOPE(MapAndEnqueue) void * Data = nullptr; int32 Width; int32 Height; - RHICmdList.MapStagingSurface(StagingBufferRef, Data, Width, Height); + RHICmdList.MapStagingSurface(StagingBufferRef, GPUFence.GetReference(), Data, Width, Height); // Schedule the copy and processing on another thread to free up the render thread as much as possible Async( diff --git a/Engine/Source/Developer/MaterialUtilities/Private/MaterialUtilities.cpp b/Engine/Source/Developer/MaterialUtilities/Private/MaterialUtilities.cpp index 37313075fb48..85eb1f14b5fa 100644 --- a/Engine/Source/Developer/MaterialUtilities/Private/MaterialUtilities.cpp +++ b/Engine/Source/Developer/MaterialUtilities/Private/MaterialUtilities.cpp @@ -139,11 +139,11 @@ UMaterialInterface* FMaterialUtilities::CreateProxyMaterialAndTextures(UPackage* if (Property == MP_BaseColor || Property == MP_EmissiveColor) { - Material->SetVectorParameterValueEditorOnly(ParameterInfo, ColorData[0].ReinterpretAsLinear()); + Material->SetVectorParameterValueEditorOnly(ParameterInfo, FLinearColor::FromSRGBColor(ColorData[0])); } else { - Material->SetScalarParameterValueEditorOnly(ParameterInfo, ColorData[0].ReinterpretAsLinear().R); + Material->SetScalarParameterValueEditorOnly(ParameterInfo, FLinearColor::FromSRGBColor(ColorData[0]).R); } } } @@ -401,7 +401,7 @@ public: FExportMaterialProxy() : FMaterial() { - SetQualityLevelProperties(EMaterialQualityLevel::High, false, GMaxRHIFeatureLevel); + SetQualityLevelProperties(GMaxRHIFeatureLevel); } FExportMaterialProxy(UMaterialInterface* InMaterialInterface, EMaterialProperty InPropertyToCompile) @@ -409,7 +409,7 @@ public: , MaterialInterface(InMaterialInterface) , PropertyToCompile(InPropertyToCompile) { - SetQualityLevelProperties(EMaterialQualityLevel::High, false, GMaxRHIFeatureLevel); + SetQualityLevelProperties(GMaxRHIFeatureLevel); Material = InMaterialInterface->GetMaterial(); ReferencedTextures = InMaterialInterface->GetReferencedTextures(); FPlatformMisc::CreateGuid(Id); diff --git a/Engine/Source/Developer/MeshMergeUtilities/Private/ProxyGenerationProcessor.cpp b/Engine/Source/Developer/MeshMergeUtilities/Private/ProxyGenerationProcessor.cpp index 5591157f685c..a0c596c543cf 100644 --- a/Engine/Source/Developer/MeshMergeUtilities/Private/ProxyGenerationProcessor.cpp +++ b/Engine/Source/Developer/MeshMergeUtilities/Private/ProxyGenerationProcessor.cpp @@ -9,6 +9,7 @@ #include "IMeshReductionManagerModule.h" #include "Modules/ModuleManager.h" #include "StaticMeshAttributes.h" +#include "Stats/Stats.h" #if WITH_EDITOR #include "Editor.h" @@ -63,6 +64,8 @@ void FProxyGenerationProcessor::AddProxyJob(FGuid InJobGuid, FMergeCompleteData* bool FProxyGenerationProcessor::Tick(float DeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_FProxyGenerationProcessor_Tick); + FScopeLock Lock(&StateLock); for (const auto& Entry : ToProcessJobDataMap) { diff --git a/Engine/Source/Developer/OutputLog/Private/SDeviceOutputLog.cpp b/Engine/Source/Developer/OutputLog/Private/SDeviceOutputLog.cpp index bb2c9aba19fd..f169b3830dca 100644 --- a/Engine/Source/Developer/OutputLog/Private/SDeviceOutputLog.cpp +++ b/Engine/Source/Developer/OutputLog/Private/SDeviceOutputLog.cpp @@ -310,6 +310,7 @@ void SDeviceOutputLog::OnDeviceSelectionChanged(FTargetDeviceEntryPtr DeviceEntr { CurrentDeviceOutputPtr = PinnedPtr->CreateDeviceOutputRouter(this); } + OnSelectedDeviceChangedDelegate.ExecuteIfBound(GetSelectedTargetDevice()); } } diff --git a/Engine/Source/Developer/OutputLog/Private/SDeviceOutputLog.h b/Engine/Source/Developer/OutputLog/Private/SDeviceOutputLog.h index 3acb455f4721..39588be78d47 100644 --- a/Engine/Source/Developer/OutputLog/Private/SDeviceOutputLog.h +++ b/Engine/Source/Developer/OutputLog/Private/SDeviceOutputLog.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "Delegates/Delegate.h" #include "Misc/OutputDeviceRedirector.h" #include "Widgets/SWidget.h" #include "Widgets/DeclarativeSyntaxSupport.h" @@ -21,6 +22,8 @@ struct FTargetDeviceEntry typedef TSharedPtr FTargetDeviceEntryPtr; +DECLARE_DELEGATE_OneParam(FSelectedTargetDeviceChangedDelegate, ITargetDevicePtr); + class SDeviceOutputLog : public SOutputLog { public: @@ -40,6 +43,7 @@ public: */ void Construct( const FArguments& InArgs ); + FSelectedTargetDeviceChangedDelegate& OnSelectedDeviceChanged() { return OnSelectedDeviceChangedDelegate; } ITargetDevicePtr GetSelectedTargetDevice() const; protected: @@ -81,5 +85,7 @@ private: FCriticalSection BufferedLinesSynch; TArray BufferedLines; + FSelectedTargetDeviceChangedDelegate OnSelectedDeviceChangedDelegate; + bool bAutoSelectDevice; }; diff --git a/Engine/Source/Developer/PakFileUtilities/Private/PakFileUtilities.cpp b/Engine/Source/Developer/PakFileUtilities/Private/PakFileUtilities.cpp index 2e819142a91f..3e4d4453934e 100644 --- a/Engine/Source/Developer/PakFileUtilities/Private/PakFileUtilities.cpp +++ b/Engine/Source/Developer/PakFileUtilities/Private/PakFileUtilities.cpp @@ -1917,7 +1917,7 @@ void VerifyIndexesMatch(TArray& EntryList, FPakFile::FDirectoryIn check(false); continue; } - const FPakEntryLocation* PathHashLocation = FPakFile::FindLocationFromIndex(FullPath, MountPoint, PathHashIndex, PathHashSeed); + const FPakEntryLocation* PathHashLocation = FPakFile::FindLocationFromIndex(FullPath, MountPoint, PathHashIndex, PathHashSeed, Info.Version); if (!PathHashLocation) { check(false); @@ -2526,7 +2526,8 @@ bool CreatePakFile(const TCHAR* Filename, TArray& FilesToAdd, con return Index[NextIndex++]; }; - FPakFile::EncodePakEntriesIntoIndex(Index.Num(), ReadNextEntry, Filename, Info, MountPoint, NumEncodedEntries, NumDeletedEntries, &PathHashSeed, &DirectoryIndex, &PathHashIndex, EncodedPakEntries, NonEncodableEntries, &CollisionDetection); + FPakFile::EncodePakEntriesIntoIndex(Index.Num(), ReadNextEntry, Filename, Info, MountPoint, NumEncodedEntries, NumDeletedEntries, &PathHashSeed, + &DirectoryIndex, &PathHashIndex, EncodedPakEntries, NonEncodableEntries, &CollisionDetection, FPakInfo::PakFile_Version_Latest); VerifyIndexesMatch(Index, DirectoryIndex, PathHashIndex, PathHashSeed, MountPoint, EncodedPakEntries, NonEncodableEntries, NumEncodedEntries, NumDeletedEntries, Info); // We write one PrimaryIndex and two SecondaryIndexes to the Pak File diff --git a/Engine/Source/Developer/RigVMDeveloper/Private/RigVMModel/RigVMController.cpp b/Engine/Source/Developer/RigVMDeveloper/Private/RigVMModel/RigVMController.cpp index 72e9e8264bbf..20e44b0a6f46 100644 --- a/Engine/Source/Developer/RigVMDeveloper/Private/RigVMModel/RigVMController.cpp +++ b/Engine/Source/Developer/RigVMDeveloper/Private/RigVMModel/RigVMController.cpp @@ -3587,11 +3587,17 @@ int32 URigVMController::DetachLinksFromPinObjects() URigVMPin* SourcePin = Link->GetSourcePin(); URigVMPin* TargetPin = Link->GetTargetPin(); - Link->SourcePinPath = SourcePin->GetPinPath(); - Link->TargetPinPath = TargetPin->GetPinPath(); + if (SourcePin) + { + Link->SourcePinPath = SourcePin->GetPinPath(); + SourcePin->Links.Remove(Link); + } - SourcePin->Links.Remove(Link); - TargetPin->Links.Remove(Link); + if (TargetPin) + { + Link->TargetPinPath = TargetPin->GetPinPath(); + TargetPin->Links.Remove(Link); + } Link->SourcePin = nullptr; Link->TargetPin = nullptr; @@ -3638,6 +3644,16 @@ int32 URigVMController::ReattachLinksToPinObjects() return NewLinks.Num(); } +void URigVMController::RemoveStaleNodes() +{ + if (!IsValidGraph()) + { + return; + } + + Graph->Nodes.Remove(nullptr); +} + #if WITH_EDITOR void URigVMController::RepopulatePinsOnNode(URigVMNode* InNode) diff --git a/Engine/Source/Developer/RigVMDeveloper/Private/RigVMModel/RigVMGraph.cpp b/Engine/Source/Developer/RigVMDeveloper/Private/RigVMModel/RigVMGraph.cpp index ef45cf9b63df..d7b38e308906 100644 --- a/Engine/Source/Developer/RigVMDeveloper/Private/RigVMModel/RigVMGraph.cpp +++ b/Engine/Source/Developer/RigVMDeveloper/Private/RigVMModel/RigVMGraph.cpp @@ -50,6 +50,11 @@ URigVMNode* URigVMGraph::FindNodeByName(const FName& InNodeName) const { for (URigVMNode* Node : Nodes) { + if (Node == nullptr) + { + continue; + } + if (Node->GetFName() == InNodeName) { return Node; diff --git a/Engine/Source/Developer/RigVMDeveloper/Public/RigVMModel/RigVMController.h b/Engine/Source/Developer/RigVMDeveloper/Public/RigVMModel/RigVMController.h index 51bb74f31684..981200456acf 100644 --- a/Engine/Source/Developer/RigVMDeveloper/Public/RigVMModel/RigVMController.h +++ b/Engine/Source/Developer/RigVMDeveloper/Public/RigVMModel/RigVMController.h @@ -381,6 +381,9 @@ public: int32 DetachLinksFromPinObjects(); int32 ReattachLinksToPinObjects(); + // Removes nodes which went stale. + void RemoveStaleNodes(); + #if WITH_EDITOR void RepopulatePinsOnNode(URigVMNode* InNode); #endif diff --git a/Engine/Source/Developer/ScreenShotComparison/Private/Widgets/SScreenComparisonRow.cpp b/Engine/Source/Developer/ScreenShotComparison/Private/Widgets/SScreenComparisonRow.cpp index 291c8e36a872..82b1b4c8fe5c 100644 --- a/Engine/Source/Developer/ScreenShotComparison/Private/Widgets/SScreenComparisonRow.cpp +++ b/Engine/Source/Developer/ScreenShotComparison/Private/Widgets/SScreenComparisonRow.cpp @@ -321,6 +321,18 @@ bool SScreenComparisonRow::CanUseSourceControl() const return ISourceControlModule::Get().IsEnabled(); } +FText SScreenComparisonRow::GetAddNewButtonTooltip() const +{ + if (ISourceControlModule::Get().IsEnabled()) + { + return LOCTEXT("AddNewToolTip", "Add new ground truth image to source control."); + } + else + { + return LOCTEXT("AddNewToolTip_Disabled", "Cannot add new ground truth image. Please connect to source control."); + } +} + bool SScreenComparisonRow::IsComparingAgainstPlatformFallback() const { const FImageComparisonResult& Comparison = Model->Report.GetComparisonResult(); @@ -378,6 +390,7 @@ TSharedRef SScreenComparisonRow::BuildAddedView() SNew(SButton) .IsEnabled(this, &SScreenComparisonRow::CanUseSourceControl) .Text(LOCTEXT("AddNew", "Add New!")) + .ToolTipText(this, &SScreenComparisonRow::GetAddNewButtonTooltip) .OnClicked(this, &SScreenComparisonRow::AddNew) ] diff --git a/Engine/Source/Developer/ScreenShotComparison/Private/Widgets/SScreenComparisonRow.h b/Engine/Source/Developer/ScreenShotComparison/Private/Widgets/SScreenComparisonRow.h index c6d71d53bdfd..51ad240c3ba0 100644 --- a/Engine/Source/Developer/ScreenShotComparison/Private/Widgets/SScreenComparisonRow.h +++ b/Engine/Source/Developer/ScreenShotComparison/Private/Widgets/SScreenComparisonRow.h @@ -49,6 +49,7 @@ private: TSharedRef BuildAddedView(); TSharedRef BuildComparisonPreview(); + FText GetAddNewButtonTooltip() const; bool CanAddNew() const; FReply AddNew(); diff --git a/Engine/Source/Developer/Settings/Private/SettingsSection.cpp b/Engine/Source/Developer/Settings/Private/SettingsSection.cpp index eceefa0ce865..9d53f62e5cbf 100644 --- a/Engine/Source/Developer/Settings/Private/SettingsSection.cpp +++ b/Engine/Source/Developer/Settings/Private/SettingsSection.cpp @@ -55,7 +55,7 @@ bool FSettingsSection::CanImport() const bool FSettingsSection::CanResetDefaults() const { - return (ResetDefaultsDelegate.IsBound() || (SettingsObject.IsValid() && SettingsObject->GetClass()->HasAnyClassFlags(CLASS_Config) && !SettingsObject->GetClass()->HasAnyClassFlags(CLASS_DefaultConfig | CLASS_GlobalUserConfig))); + return (ResetDefaultsDelegate.IsBound() || (SettingsObject.IsValid() && SettingsObject->GetClass()->HasAnyClassFlags(CLASS_Config) && !SettingsObject->GetClass()->HasAnyClassFlags(CLASS_DefaultConfig | CLASS_GlobalUserConfig | CLASS_ProjectUserConfig))); } @@ -67,7 +67,7 @@ bool FSettingsSection::CanSave() const bool FSettingsSection::CanSaveDefaults() const { - return (SaveDefaultsDelegate.IsBound() || (SettingsObject.IsValid() && SettingsObject->GetClass()->HasAnyClassFlags(CLASS_Config) && !SettingsObject->GetClass()->HasAnyClassFlags(CLASS_DefaultConfig | CLASS_GlobalUserConfig))); + return (SaveDefaultsDelegate.IsBound() || (SettingsObject.IsValid() && SettingsObject->GetClass()->HasAnyClassFlags(CLASS_Config) && !SettingsObject->GetClass()->HasAnyClassFlags(CLASS_DefaultConfig | CLASS_GlobalUserConfig | CLASS_ProjectUserConfig))); } @@ -173,7 +173,7 @@ bool FSettingsSection::ResetDefaults() return ResetDefaultsDelegate.Execute(); } - if (SettingsObject.IsValid() && SettingsObject->GetClass()->HasAnyClassFlags(CLASS_Config) && !SettingsObject->GetClass()->HasAnyClassFlags(CLASS_DefaultConfig | CLASS_GlobalUserConfig)) + if (SettingsObject.IsValid() && SettingsObject->GetClass()->HasAnyClassFlags(CLASS_Config) && !SettingsObject->GetClass()->HasAnyClassFlags(CLASS_DefaultConfig | CLASS_GlobalUserConfig | CLASS_ProjectUserConfig)) { FString ConfigName = SettingsObject->GetClass()->GetConfigName(); @@ -224,6 +224,10 @@ bool FSettingsSection::Save() { SettingsObject->UpdateGlobalUserConfigFile(); } + else if (SettingsObject->GetClass()->HasAnyClassFlags(CLASS_ProjectUserConfig)) + { + SettingsObject->UpdateProjectUserConfigFile(); + } else { SettingsObject->SaveConfig(); diff --git a/Engine/Source/Developer/ShaderFormatVectorVM/Private/VectorVMShaderCompiler.cpp b/Engine/Source/Developer/ShaderFormatVectorVM/Private/VectorVMShaderCompiler.cpp index e7a90ac19791..e7ac651a855e 100644 --- a/Engine/Source/Developer/ShaderFormatVectorVM/Private/VectorVMShaderCompiler.cpp +++ b/Engine/Source/Developer/ShaderFormatVectorVM/Private/VectorVMShaderCompiler.cpp @@ -184,10 +184,12 @@ bool CompileShader_VectorVM(const FShaderCompilerInput& Input, FShaderCompilerOu TArray OutputByLines; PreprocessedShader.ParseIntoArrayLines(OutputByLines, false); + UE_LOG(LogVectorVMShaderCompiler, Warning, TEXT("Warnings while processing %s"), *Input.DebugGroupName); + FString OutputHlsl; for (int32 i = 0; i < OutputByLines.Num(); i++) { - UE_LOG(LogVectorVMShaderCompiler, Warning, TEXT("/*%d*/%s"), i, *OutputByLines[i]); + UE_LOG(LogVectorVMShaderCompiler, Display, TEXT("/*%d*/%s"), i, *OutputByLines[i]); } } else diff --git a/Engine/Source/Developer/ShaderFormatVectorVM/Private/ir_vm_gen_bytecode_visitor.cpp b/Engine/Source/Developer/ShaderFormatVectorVM/Private/ir_vm_gen_bytecode_visitor.cpp index 7e59b618d901..6942c36cc2ca 100644 --- a/Engine/Source/Developer/ShaderFormatVectorVM/Private/ir_vm_gen_bytecode_visitor.cpp +++ b/Engine/Source/Developer/ShaderFormatVectorVM/Private/ir_vm_gen_bytecode_visitor.cpp @@ -1388,6 +1388,13 @@ struct op_external_func : public op_base bool should_cull_function(unsigned int op_idx) { + FString FuncName = FString::Printf(TEXT("%s"), ANSI_TO_TCHAR(sig->function_name())); + bool bPotentiallyHasSideEffects = FuncName.Contains(TEXT("_UEImpureCall_")); + if (bPotentiallyHasSideEffects) + { + return false; + } + //If the outputs of this call are never used after the current op then we cull this function. bool func_is_used = false; for (variable_info_node* output : outputs) diff --git a/Engine/Source/Developer/ShaderFormatVectorVM/Private/ir_vm_scalarize_visitor.cpp b/Engine/Source/Developer/ShaderFormatVectorVM/Private/ir_vm_scalarize_visitor.cpp index bbb2161c26f2..f5a78e33772e 100644 --- a/Engine/Source/Developer/ShaderFormatVectorVM/Private/ir_vm_scalarize_visitor.cpp +++ b/Engine/Source/Developer/ShaderFormatVectorVM/Private/ir_vm_scalarize_visitor.cpp @@ -393,9 +393,10 @@ class ir_scalarize_visitor2 : public ir_hierarchical_visitor write_mask = assign->write_mask == 0 ? 0xFFFFFFFF : assign->write_mask; ir_assignment* comp_assign = NULL; - for (unsigned comp_idx = 0; comp_idx < num_components; ++comp_idx) + unsigned comp_idx = 0; + for (unsigned write_index = 0; write_index < num_components; ++write_index) { - if (is_struct || (write_mask & 0x1)) + if (is_struct || (write_mask & (1 << write_index))) { if (comp_assign) { @@ -419,7 +420,7 @@ class ir_scalarize_visitor2 : public ir_hierarchical_visitor } else { - comp_assign->write_mask = 1 << dest_component; + comp_assign->write_mask = 1 << write_index; } curr_rval = nullptr; @@ -428,8 +429,9 @@ class ir_scalarize_visitor2 : public ir_hierarchical_visitor { comp_assign->rhs = curr_rval; } + + ++comp_idx; } - write_mask = write_mask >> 1; } if (comp_assign) diff --git a/Engine/Source/Developer/SkeletalMeshUtilitiesCommon/Private/LODUtilities.cpp b/Engine/Source/Developer/SkeletalMeshUtilitiesCommon/Private/LODUtilities.cpp index 7260e8eb574e..f3a91001307d 100644 --- a/Engine/Source/Developer/SkeletalMeshUtilitiesCommon/Private/LODUtilities.cpp +++ b/Engine/Source/Developer/SkeletalMeshUtilitiesCommon/Private/LODUtilities.cpp @@ -458,6 +458,13 @@ struct FTargetMatch { float BarycentricWeight[3]; //The weight we use to interpolate the TARGET data uint32 Indices[3]; //BASE Index of the triangle vertice + + //Default constructor + FTargetMatch() + { + BarycentricWeight[0] = BarycentricWeight[1] = BarycentricWeight[2] = 0.0f; + Indices[0] = Indices[1] = Indices[2] = INDEX_NONE; + } }; void ProjectTargetOnBase(const TArray& BaseVertices, const TArray>& PerSectionBaseTriangleIndices, @@ -934,7 +941,7 @@ void FLODUtilities::ApplyMorphTargetsToLOD(USkeletalMesh* SkeletalMesh, int32 So } //Every target vertices match a Base LOD triangle, we also want the barycentric weight of the triangle match. All this done using the UVs TArray TargetMatchData; - TargetMatchData.AddUninitialized(TargetVertices.Num()); + TargetMatchData.AddDefaulted(TargetVertices.Num()); //Match all target vertices to a Base triangle Using UVs. ProjectTargetOnBase(BaseVertices, BaseTriangleIndices, TargetMatchData, TargetLODModel.Sections, TargetSectionMatchBaseIndex, *SkeletalMesh->GetName()); //Helper to retrieve the FMorphTargetDelta from the BaseIndex diff --git a/Engine/Source/Developer/TargetPlatform/Private/TargetPlatformBase.cpp b/Engine/Source/Developer/TargetPlatform/Private/TargetPlatformBase.cpp index 537b7bd64a7a..64ac949b4d01 100644 --- a/Engine/Source/Developer/TargetPlatform/Private/TargetPlatformBase.cpp +++ b/Engine/Source/Developer/TargetPlatform/Private/TargetPlatformBase.cpp @@ -54,6 +54,11 @@ bool FTargetPlatformBase::UsesRayTracing() const return CVar ? (CVar->GetInt() != 0) : false; } +bool FTargetPlatformBase::ForcesSimpleSkyDiffuse() const +{ + return false; +} + float FTargetPlatformBase::GetDownSampleMeshDistanceFieldDivider() const { return 1.0f; diff --git a/Engine/Source/Developer/TargetPlatform/Public/Common/TargetPlatformBase.h b/Engine/Source/Developer/TargetPlatform/Public/Common/TargetPlatformBase.h index 727d9b02c728..deea1eecafc5 100644 --- a/Engine/Source/Developer/TargetPlatform/Public/Common/TargetPlatformBase.h +++ b/Engine/Source/Developer/TargetPlatform/Public/Common/TargetPlatformBase.h @@ -55,6 +55,8 @@ public: TARGETPLATFORM_API virtual bool UsesRayTracing() const override; + TARGETPLATFORM_API virtual bool ForcesSimpleSkyDiffuse() const override; + TARGETPLATFORM_API virtual float GetDownSampleMeshDistanceFieldDivider() const override; TARGETPLATFORM_API virtual int32 GetHeightFogModeForOpaque() const override; diff --git a/Engine/Source/Developer/TargetPlatform/Public/Interfaces/ITargetPlatform.h b/Engine/Source/Developer/TargetPlatform/Public/Interfaces/ITargetPlatform.h index 902826d7bad6..998422d45b68 100644 --- a/Engine/Source/Developer/TargetPlatform/Public/Interfaces/ITargetPlatform.h +++ b/Engine/Source/Developer/TargetPlatform/Public/Interfaces/ITargetPlatform.h @@ -422,6 +422,11 @@ public: */ virtual bool UsesRayTracing() const = 0; + /** + * Gets whether the platform will use SH2 instead of SH3 for sky irradiance. + */ + virtual bool ForcesSimpleSkyDiffuse() const = 0; + /** * Gets down sample mesh distance field divider. * diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/LoadTimeTraceAnalysis.cpp b/Engine/Source/Developer/TraceServices/Private/Analyzers/LoadTimeTraceAnalysis.cpp index 04802637538f..0ed1ce60d085 100644 --- a/Engine/Source/Developer/TraceServices/Private/Analyzers/LoadTimeTraceAnalysis.cpp +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/LoadTimeTraceAnalysis.cpp @@ -357,7 +357,7 @@ bool FAsyncLoadingTraceAnalyzer::OnEvent(uint16 RouteId, EStyle Style, const FOn FAsyncPackageState* ImportedAsyncPackage = ActiveAsyncPackagesMap.FindRef(ImportedAsyncPackagePtr); if (AsyncPackage && ImportedAsyncPackage) { - if (ensure(AsyncPackage->Request)) + if (AsyncPackage->Request) { PackageRequestAssociation(Context, ImportedAsyncPackage, AsyncPackage->Request); } diff --git a/Engine/Source/Developer/Windows/ShaderFormatD3D/Private/D3DShaderCompiler.inl b/Engine/Source/Developer/Windows/ShaderFormatD3D/Private/D3DShaderCompiler.inl index 748dc145517b..0b6d9aae3280 100644 --- a/Engine/Source/Developer/Windows/ShaderFormatD3D/Private/D3DShaderCompiler.inl +++ b/Engine/Source/Developer/Windows/ShaderFormatD3D/Private/D3DShaderCompiler.inl @@ -12,6 +12,7 @@ protected: FString DumpDisasmFilename; FString BatchBaseFilename; FString DumpDebugInfoPath; + bool bEnable16BitTypes = false; bool bDump = false; TArray ExtraArguments; @@ -20,12 +21,14 @@ public: FDxcArguments(const FString& InEntryPoint, const TCHAR* InShaderProfile, const FString& InExports, const FString& InDumpDebugInfoPath, // Optional, empty when not dumping shader debug info const FString& InBaseFilename, + bool bInEnable16BitTypes, bool bKeepDebugInfo, uint32 D3DCompileFlags, uint32 AutoBindingSpace = ~0u) : ShaderProfile(InShaderProfile) , EntryPoint(InEntryPoint) , Exports(InExports) , DumpDebugInfoPath(InDumpDebugInfoPath) + , bEnable16BitTypes(bInEnable16BitTypes) { BatchBaseFilename = FPaths::GetBaseFilename(InBaseFilename); @@ -117,10 +120,12 @@ public: bKeepDebugInfo = true; } - checkf(D3DCompileFlags == 0, TEXT("Unhandled shader compiler flags 0x%x!"), D3DCompileFlags); + if (bEnable16BitTypes) + { + ExtraArguments.Add(L"/enable-16bit-types"); + } - ExtraArguments.Add(L"-HV"); - ExtraArguments.Add(L"2016"); + checkf(D3DCompileFlags == 0, TEXT("Unhandled shader compiler flags 0x%x!"), D3DCompileFlags); ExtraArguments.Add(L"/Zss"); ExtraArguments.Add(L"/Qembed_debug"); diff --git a/Engine/Source/Developer/Windows/ShaderFormatD3D/Private/D3DShaderCompilerDXC.cpp b/Engine/Source/Developer/Windows/ShaderFormatD3D/Private/D3DShaderCompilerDXC.cpp index b8cd5df7d7f7..4585537635c4 100644 --- a/Engine/Source/Developer/Windows/ShaderFormatD3D/Private/D3DShaderCompilerDXC.cpp +++ b/Engine/Source/Developer/Windows/ShaderFormatD3D/Private/D3DShaderCompilerDXC.cpp @@ -262,8 +262,10 @@ static bool RemoveContainerReflection(dxc::DxcDllSupport& DxcDllHelper, TRefCoun VERIFYHRESULT(DxcDllHelper.CreateInstance(CLSID_DxcContainerBuilder, Builder.GetInitReference())); VERIFYHRESULT(Builder->Load(Dxil)); - bool bRemoved = false; - if (SUCCEEDED(Builder->RemovePart(DXC_PART_PDB)) || SUCCEEDED(Builder->RemovePart(DXC_PART_REFLECTION_DATA))) + // Try and remove both the PDB & Reflection Data + bool bPDBRemoved = SUCCEEDED(Builder->RemovePart(DXC_PART_PDB)); + bool bReflectionDataRemoved = SUCCEEDED(Builder->RemovePart(DXC_PART_REFLECTION_DATA)); + if (bPDBRemoved || bReflectionDataRemoved) { VERIFYHRESULT(Builder->SerializeContainer(Result.GetInitReference())); if (SUCCEEDED(Result->GetResult(StrippedDxil.GetInitReference()))) @@ -624,6 +626,8 @@ bool CompileAndProcessD3DShaderDXC(FString& PreprocessedShaderSource, FString RayIntersectionEntryPoint; // Optional for hit group shaders FString RayTracingExports; + bool bEnable16BitTypes = false; + if (bIsRayTracingShader) { ParseRayTracingEntryPoint(Input.EntryPointName, RayEntryPoint, RayAnyHitEntryPoint, RayIntersectionEntryPoint); @@ -641,6 +645,9 @@ bool CompileAndProcessD3DShaderDXC(FString& PreprocessedShaderSource, RayTracingExports += TEXT(";"); RayTracingExports += RayIntersectionEntryPoint; } + + // Enable 16bit_types to reduce DXIL size (compiler bug - will be fixed) + bEnable16BitTypes = true; } // Write out the preprocessed file and a batch file to compile it if requested (DumpDebugInfoPath is valid) @@ -673,7 +680,7 @@ bool CompileAndProcessD3DShaderDXC(FString& PreprocessedShaderSource, const bool bKeepDebugInfo = Input.Environment.CompilerFlags.Contains(CFLAG_KeepDebugInfo); FDxcArguments Args(EntryPointName, ShaderProfile, RayTracingExports, - Input.DumpDebugInfoPath, Filename, bKeepDebugInfo, DXCFlags, AutoBindingSpace); + Input.DumpDebugInfoPath, Filename, bEnable16BitTypes, bKeepDebugInfo, DXCFlags, AutoBindingSpace); if (bDumpDebugInfo) { diff --git a/Engine/Source/Editor/AnimGraph/AnimGraph.Build.cs b/Engine/Source/Editor/AnimGraph/AnimGraph.Build.cs index aa1fa34bfc2f..896bdd6fa6eb 100644 --- a/Engine/Source/Editor/AnimGraph/AnimGraph.Build.cs +++ b/Engine/Source/Editor/AnimGraph/AnimGraph.Build.cs @@ -38,6 +38,8 @@ public class AnimGraph : ModuleRules "ContentBrowser", "KismetWidgets", "ToolMenus", + "KismetCompiler", + "Kismet", "EditorWidgets", } ); @@ -57,5 +59,5 @@ public class AnimGraph : ModuleRules "AnimationBlueprintEditor", } ); - } + } } diff --git a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_AssetPlayerBase.h b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_AssetPlayerBase.h index 6833ae2d19f9..b0d67ce2dbe8 100644 --- a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_AssetPlayerBase.h +++ b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_AssetPlayerBase.h @@ -15,8 +15,8 @@ ANIMGRAPH_API UClass* GetNodeClassForAsset(const UClass* AssetClass); ANIMGRAPH_API bool SupportNodeClassForAsset(const UClass* AssetClass, UClass* NodeClass); /** Helper / intermediate for asset player graphical nodes */ -UCLASS(Abstract, MinimalAPI) -class UAnimGraphNode_AssetPlayerBase : public UAnimGraphNode_Base +UCLASS(Abstract) +class ANIMGRAPH_API UAnimGraphNode_AssetPlayerBase : public UAnimGraphNode_Base { GENERATED_BODY() public: @@ -25,8 +25,11 @@ public: FAnimationGroupReference SyncGroup; /** UEdGraphNode interface */ - ANIMGRAPH_API virtual void PinConnectionListChanged(UEdGraphPin* Pin) override; - ANIMGRAPH_API virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override; + virtual void PinConnectionListChanged(UEdGraphPin* Pin) override; + virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override; + + /** UAnimGraphNode_Base interface */ + virtual void OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) override; virtual void SetAnimationAsset(UAnimationAsset* Asset) { check(false); /*Base function called*/ } }; diff --git a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_Base.h b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_Base.h index d77b040b6d88..23b3c5e015a6 100644 --- a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_Base.h +++ b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_Base.h @@ -106,7 +106,7 @@ enum class EBlueprintUsage : uint8 UsesBlueprint }; -/** Enum that indicates level of support of this node for a parciular asset class */ +/** Enum that indicates level of support of this node for a particular asset class */ enum class EAnimAssetHandlerType : uint8 { PrimaryHandler, @@ -114,6 +114,55 @@ enum class EAnimAssetHandlerType : uint8 NotSupported }; +/** The type of a property binding */ +UENUM() +enum class EAnimGraphNodePropertyBindingType +{ + None, + Property, + Function, +}; + +USTRUCT() +struct FAnimGraphNodePropertyBinding +{ + GENERATED_BODY() + + FAnimGraphNodePropertyBinding() = default; + + /** Pin type */ + UPROPERTY() + FEdGraphPinType PinType; + + /** Source type if the binding is a promotion */ + UPROPERTY() + FEdGraphPinType PromotedPinType; + + /** Property binding name */ + UPROPERTY() + FName PropertyName; + + /** The property path as text */ + UPROPERTY() + FText PathAsText; + + /** The property path a pin is bound to */ + UPROPERTY() + TArray PropertyPath; + + /** Whether the binding is a function or not */ + UPROPERTY() + EAnimGraphNodePropertyBindingType Type = EAnimGraphNodePropertyBindingType::Property; + + /** Whether the pin is bound or not */ + UPROPERTY() + bool bIsBound = false; + + /** Whether the pin binding is a promotion (e.g. bool->int) */ + UPROPERTY() + bool bIsPromotion = false; +}; + /** * This is the base class for any animation graph nodes that generate or consume an animation pose in * the animation blend graph. @@ -128,6 +177,10 @@ class ANIMGRAPH_API UAnimGraphNode_Base : public UK2Node UPROPERTY(EditAnywhere, Category=PinOptions, EditFixedSize) TArray ShowPinForProperties; + /** Map from property name->binding info */ + UPROPERTY(EditAnywhere, Category=PinOptions) + TMap PropertyBindings; + UPROPERTY(Transient) EBlueprintUsage BlueprintUsage; @@ -144,6 +197,7 @@ class ANIMGRAPH_API UAnimGraphNode_Base : public UK2Node virtual bool ShowPaletteIconOnNode() const override{ return false; } virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override; virtual FString GetPinMetaData(FName InPinName, FName InKey) override; + virtual void AddSearchMetaDataInfo(TArray& OutTaggedMetaData) const override; // End of UEdGraphNode interface // UK2Node interface @@ -155,6 +209,7 @@ class ANIMGRAPH_API UAnimGraphNode_Base : public UK2Node virtual void GetNodeAttributes(TArray>& OutNodeAttributes) const override; virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; virtual FText GetMenuCategory() const override; + virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override; // By default return any animation assets we have virtual UObject* GetJumpTargetForDoubleClick() const override { return GetAnimationAsset(); } @@ -200,6 +255,15 @@ class ANIMGRAPH_API UAnimGraphNode_Base : public UK2Node // Replace references to animations that exist in the supplied maps virtual void ReplaceReferredAnimations(const TMap& AnimAssetReplacementMap) {}; + // Process this node's data during compilation (override point) + virtual void OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) {} + + // Get all the subsystems that we want to add to the class to support this node + virtual void GetRequiredClassSubsystems(TArray>& OutSubsystemClasses) const {} + + // Process this node's data during compilation + void ProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext); + // Helper function for GetAllAnimationSequencesReferred void HandleAnimReferenceCollection(UAnimationAsset* AnimAsset, TArray& AnimationAssets) const; @@ -294,8 +358,9 @@ class ANIMGRAPH_API UAnimGraphNode_Base : public UK2Node FOnNodeTitleChangedEvent& OnNodeTitleChangedEvent() { return NodeTitleChangedEvent; } protected: - friend FAnimBlueprintCompilerContext; - friend FAnimGraphNodeDetails; + friend class FAnimBlueprintCompilerContext; + friend class FAnimGraphNodeDetails; + friend class UAnimBlueprintCompilerSubsystem_Base; // Gets the animation FNode type represented by this ed graph node UScriptStruct* GetFNodeType() const; diff --git a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_CustomProperty.h b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_CustomProperty.h index 593de84d3f73..37c833e4d181 100644 --- a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_CustomProperty.h +++ b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_CustomProperty.h @@ -7,6 +7,7 @@ #include "Misc/Guid.h" #include "AnimGraphNode_Base.h" #include "Animation/AnimNode_CustomProperty.h" +#include "IClassVariableCreator.h" #include "AnimGraphNode_CustomProperty.generated.h" @@ -15,12 +16,15 @@ class IDetailLayoutBuilder; class IPropertyHandle; UCLASS(Abstract) -class ANIMGRAPH_API UAnimGraphNode_CustomProperty : public UAnimGraphNode_Base +class ANIMGRAPH_API UAnimGraphNode_CustomProperty : public UAnimGraphNode_Base, public IClassVariableCreator { GENERATED_BODY() public: + // IClassVariableCreator interface + virtual void CreateClassVariablesFromBlueprint(FKismetCompilerContext& InCompilerContext) override; + //~ Begin UEdGraphNode Interface. virtual void ValidateAnimNodeDuringCompilation(USkeleton* ForSkeleton, FCompilerResultsLog& MessageLog) override; virtual void ReallocatePinsDuringReconstruction(TArray& OldPins) override; @@ -30,6 +34,8 @@ public: // UAnimGraphNode_Base interface virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + virtual void OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) override; + // Gets the property on InOwnerInstanceClass that corresponds to InInputPin void GetInstancePinProperty(const UClass* InOwnerInstanceClass, UEdGraphPin* InInputPin, FProperty*& OutProperty); // Gets the unique name for the property linked to a given pin diff --git a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_LinkedAnimGraphBase.h b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_LinkedAnimGraphBase.h index 44ab6e770719..4b49615afd35 100644 --- a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_LinkedAnimGraphBase.h +++ b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_LinkedAnimGraphBase.h @@ -6,6 +6,8 @@ #include "UObject/ObjectMacros.h" #include "Misc/Guid.h" #include "AnimGraphNode_CustomProperty.h" +#include "IClassVariableCreator.h" +#include "Animation/AnimInstanceSubsystemData.h" #include "AnimGraphNode_LinkedAnimGraphBase.generated.h" @@ -21,7 +23,6 @@ class UAnimGraphNode_LinkedAnimGraphBase : public UAnimGraphNode_CustomProperty GENERATED_BODY() public: - //~ Begin UEdGraphNode Interface. virtual FLinearColor GetNodeTitleColor() const override; virtual FText GetTooltipText() const override; @@ -43,6 +44,11 @@ public: virtual const FAnimNode_LinkedAnimGraph* GetLinkedAnimGraphNode() const PURE_VIRTUAL(UAnimGraphNode_LinkedAnimGraphBase::GetLinkedAnimGraphNode, return nullptr;); protected: + friend class UAnimBlueprintCompilerSubsystem_LinkedAnimGraph; + + // Called pre-compilation to allocate pose links + void AllocatePoseLinks(); + // Finds out whether there is a loop in the graph formed by linked instances from this node bool HasInstanceLoop(); diff --git a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_LinkedInputPose.h b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_LinkedInputPose.h index 623a9a869cc8..8b7917d80e7e 100644 --- a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_LinkedInputPose.h +++ b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_LinkedInputPose.h @@ -7,6 +7,8 @@ #include "AnimGraphNode_Base.h" #include "Animation/AnimNode_LinkedInputPose.h" #include "Engine/MemberReference.h" +#include "IClassVariableCreator.h" + #include "AnimGraphNode_LinkedInputPose.generated.h" class SEditableTextBox; @@ -39,7 +41,7 @@ struct FAnimBlueprintFunctionPinInfo }; UCLASS() -class ANIMGRAPH_API UAnimGraphNode_LinkedInputPose : public UAnimGraphNode_Base +class ANIMGRAPH_API UAnimGraphNode_LinkedInputPose : public UAnimGraphNode_Base, public IClassVariableCreator { GENERATED_BODY() @@ -63,6 +65,9 @@ public: /** UObject interface */ virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + /** IClassVariableCreator interface */ + virtual void CreateClassVariablesFromBlueprint(FKismetCompilerContext& InCompilerContext) override; + /** UEdGraphNode interface */ virtual FLinearColor GetNodeTitleColor() const override; virtual FText GetTooltipText() const override; @@ -75,6 +80,7 @@ public: /** UK2Node interface */ virtual bool HasExternalDependencies(TArray* OptionalOutput) const override; + virtual void ExpandNode(class FKismetCompilerContext& InCompilerContext, UEdGraph* InSourceGraph) override; /** UAnimGraphNode_Base interface */ virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; diff --git a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_Root.h b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_Root.h index 1ef66af37372..565714ec1c5d 100644 --- a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_Root.h +++ b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_Root.h @@ -25,10 +25,12 @@ class UAnimGraphNode_Root : public UAnimGraphNode_Base virtual bool CanUserDeleteNode() const override { return false; } virtual bool CanDuplicateNode() const override { return false; } virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; + virtual bool IsNodeRootSet() const override { return true; } //~ End UEdGraphNode Interface. //~ Begin UAnimGraphNode_Base Interface virtual bool IsSinkNode() const override; + virtual void OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) override; // Get the link to the documentation virtual FString GetDocumentationLink() const override; diff --git a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_SaveCachedPose.h b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_SaveCachedPose.h index 249cbefa68ca..858541e11572 100644 --- a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_SaveCachedPose.h +++ b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_SaveCachedPose.h @@ -30,6 +30,7 @@ class UAnimGraphNode_SaveCachedPose : public UAnimGraphNode_Base virtual void OnRenameNode(const FString& NewName) override; virtual TSharedPtr MakeNameValidator() const override; virtual bool IsCompatibleWithGraph(const UEdGraph* TargetGraph) const override; + virtual bool IsNodeRootSet() const override { return true; } // End of UEdGraphNode interface // UK2Node interface. diff --git a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_StateMachineBase.h b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_StateMachineBase.h index 26528ff664ea..1d5bba4ed8d5 100644 --- a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_StateMachineBase.h +++ b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_StateMachineBase.h @@ -36,6 +36,7 @@ class ANIMGRAPH_API UAnimGraphNode_StateMachineBase : public UAnimGraphNode_Base // UAnimGraphNode_Base interface virtual FString GetNodeCategory() const override; + virtual void OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) override; // End of UAnimGraphNode_Base interface // @return the name of this state machine diff --git a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_StateResult.h b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_StateResult.h index 72f898928401..3bd1c72d6cf0 100644 --- a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_StateResult.h +++ b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_StateResult.h @@ -24,10 +24,12 @@ class UAnimGraphNode_StateResult : public UAnimGraphNode_Base virtual bool CanUserDeleteNode() const override { return false; } virtual bool CanDuplicateNode() const override { return false; } virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; + virtual bool IsNodeRootSet() const override { return true; } //~ End UEdGraphNode Interface. //~ Begin UAnimGraphNode_Base Interface virtual bool IsSinkNode() const override; + virtual void OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) override; // Get the link to the documentation virtual FString GetDocumentationLink() const override; diff --git a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_TransitionResult.h b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_TransitionResult.h index aab2e51539a0..c47dd21448e2 100644 --- a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_TransitionResult.h +++ b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_TransitionResult.h @@ -24,6 +24,7 @@ class UAnimGraphNode_TransitionResult : public UAnimGraphNode_Base virtual FLinearColor GetNodeTitleColor() const override; virtual FText GetTooltipText() const override; virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + virtual bool IsNodeRootSet() const override { return true; } // End of UEdGraphNode interface // UK2Node interface. diff --git a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_UseCachedPose.h b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_UseCachedPose.h index 47c41e336c53..62f726544b9b 100644 --- a/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_UseCachedPose.h +++ b/Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_UseCachedPose.h @@ -37,6 +37,7 @@ public: // UAnimGraphNode_Base interface virtual FString GetNodeCategory() const override; virtual void EarlyValidation(class FCompilerResultsLog& MessageLog) const override; + virtual void OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) override; // End of UAnimGraphNode_Base interface private: diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompiler.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompiler.cpp new file mode 100644 index 000000000000..e6f0a196f7c1 --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompiler.cpp @@ -0,0 +1,1369 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "AnimBlueprintCompiler.h" +#include "UObject/UObjectHash.h" +#include "Animation/AnimInstance.h" +#include "EdGraphUtilities.h" +#include "K2Node_CallFunction.h" +#include "K2Node_StructMemberGet.h" +#include "K2Node_BreakStruct.h" +#include "K2Node_MakeStruct.h" +#include "K2Node_CallArrayFunction.h" +#include "K2Node_CustomEvent.h" +#include "K2Node_Knot.h" +#include "K2Node_StructMemberSet.h" +#include "K2Node_VariableGet.h" +#include "K2Node_VariableSet.h" +#include "K2Node_GetArrayItem.h" + +#include "AnimationGraphSchema.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Kismet/KismetArrayLibrary.h" +#include "Kismet/KismetMathLibrary.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetReinstanceUtilities.h" +#include "AnimGraphNode_Root.h" +#include "Animation/AnimNode_CustomProperty.h" +#include "Animation/AnimNode_LinkedAnimGraph.h" +#include "AnimGraphNode_LinkedAnimGraph.h" +#include "AnimationEditorUtils.h" +#include "AnimationGraph.h" +#include "AnimBlueprintPostCompileValidation.h" +#include "AnimGraphNode_LinkedInputPose.h" +#include "K2Node_FunctionEntry.h" +#include "K2Node_FunctionResult.h" +#include "AnimGraphNode_LinkedAnimLayer.h" +#include "String/ParseTokens.h" +#include "Algo/Transform.h" +#include "Algo/Accumulate.h" +#include "IClassVariableCreator.h" +#include "Animation/AnimInstanceSubsystemData.h" + +#define LOCTEXT_NAMESPACE "AnimBlueprintCompiler" + +////////////////////////////////////////////////////////////////////////// +// FAnimBlueprintCompiler + +FAnimBlueprintCompilerContext::FAnimBlueprintCompilerContext(UAnimBlueprint* SourceSketch, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompileOptions) + : FKismetCompilerContext(SourceSketch, InMessageLog, InCompileOptions) + , AnimBlueprint(SourceSketch) + , bIsDerivedAnimBlueprint(false) +{ + AnimBlueprintCompilerSubsystemCollection.RegisterContext(this); + AnimBlueprintCompilerSubsystemCollection.Initialize(GetTransientPackage()); + + // Make sure the skeleton has finished preloading + if (AnimBlueprint->TargetSkeleton != nullptr) + { + if (FLinkerLoad* Linker = AnimBlueprint->TargetSkeleton->GetLinker()) + { + Linker->Preload(AnimBlueprint->TargetSkeleton); + } + } + + if (AnimBlueprint->HasAnyFlags(RF_NeedPostLoad)) + { + //Compilation during loading .. need to verify node guids as some anim blueprints have duplicated guids + + TArray ChildGraphs; + ChildGraphs.Reserve(20); + + TSet NodeGuids; + NodeGuids.Reserve(200); + + // Tracking to see if we need to warn for deterministic cooking + bool bNodeGuidsRegenerated = false; + + auto CheckGraph = [&bNodeGuidsRegenerated, &NodeGuids, &ChildGraphs](UEdGraph* InGraph) + { + if (AnimationEditorUtils::IsAnimGraph(InGraph)) + { + ChildGraphs.Reset(); + AnimationEditorUtils::FindChildGraphsFromNodes(InGraph, ChildGraphs); + + for (int32 Index = 0; Index < ChildGraphs.Num(); ++Index) // Not ranged for as we modify array within the loop + { + UEdGraph* ChildGraph = ChildGraphs[Index]; + + // Get subgraphs before continuing + AnimationEditorUtils::FindChildGraphsFromNodes(ChildGraph, ChildGraphs); + + for (UEdGraphNode* Node : ChildGraph->Nodes) + { + if (Node) + { + if (NodeGuids.Contains(Node->NodeGuid)) + { + bNodeGuidsRegenerated = true; + + Node->CreateNewGuid(); // GUID is already being used, create a new one. + } + else + { + NodeGuids.Add(Node->NodeGuid); + } + } + } + } + } + }; + + for (UEdGraph* Graph : AnimBlueprint->FunctionGraphs) + { + CheckGraph(Graph); + } + + for(FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) + { + for(UEdGraph* Graph : InterfaceDesc.Graphs) + { + CheckGraph(Graph); + } + } + + if(bNodeGuidsRegenerated) + { + UE_LOG(LogAnimation, Warning, TEXT("Animation Blueprint %s has nodes with invalid node guids that have been regenerated. This blueprint will not cook deterministically until it is resaved."), *AnimBlueprint->GetPathName()); + } + } + + // Determine if there is an anim blueprint in the ancestry of this class + bIsDerivedAnimBlueprint = UAnimBlueprint::FindRootAnimBlueprint(AnimBlueprint) != NULL; + + // Regenerate temporary stub functions + // We do this here to catch the standard and 'fast' (compilation manager) compilation paths + CreateAnimGraphStubFunctions(); +} + +FAnimBlueprintCompilerContext::~FAnimBlueprintCompilerContext() +{ + DestroyAnimGraphStubFunctions(); + + AnimBlueprintCompilerSubsystemCollection.Deinitialize(); +} + +void FAnimBlueprintCompilerContext::ForAllSubGraphs(UEdGraph* InGraph, TFunctionRef InPerGraphFunction) +{ + TArray AllGraphs; + AllGraphs.Add(InGraph); + InGraph->GetAllChildrenGraphs(AllGraphs); + + for(UEdGraph* CurrGraph : AllGraphs) + { + InPerGraphFunction(CurrGraph); + } +}; + +void FAnimBlueprintCompilerContext::CreateClassVariablesFromBlueprint() +{ + FKismetCompilerContext::CreateClassVariablesFromBlueprint(); + + if(!bIsDerivedAnimBlueprint) + { + auto ProcessGraph = [this](UEdGraph* InGraph) + { + TArray ClassVariableCreators; + InGraph->GetNodesOfClass(ClassVariableCreators); + for(IClassVariableCreator* ClassVariableCreator : ClassVariableCreators) + { + ClassVariableCreator->CreateClassVariablesFromBlueprint(*this); + } + }; + + for (UEdGraph* Graph : Blueprint->FunctionGraphs) + { + ForAllSubGraphs(Graph, ProcessGraph); + } + + for(FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) + { + for(UEdGraph* Graph : InterfaceDesc.Graphs) + { + ForAllSubGraphs(Graph, ProcessGraph); + } + } + } +} + + +UEdGraphSchema_K2* FAnimBlueprintCompilerContext::CreateSchema() +{ + AnimSchema = NewObject(); + return AnimSchema; +} + +void FAnimBlueprintCompilerContext::ProcessAnimationNode(UAnimGraphNode_Base* VisualAnimNode) +{ + // Early out if this node has already been processed + if (AllocatedAnimNodes.Contains(VisualAnimNode)) + { + return; + } + + // Make sure the visual node has a runtime node template + const UScriptStruct* NodeType = VisualAnimNode->GetFNodeType(); + if (NodeType == NULL) + { + MessageLog.Error(TEXT("@@ has no animation node member"), VisualAnimNode); + return; + } + + // Give the visual node a chance to do validation + { + const int32 PreValidationErrorCount = MessageLog.NumErrors; + VisualAnimNode->ValidateAnimNodeDuringCompilation(AnimBlueprint->TargetSkeleton, MessageLog); + VisualAnimNode->BakeDataDuringCompilation(MessageLog); + if (MessageLog.NumErrors != PreValidationErrorCount) + { + return; + } + } + + // Create a property for the node + const FString NodeVariableName = ClassScopeNetNameMap.MakeValidName(VisualAnimNode); + + const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault(); + + FEdGraphPinType NodeVariableType; + NodeVariableType.PinCategory = UAnimationGraphSchema::PC_Struct; + NodeVariableType.PinSubCategoryObject = MakeWeakObjectPtr(const_cast(NodeType)); + + FStructProperty* NewProperty = CastField(CreateVariable(FName(*NodeVariableName), NodeVariableType)); + + if (NewProperty == NULL) + { + MessageLog.Error(TEXT("Failed to create node property for @@"), VisualAnimNode); + } + + // Register this node with the compile-time data structures + const int32 AllocatedIndex = AllocateNodeIndexCounter++; + AllocatedAnimNodes.Add(VisualAnimNode, NewProperty); + AllocatedNodePropertiesToNodes.Add(NewProperty, VisualAnimNode); + AllocatedAnimNodeIndices.Add(VisualAnimNode, AllocatedIndex); + AllocatedPropertiesByIndex.Add(AllocatedIndex, NewProperty); + + UAnimGraphNode_Base* TrueSourceObject = MessageLog.FindSourceObjectTypeChecked(VisualAnimNode); + SourceNodeToProcessedNodeMap.Add(TrueSourceObject, VisualAnimNode); + + // Register the slightly more permanent debug information + FAnimBlueprintDebugData& NewAnimBlueprintDebugData = NewAnimBlueprintClass->GetAnimBlueprintDebugData(); + NewAnimBlueprintDebugData.NodePropertyToIndexMap.Add(TrueSourceObject, AllocatedIndex); + NewAnimBlueprintDebugData.NodeGuidToIndexMap.Add(TrueSourceObject->NodeGuid, AllocatedIndex); + NewAnimBlueprintDebugData.NodePropertyIndexToNodeMap.Add(AllocatedIndex, TrueSourceObject); + NewAnimBlueprintClass->GetDebugData().RegisterClassPropertyAssociation(TrueSourceObject, NewProperty); + + // Add to the classes subsystem map + TArray> SubsystemClasses; + VisualAnimNode->GetRequiredClassSubsystems(SubsystemClasses); + AddSubsystemClasses(SubsystemClasses); + + VisualAnimNode->ProcessDuringCompilation(*this); +} + +void FAnimBlueprintCompilerContext::ProcessClassSubsystems() +{ + // Sort subsystems by class name + NewAnimBlueprintClass->Subsystems.Sort([](UAnimBlueprintClassSubsystem& InSubsystemA, UAnimBlueprintClassSubsystem& InSubsystemB) + { + return InSubsystemA.GetClass()->GetName() < InSubsystemB.GetClass()->GetName(); + }); + + // Rebuild subsystem maps + NewAnimBlueprintClass->RebuildSubsystemMaps(); + + // Process all gathered class subsystems + for(UAnimBlueprintClassSubsystem* Subsystem : NewAnimBlueprintClass->Subsystems) + { + ProcessClassSubsystem(Subsystem); + } +} + +void FAnimBlueprintCompilerContext::ProcessClassSubsystem(UAnimBlueprintClassSubsystem* InSubsystem) +{ + const FString SubsystemVariableName = ClassScopeNetNameMap.MakeValidName(InSubsystem); + + const UScriptStruct* InstanceDataType = InSubsystem->GetInstanceDataType(); + check(InstanceDataType->IsChildOf(FAnimInstanceSubsystemData::StaticStruct())); + + FEdGraphPinType SubsystemVariableType; + SubsystemVariableType.PinCategory = UAnimationGraphSchema::PC_Struct; + SubsystemVariableType.PinSubCategoryObject = MakeWeakObjectPtr(const_cast(InstanceDataType)); + + FStructProperty* NewProperty = CastField(CreateVariable(FName(*SubsystemVariableName), SubsystemVariableType)); + if (NewProperty == nullptr) + { + MessageLog.Error(*FText::Format(LOCTEXT("SubsystemPropertyCreationFailed", "Failed to create subsystem property for '{0}'"), FText::FromString(InSubsystem->GetName())).ToString()); + } +} + +void FAnimBlueprintCompilerContext::AddSubsystemClasses(const TArray>& SubsystemClasses) +{ + for(const TSubclassOf& NodeSubsystemClass : SubsystemClasses) + { + UAnimBlueprintClassSubsystem* ExistingSubsystem = NewAnimBlueprintClass->SubsystemMap.FindRef(NodeSubsystemClass); + if(ExistingSubsystem == nullptr) + { + UAnimBlueprintClassSubsystem* NewSubsystem = NewObject(NewAnimBlueprintClass, NodeSubsystemClass.Get()); + NewAnimBlueprintClass->Subsystems.Add(NewSubsystem); + NewAnimBlueprintClass->SubsystemMap.Add(NodeSubsystemClass, NewSubsystem); + } + } +} + +int32 FAnimBlueprintCompilerContext::GetAllocationIndexOfNode(UAnimGraphNode_Base* VisualAnimNode) +{ + ProcessAnimationNode(VisualAnimNode); + int32* pResult = AllocatedAnimNodeIndices.Find(VisualAnimNode); + return (pResult != NULL) ? *pResult : INDEX_NONE; +} + +bool FAnimBlueprintCompilerContext::ShouldForceKeepNode(const UEdGraphNode* Node) const +{ + // Force keep anim nodes during the standard pruning step. Isolated ones will then be removed via PruneIsolatedAnimationNodes. + // Anim graph nodes are always culled at their expansion step anyways. + return Node->IsA(); +} + +void FAnimBlueprintCompilerContext::PostExpansionStep(UEdGraph* Graph) +{ + ForEachSubsystem([Graph](UAnimBlueprintCompilerSubsystem* InSubsystem) + { + InSubsystem->PostExpansionStep(Graph); + }); +} + +void FAnimBlueprintCompilerContext::PruneIsolatedAnimationNodes(const TArray& RootSet, TArray& GraphNodes) +{ + struct FNodeVisitorDownPoseWires + { + TSet VisitedNodes; + const UAnimationGraphSchema* Schema; + + FNodeVisitorDownPoseWires() + { + Schema = GetDefault(); + } + + void TraverseNodes(UEdGraphNode* Node) + { + VisitedNodes.Add(Node); + + // Follow every exec output pin + for (int32 i = 0; i < Node->Pins.Num(); ++i) + { + UEdGraphPin* MyPin = Node->Pins[i]; + + if ((MyPin->Direction == EGPD_Input) && (Schema->IsPosePin(MyPin->PinType))) + { + for (int32 j = 0; j < MyPin->LinkedTo.Num(); ++j) + { + UEdGraphPin* OtherPin = MyPin->LinkedTo[j]; + UEdGraphNode* OtherNode = OtherPin->GetOwningNode(); + if (!VisitedNodes.Contains(OtherNode)) + { + TraverseNodes(OtherNode); + } + } + } + } + } + }; + + // Prune the nodes that aren't reachable via an animation pose link + FNodeVisitorDownPoseWires Visitor; + + for (auto RootIt = RootSet.CreateConstIterator(); RootIt; ++RootIt) + { + UAnimGraphNode_Base* RootNode = *RootIt; + Visitor.TraverseNodes(RootNode); + } + + for (int32 NodeIndex = 0; NodeIndex < GraphNodes.Num(); ++NodeIndex) + { + UAnimGraphNode_Base* Node = GraphNodes[NodeIndex]; + + // We cant prune linked input poses as even if they are not linked to the root, they are needed for the dynamic link phase at runtime + if (!Visitor.VisitedNodes.Contains(Node) && !IsNodePure(Node) && !Node->IsA()) + { + Node->BreakAllNodeLinks(); + GraphNodes.RemoveAtSwap(NodeIndex); + --NodeIndex; + } + } +} + +void FAnimBlueprintCompilerContext::ProcessAnimationNodes(TArray& AnimNodeList) +{ + // Process the remaining nodes + for (UAnimGraphNode_Base* AnimNode : AnimNodeList) + { + ProcessAnimationNode(AnimNode); + } +} + +void FAnimBlueprintCompilerContext::GetLinkedAnimNodes(UAnimGraphNode_Base* InGraphNode, TArray &LinkedAnimNodes) +{ + for(UEdGraphPin* Pin : InGraphNode->Pins) + { + if(Pin->Direction == EEdGraphPinDirection::EGPD_Input && + Pin->PinType.PinCategory == TEXT("struct")) + { + if(UScriptStruct* Struct = Cast(Pin->PinType.PinSubCategoryObject.Get())) + { + if(Struct->IsChildOf(FPoseLinkBase::StaticStruct())) + { + GetLinkedAnimNodes_TraversePin(Pin, LinkedAnimNodes); + } + } + } + } +} + +void FAnimBlueprintCompilerContext::GetLinkedAnimNodes_TraversePin(UEdGraphPin* InPin, TArray& LinkedAnimNodes) +{ + if(!InPin) + { + return; + } + + for(UEdGraphPin* LinkedPin : InPin->LinkedTo) + { + if(!LinkedPin) + { + continue; + } + + UEdGraphNode* OwningNode = LinkedPin->GetOwningNode(); + + if(UK2Node_Knot* InnerKnot = Cast(OwningNode)) + { + GetLinkedAnimNodes_TraversePin(InnerKnot->GetInputPin(), LinkedAnimNodes); + } + else if(UAnimGraphNode_Base* AnimNode = Cast(OwningNode)) + { + GetLinkedAnimNodes_ProcessAnimNode(AnimNode, LinkedAnimNodes); + } + } +} + +void FAnimBlueprintCompilerContext::GetLinkedAnimNodes_ProcessAnimNode(UAnimGraphNode_Base* AnimNode, TArray &LinkedAnimNodes) +{ + if(!AllocatedAnimNodes.Contains(AnimNode)) + { + UAnimGraphNode_Base* TrueSourceNode = MessageLog.FindSourceObjectTypeChecked(AnimNode); + + if(UAnimGraphNode_Base** AllocatedNode = SourceNodeToProcessedNodeMap.Find(TrueSourceNode)) + { + LinkedAnimNodes.Add(*AllocatedNode); + } + else + { + FString ErrorString = FText::Format(LOCTEXT("MissingLinkFmt", "Missing allocated node for {0} while searching for node links - likely due to the node having outstanding errors."), FText::FromString(AnimNode->GetName())).ToString(); + MessageLog.Error(*ErrorString); + } + } + else + { + LinkedAnimNodes.Add(AnimNode); + } +} + +void FAnimBlueprintCompilerContext::ProcessAllAnimationNodes() +{ + // Validate that we have a skeleton + if ((AnimBlueprint->TargetSkeleton == nullptr) && !AnimBlueprint->bIsNewlyCreated) + { + MessageLog.Error(*LOCTEXT("NoSkeleton", "@@ - The skeleton asset for this animation Blueprint is missing, so it cannot be compiled!").ToString(), AnimBlueprint); + return; + } + + // Build the raw node lists + TArray RootAnimNodeList; + ConsolidatedEventGraph->GetNodesOfClass(RootAnimNodeList); + + // We recursively build the node lists for pre- and post-processing phases to make sure + // we catch any subsystem-relevant nodes in sub-graphs + TArray AllSubGraphsAnimNodeList; + ForAllSubGraphs(ConsolidatedEventGraph, [&AllSubGraphsAnimNodeList](UEdGraph* InGraph) + { + InGraph->GetNodesOfClass(AllSubGraphsAnimNodeList); + }); + + // Find the root nodes + TArray RootSet; + + AllocateNodeIndexCounter = 0; + + for (UAnimGraphNode_Base* SourceNode : RootAnimNodeList) + { + UAnimGraphNode_Base* TrueNode = MessageLog.FindSourceObjectTypeChecked(SourceNode); + TrueNode->BlueprintUsage = EBlueprintUsage::NoProperties; + + if(SourceNode->IsNodeRootSet()) + { + RootSet.Add(SourceNode); + } + } + + if (RootAnimNodeList.Num() > 0) + { + // Prune any anim nodes (they will be skipped by PruneIsolatedNodes above) + PruneIsolatedAnimationNodes(RootSet, RootAnimNodeList); + + // Validate the graph + ValidateGraphIsWellFormed(ConsolidatedEventGraph); + + ForEachSubsystem([this, &AllSubGraphsAnimNodeList](UAnimBlueprintCompilerSubsystem* InSubsystem) + { + // Add any subsystem classes that need to be added without paying heed to node connectivity + TArray> SubsystemClasses; + InSubsystem->GetRequiredClassSubsystems(SubsystemClasses); + AddSubsystemClasses(SubsystemClasses); + + InSubsystem->PreProcessAnimationNodes(AllSubGraphsAnimNodeList); + }); + + // Process the animation nodes + ProcessAnimationNodes(RootAnimNodeList); + + ForEachSubsystem([&AllSubGraphsAnimNodeList](UAnimBlueprintCompilerSubsystem* InSubsystem) + { + InSubsystem->PostProcessAnimationNodes(AllSubGraphsAnimNodeList); + }); + + // Process all the gathered class subsystems + ProcessClassSubsystems(); + } + else + { + MessageLog.Error(*LOCTEXT("ExpectedAFunctionEntry_Error", "Expected at least one animation node, but did not find any").ToString()); + } +} + +void FAnimBlueprintCompilerContext::CopyTermDefaultsToDefaultObject(UObject* DefaultObject) +{ + Super::CopyTermDefaultsToDefaultObject(DefaultObject); + + UAnimInstance* DefaultAnimInstance = Cast(DefaultObject); + + if (bIsDerivedAnimBlueprint && DefaultAnimInstance) + { + // If we are a derived animation graph; apply any stored overrides. + // Restore values from the root class to catch values where the override has been removed. + UAnimBlueprintGeneratedClass* RootAnimClass = NewAnimBlueprintClass; + while (UAnimBlueprintGeneratedClass* NextClass = Cast(RootAnimClass->GetSuperClass())) + { + RootAnimClass = NextClass; + } + UObject* RootDefaultObject = RootAnimClass->GetDefaultObject(); + + for (TFieldIterator It(RootAnimClass); It; ++It) + { + FProperty* RootProp = *It; + + if (FStructProperty* RootStructProp = CastField(RootProp)) + { + if (RootStructProp->Struct->IsChildOf(FAnimNode_Base::StaticStruct())) + { + FStructProperty* ChildStructProp = FindFProperty(NewAnimBlueprintClass, *RootStructProp->GetName()); + check(ChildStructProp); + uint8* SourcePtr = RootStructProp->ContainerPtrToValuePtr(RootDefaultObject); + uint8* DestPtr = ChildStructProp->ContainerPtrToValuePtr(DefaultAnimInstance); + check(SourcePtr && DestPtr); + RootStructProp->CopyCompleteValue(DestPtr, SourcePtr); + } + } + } + } + + // Give game-specific logic a chance to replace animations + if(DefaultAnimInstance) + { + DefaultAnimInstance->ApplyAnimOverridesToCDO(MessageLog); + } + + if (bIsDerivedAnimBlueprint && DefaultAnimInstance) + { + // Patch the overridden values into the CDO + TArray AssetOverrides; + AnimBlueprint->GetAssetOverrides(AssetOverrides); + for (FAnimParentNodeAssetOverride* Override : AssetOverrides) + { + if (Override->NewAsset) + { + FAnimNode_Base* BaseNode = NewAnimBlueprintClass->GetPropertyInstance(DefaultAnimInstance, Override->ParentNodeGuid, EPropertySearchMode::Hierarchy); + if (BaseNode) + { + BaseNode->OverrideAsset(Override->NewAsset); + } + } + } + + return; + } + + if(DefaultAnimInstance) + { + int32 LinkIndexCount = 0; + TMap LinkIndexMap; + TMap NodeBaseAddresses; + + // Initialize animation nodes from their templates + for (TFieldIterator It(DefaultAnimInstance->GetClass(), EFieldIteratorFlags::ExcludeSuper); It; ++It) + { + FProperty* TargetProperty = *It; + + if (UAnimGraphNode_Base* VisualAnimNode = AllocatedNodePropertiesToNodes.FindRef(TargetProperty)) + { + const FStructProperty* SourceNodeProperty = VisualAnimNode->GetFNodeProperty(); + check(SourceNodeProperty != NULL); + check(CastFieldChecked(TargetProperty)->Struct == SourceNodeProperty->Struct); + + uint8* DestinationPtr = TargetProperty->ContainerPtrToValuePtr(DefaultAnimInstance); + uint8* SourcePtr = SourceNodeProperty->ContainerPtrToValuePtr(VisualAnimNode); + + if(UAnimGraphNode_Root* RootNode = ExactCast(VisualAnimNode)) + { + // patch graph name into root nodes + FAnimNode_Root NewRoot = *reinterpret_cast(SourcePtr); + NewRoot.Name = Cast(MessageLog.FindSourceObject(RootNode))->GetGraph()->GetFName(); + TargetProperty->CopyCompleteValue(DestinationPtr, &NewRoot); + } + else if(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode = ExactCast(VisualAnimNode)) + { + // patch graph name into linked input pose nodes + FAnimNode_LinkedInputPose NewLinkedInputPose = *reinterpret_cast(SourcePtr); + NewLinkedInputPose.Graph = Cast(MessageLog.FindSourceObject(LinkedInputPoseNode))->GetGraph()->GetFName(); + TargetProperty->CopyCompleteValue(DestinationPtr, &NewLinkedInputPose); + } + else if(UAnimGraphNode_LinkedAnimGraph* LinkedAnimGraphNode = ExactCast(VisualAnimNode)) + { + // patch node index into linked anim graph nodes + FAnimNode_LinkedAnimGraph NewLinkedAnimGraph = *reinterpret_cast(SourcePtr); + NewLinkedAnimGraph.NodeIndex = LinkIndexCount; + TargetProperty->CopyCompleteValue(DestinationPtr, &NewLinkedAnimGraph); + } + else if(UAnimGraphNode_LinkedAnimLayer* LinkedAnimLayerNode = ExactCast(VisualAnimNode)) + { + // patch node index into linked anim layer nodes + FAnimNode_LinkedAnimLayer NewLinkedAnimLayer = *reinterpret_cast(SourcePtr); + NewLinkedAnimLayer.NodeIndex = LinkIndexCount; + TargetProperty->CopyCompleteValue(DestinationPtr, &NewLinkedAnimLayer); + } + else + { + TargetProperty->CopyCompleteValue(DestinationPtr, SourcePtr); + } + + LinkIndexMap.Add(VisualAnimNode, LinkIndexCount); + NodeBaseAddresses.Add(VisualAnimNode, DestinationPtr); + ++LinkIndexCount; + } + } + + // And wire up node links + for (auto PoseLinkIt = ValidPoseLinkList.CreateIterator(); PoseLinkIt; ++PoseLinkIt) + { + FPoseLinkMappingRecord& Record = *PoseLinkIt; + + UAnimGraphNode_Base* LinkingNode = Record.GetLinkingNode(); + UAnimGraphNode_Base* LinkedNode = Record.GetLinkedNode(); + + // @TODO this is quick solution for crash - if there were previous errors and some nodes were not added, they could still end here - + // this check avoids that and since there are already errors, compilation won't be successful. + // but I'd prefer stopping compilation earlier to avoid getting here in first place + if (LinkIndexMap.Contains(LinkingNode) && LinkIndexMap.Contains(LinkedNode)) + { + const int32 SourceNodeIndex = LinkIndexMap.FindChecked(LinkingNode); + const int32 LinkedNodeIndex = LinkIndexMap.FindChecked(LinkedNode); + uint8* DestinationPtr = NodeBaseAddresses.FindChecked(LinkingNode); + + Record.PatchLinkIndex(DestinationPtr, LinkedNodeIndex, SourceNodeIndex); + } + } + + // Let each subsystem copy any data it wants + ForEachSubsystem([DefaultObject](UAnimBlueprintCompilerSubsystem* InSubsystem) + { + InSubsystem->CopyTermDefaultsToDefaultObject(DefaultObject); + }); + + UAnimBlueprintGeneratedClass* AnimBlueprintGeneratedClass = CastChecked(NewClass); + + // copy threaded update flag to CDO + DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = AnimBlueprint->bUseMultiThreadedAnimationUpdate; + + // Verify thread-safety + if(GetDefault()->bAllowMultiThreadedAnimationUpdate && DefaultAnimInstance->bUseMultiThreadedAnimationUpdate) + { + // If we are a child anim BP, check parent classes & their CDOs + if (UAnimBlueprintGeneratedClass* ParentClass = Cast(AnimBlueprintGeneratedClass->GetSuperClass())) + { + UAnimBlueprint* ParentAnimBlueprint = Cast(ParentClass->ClassGeneratedBy); + if (ParentAnimBlueprint && !ParentAnimBlueprint->bUseMultiThreadedAnimationUpdate) + { + DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false; + } + + UAnimInstance* ParentDefaultObject = Cast(ParentClass->GetDefaultObject(false)); + if (ParentDefaultObject && !ParentDefaultObject->bUseMultiThreadedAnimationUpdate) + { + DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false; + } + } + + // iterate all properties to determine validity + for (FStructProperty* Property : TFieldRange(AnimBlueprintGeneratedClass, EFieldIteratorFlags::IncludeSuper)) + { + if(Property->Struct->IsChildOf(FAnimNode_Base::StaticStruct())) + { + FAnimNode_Base* AnimNode = Property->ContainerPtrToValuePtr(DefaultAnimInstance); + if(!AnimNode->CanUpdateInWorkerThread()) + { + MessageLog.Warning(*FText::Format(LOCTEXT("HasIncompatibleNode", "Found incompatible node \"{0}\" in blend graph. Disable threaded update or use member variable access."), FText::FromName(Property->Struct->GetFName())).ToString()) + ->AddToken(FDocumentationToken::Create(TEXT("Engine/Animation/AnimBlueprints/AnimGraph")));; + + DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false; + } + } + } + + if (FunctionList.Num() > 0) + { + // find the ubergraph in the function list + FKismetFunctionContext* UbergraphFunctionContext = nullptr; + for (FKismetFunctionContext& FunctionContext : FunctionList) + { + if (FunctionList[0].Function->GetName().StartsWith(TEXT("ExecuteUbergraph"))) + { + UbergraphFunctionContext = &FunctionContext; + break; + } + } + + if (UbergraphFunctionContext) + { + // run through the per-node compiled statements looking for struct-sets used by anim nodes + for (auto& StatementPair : UbergraphFunctionContext->StatementsPerNode) + { + if (UK2Node_StructMemberSet* StructMemberSetNode = Cast(StatementPair.Key)) + { + UObject* SourceNode = MessageLog.FindSourceObject(StructMemberSetNode); + + if (SourceNode && StructMemberSetNode->StructType->IsChildOf(FAnimNode_Base::StaticStruct())) + { + for (FBlueprintCompiledStatement* Statement : StatementPair.Value) + { + if (Statement->Type == KCST_CallFunction && Statement->FunctionToCall) + { + // pure function? + const bool bPureFunctionCall = Statement->FunctionToCall->HasAnyFunctionFlags(FUNC_BlueprintPure); + + // function called on something other than function library or anim instance? + UClass* FunctionClass = CastChecked(Statement->FunctionToCall->GetOuter()); + const bool bFunctionLibraryCall = FunctionClass->IsChildOf(); + const bool bAnimInstanceCall = FunctionClass->IsChildOf(); + + // Whitelisted/blacklisted? Some functions are not really 'pure', so we give people the opportunity to mark them up. + // Mark up the class if it is generally thread safe, then unsafe functions can be marked up individually. We assume + // that classes are unsafe by default, as well as if they are marked up NotBlueprintThreadSafe. + const bool bClassThreadSafe = FunctionClass->HasMetaData(TEXT("BlueprintThreadSafe")); + const bool bClassNotThreadSafe = FunctionClass->HasMetaData(TEXT("NotBlueprintThreadSafe")) || !FunctionClass->HasMetaData(TEXT("BlueprintThreadSafe")); + const bool bFunctionThreadSafe = Statement->FunctionToCall->HasMetaData(TEXT("BlueprintThreadSafe")); + const bool bFunctionNotThreadSafe = Statement->FunctionToCall->HasMetaData(TEXT("NotBlueprintThreadSafe")); + + const bool bThreadSafe = (bClassThreadSafe && !bFunctionNotThreadSafe) || (bClassNotThreadSafe && bFunctionThreadSafe); + + const bool bValidForUsage = bPureFunctionCall && bThreadSafe && (bFunctionLibraryCall || bAnimInstanceCall); + + if (!bValidForUsage) + { + UEdGraphNode* FunctionNode = nullptr; + if (Statement->FunctionContext && Statement->FunctionContext->SourcePin) + { + FunctionNode = Statement->FunctionContext->SourcePin->GetOwningNode(); + } + else if (Statement->LHS && Statement->LHS->SourcePin) + { + FunctionNode = Statement->LHS->SourcePin->GetOwningNode(); + } + + if (FunctionNode) + { + MessageLog.Warning(*LOCTEXT("NotThreadSafeWarningNodeContext", "Node @@ uses potentially thread-unsafe call @@. Disable threaded update or use a thread-safe call. Function may need BlueprintThreadSafe metadata adding.").ToString(), SourceNode, FunctionNode) + ->AddToken(FDocumentationToken::Create(TEXT("Engine/Animation/AnimBlueprints/AnimGraph"))); + } + else if (Statement->FunctionToCall) + { + MessageLog.Warning(*FText::Format(LOCTEXT("NotThreadSafeWarningFunctionContext", "Node @@ uses potentially thread-unsafe call {0}. Disable threaded update or use a thread-safe call. Function may need BlueprintThreadSafe metadata adding."), Statement->FunctionToCall->GetDisplayNameText()).ToString(), SourceNode) + ->AddToken(FDocumentationToken::Create(TEXT("Engine/Animation/AnimBlueprints/AnimGraph"))); + } + else + { + MessageLog.Warning(*LOCTEXT("NotThreadSafeWarningUnknownContext", "Node @@ uses potentially thread-unsafe call. Disable threaded update or use a thread-safe call.").ToString(), SourceNode) + ->AddToken(FDocumentationToken::Create(TEXT("Engine/Animation/AnimBlueprints/AnimGraph"))); + } + + DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false; + } + } + } + } + } + } + } + } + } + } +} + +void FAnimBlueprintCompilerContext::ExpandSplitPins(UEdGraph* InGraph) +{ + for (TArray::TIterator NodeIt(InGraph->Nodes); NodeIt; ++NodeIt) + { + UK2Node* K2Node = Cast(*NodeIt); + if (K2Node != nullptr) + { + K2Node->ExpandSplitPins(*this, InGraph); + } + } +} + +// Merges in any all ubergraph pages into the gathering ubergraph +void FAnimBlueprintCompilerContext::MergeUbergraphPagesIn(UEdGraph* Ubergraph) +{ + Super::MergeUbergraphPagesIn(Ubergraph); + + if (bIsDerivedAnimBlueprint) + { + // Skip any work related to an anim graph, it's all done by the parent class + } + else + { + // Move all animation graph nodes and associated pure logic chains into the consolidated event graph + auto MoveGraph = [this](UEdGraph* InGraph) + { + if (InGraph->Schema->IsChildOf(UAnimationGraphSchema::StaticClass())) + { + // Merge all the animation nodes, contents, etc... into the ubergraph + UEdGraph* ClonedGraph = FEdGraphUtilities::CloneGraph(InGraph, NULL, &MessageLog, true); + + // Prune the graph up-front + const bool bIncludePotentialRootNodes = false; + PruneIsolatedNodes(ClonedGraph, bIncludePotentialRootNodes); + + const bool bIsLoading = Blueprint->bIsRegeneratingOnLoad || IsAsyncLoading(); + const bool bIsCompiling = Blueprint->bBeingCompiled; + ClonedGraph->MoveNodesToAnotherGraph(ConsolidatedEventGraph, bIsLoading, bIsCompiling); + + // Move subgraphs too + ConsolidatedEventGraph->SubGraphs.Append(ClonedGraph->SubGraphs); + } + }; + + for (UEdGraph* Graph : Blueprint->FunctionGraphs) + { + MoveGraph(Graph); + } + + for(FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) + { + for(UEdGraph* Graph : InterfaceDesc.Graphs) + { + MoveGraph(Graph); + } + } + + // Make sure we expand any split pins here before we process animation nodes. + ExpandSplitPins(ConsolidatedEventGraph); + + // Compile the animation graph + ProcessAllAnimationNodes(); + } +} + +void FAnimBlueprintCompilerContext::ProcessOneFunctionGraph(UEdGraph* SourceGraph, bool bInternalFunction) +{ + if (SourceGraph->Schema->IsChildOf(UAnimationGraphSchema::StaticClass())) + { + // Animation graph + // Do nothing, as this graph has already been processed + } + else + { + bool bProcessGraph = true; + + // Let each subsystem opt out + ForEachSubsystem([SourceGraph, &bProcessGraph](UAnimBlueprintCompilerSubsystem* InSubsystem) + { + bProcessGraph &= InSubsystem->ShouldProcessFunctionGraph(SourceGraph); + }); + + if(bProcessGraph) + { + // Let the regular K2 compiler handle this one + Super::ProcessOneFunctionGraph(SourceGraph, bInternalFunction); + } + } +} + +void FAnimBlueprintCompilerContext::EnsureProperGeneratedClass(UClass*& TargetUClass) +{ + if( TargetUClass && !((UObject*)TargetUClass)->IsA(UAnimBlueprintGeneratedClass::StaticClass()) ) + { + FKismetCompilerUtilities::ConsignToOblivion(TargetUClass, Blueprint->bIsRegeneratingOnLoad); + TargetUClass = NULL; + } +} + +void FAnimBlueprintCompilerContext::SpawnNewClass(const FString& NewClassName) +{ + NewAnimBlueprintClass = FindObject(Blueprint->GetOutermost(), *NewClassName); + + if (NewAnimBlueprintClass == NULL) + { + NewAnimBlueprintClass = NewObject(Blueprint->GetOutermost(), FName(*NewClassName), RF_Public | RF_Transactional); + } + else + { + // Already existed, but wasn't linked in the Blueprint yet due to load ordering issues + FBlueprintCompileReinstancer::Create(NewAnimBlueprintClass); + } + NewClass = NewAnimBlueprintClass; + + // Add any subsystem classes that need to be added without paying heed to node connectivity + ForEachSubsystem([this](UAnimBlueprintCompilerSubsystem* InSubsystem) + { + TArray> SubsystemClasses; + InSubsystem->GetRequiredClassSubsystems(SubsystemClasses); + AddSubsystemClasses(SubsystemClasses); + }); + + // Give each subsystem a crack + ForEachSubsystem([this](UAnimBlueprintCompilerSubsystem* InSubsystem) + { + InSubsystem->StartCompilingClass(NewAnimBlueprintClass); + }); +} + +void FAnimBlueprintCompilerContext::OnPostCDOCompiled() +{ + for (UAnimBlueprintGeneratedClass* ClassWithInputHandlers = NewAnimBlueprintClass; ClassWithInputHandlers != nullptr; ClassWithInputHandlers = Cast(ClassWithInputHandlers->GetSuperClass())) + { + FExposedValueHandler::ClassInitialization(ClassWithInputHandlers->EvaluateGraphExposedInputs, NewAnimBlueprintClass->ClassDefaultObject); + + ClassWithInputHandlers->LinkFunctionsToDefaultObjectNodes(NewAnimBlueprintClass->ClassDefaultObject); + } +} + +void FAnimBlueprintCompilerContext::OnNewClassSet(UBlueprintGeneratedClass* ClassToUse) +{ + NewAnimBlueprintClass = CastChecked(ClassToUse); +} + +void FAnimBlueprintCompilerContext::CleanAndSanitizeClass(UBlueprintGeneratedClass* ClassToClean, UObject*& InOldCDO) +{ + Super::CleanAndSanitizeClass(ClassToClean, InOldCDO); + + // Make sure our typed pointer is set + check(ClassToClean == NewClass && NewAnimBlueprintClass == NewClass); + + NewAnimBlueprintClass->AnimBlueprintDebugData = FAnimBlueprintDebugData(); + + // Reset the baked data + //@TODO: Move this into PurgeClass + NewAnimBlueprintClass->BakedStateMachines.Empty(); + NewAnimBlueprintClass->AnimNotifies.Empty(); + NewAnimBlueprintClass->AnimBlueprintFunctions.Empty(); + NewAnimBlueprintClass->OrderedSavedPoseIndicesMap.Empty(); + NewAnimBlueprintClass->AnimNodeProperties.Empty(); + NewAnimBlueprintClass->LinkedAnimGraphNodeProperties.Empty(); + NewAnimBlueprintClass->LinkedAnimLayerNodeProperties.Empty(); + NewAnimBlueprintClass->PreUpdateNodeProperties.Empty(); + NewAnimBlueprintClass->DynamicResetNodeProperties.Empty(); + NewAnimBlueprintClass->StateMachineNodeProperties.Empty(); + NewAnimBlueprintClass->InitializationNodeProperties.Empty(); + NewAnimBlueprintClass->EvaluateGraphExposedInputs.Empty(); + NewAnimBlueprintClass->GraphAssetPlayerInformation.Empty(); + NewAnimBlueprintClass->GraphBlendOptions.Empty(); + NewAnimBlueprintClass->Subsystems.Empty(); + NewAnimBlueprintClass->SubsystemMap.Empty(); + NewAnimBlueprintClass->SubsystemInterfaceMap.Empty(); + NewAnimBlueprintClass->SubsystemProperties.Empty(); + + // Copy over runtime data from the blueprint to the class + NewAnimBlueprintClass->TargetSkeleton = AnimBlueprint->TargetSkeleton; + + UAnimBlueprint* RootAnimBP = UAnimBlueprint::FindRootAnimBlueprint(AnimBlueprint); + bIsDerivedAnimBlueprint = RootAnimBP != NULL; + + // Add any class subsystems that need to be added without paying heed to node connectivity + ForEachSubsystem([this](UAnimBlueprintCompilerSubsystem* InSubsystem) + { + TArray> SubsystemClasses; + InSubsystem->GetRequiredClassSubsystems(SubsystemClasses); + AddSubsystemClasses(SubsystemClasses); + }); + + // Give each subsystem a crack + ForEachSubsystem([this](UAnimBlueprintCompilerSubsystem* InSubsystem) + { + InSubsystem->StartCompilingClass(NewAnimBlueprintClass); + }); +} + +void FAnimBlueprintCompilerContext::FinishCompilingClass(UClass* Class) +{ + const UAnimBlueprint* PossibleRoot = UAnimBlueprint::FindRootAnimBlueprint(AnimBlueprint); + const UAnimBlueprint* Src = PossibleRoot ? PossibleRoot : AnimBlueprint; + + UAnimBlueprintGeneratedClass* AnimBlueprintGeneratedClass = CastChecked(Class); + AnimBlueprintGeneratedClass->SyncGroupNames.Reset(); + AnimBlueprintGeneratedClass->SyncGroupNames.Reserve(Src->Groups.Num()); + for (const FAnimGroupInfo& GroupInfo : Src->Groups) + { + AnimBlueprintGeneratedClass->SyncGroupNames.Add(GroupInfo.Name); + } + + // Add graph blend options to class if blend values were actually customized + auto AddBlendOptions = [AnimBlueprintGeneratedClass](UEdGraph* Graph) + { + UAnimationGraph* AnimGraph = Cast(Graph); + if (AnimGraph && (AnimGraph->BlendOptions.BlendInTime >= 0.0f || AnimGraph->BlendOptions.BlendOutTime >= 0.0f)) + { + AnimBlueprintGeneratedClass->GraphBlendOptions.Add(AnimGraph->GetFName(), AnimGraph->BlendOptions); + } + }; + + + for (UEdGraph* Graph : Blueprint->FunctionGraphs) + { + AddBlendOptions(Graph); + } + + for (FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) + { + if (InterfaceDesc.Interface->IsChildOf()) + { + for (UEdGraph* Graph : InterfaceDesc.Graphs) + { + AddBlendOptions(Graph); + } + } + } + + // Give each subsystem a crack + ForEachSubsystem([Class](UAnimBlueprintCompilerSubsystem* InSubsystem) + { + InSubsystem->FinishCompilingClass(Class); + }); + + Super::FinishCompilingClass(Class); +} + +void FAnimBlueprintCompilerContext::PostCompile() +{ + Super::PostCompile(); + + for (UPoseWatch* PoseWatch : AnimBlueprint->PoseWatches) + { + AnimationEditorUtils::SetPoseWatch(PoseWatch, AnimBlueprint); + } + + UAnimBlueprintGeneratedClass* AnimBlueprintGeneratedClass = CastChecked(NewClass); + if(UAnimInstance* DefaultAnimInstance = Cast(AnimBlueprintGeneratedClass->GetDefaultObject())) + { + // iterate all anim node and call PostCompile + const USkeleton* CurrentSkeleton = AnimBlueprint->TargetSkeleton; + for (FStructProperty* Property : TFieldRange(AnimBlueprintGeneratedClass, EFieldIteratorFlags::IncludeSuper)) + { + if (Property->Struct->IsChildOf(FAnimNode_Base::StaticStruct())) + { + FAnimNode_Base* AnimNode = Property->ContainerPtrToValuePtr(DefaultAnimInstance); + AnimNode->PostCompile(CurrentSkeleton); + } + } + } +} + +void FAnimBlueprintCompilerContext::PostCompileDiagnostics() +{ + FKismetCompilerContext::PostCompileDiagnostics(); + +#if WITH_EDITORONLY_DATA // ANIMINST_PostCompileValidation + // See if AnimInstance implements a PostCompileValidation Class. + // If so, instantiate it, and let it perform Validation of our newly compiled AnimBlueprint. + if (const UAnimInstance* const DefaultAnimInstance = Cast(NewAnimBlueprintClass->GetDefaultObject())) + { + if (DefaultAnimInstance->PostCompileValidationClassName.IsValid()) + { + UClass* PostCompileValidationClass = LoadClass(nullptr, *DefaultAnimInstance->PostCompileValidationClassName.ToString()); + if (PostCompileValidationClass) + { + UAnimBlueprintPostCompileValidation* PostCompileValidation = NewObject(GetTransientPackage(), PostCompileValidationClass); + if (PostCompileValidation) + { + FAnimBPCompileValidationParams PCV_Params(DefaultAnimInstance, NewAnimBlueprintClass, MessageLog, AllocatedNodePropertiesToNodes); + PostCompileValidation->DoPostCompileValidation(PCV_Params); + } + } + } + } +#endif // WITH_EDITORONLY_DATA + + if (!bIsDerivedAnimBlueprint) + { + bool bUsingCopyPoseFromMesh = false; + + // Run thru all nodes and make sure they like the final results + for (auto NodeIt = AllocatedAnimNodeIndices.CreateConstIterator(); NodeIt; ++NodeIt) + { + if (UAnimGraphNode_Base* Node = NodeIt.Key()) + { + Node->ValidateAnimNodePostCompile(MessageLog, NewAnimBlueprintClass, NodeIt.Value()); + bUsingCopyPoseFromMesh = bUsingCopyPoseFromMesh || Node->UsingCopyPoseFromMesh(); + } + } + + // Update CDO + if (UAnimInstance* const DefaultAnimInstance = Cast(NewAnimBlueprintClass->GetDefaultObject())) + { + DefaultAnimInstance->bUsingCopyPoseFromMesh = bUsingCopyPoseFromMesh; + } + } +} + +void FAnimBlueprintCompilerContext::CreateAnimGraphStubFunctions() +{ + TArray NewGraphs; + + auto CreateStubForGraph = [this, &NewGraphs](UEdGraph* InGraph) + { + if(InGraph->Schema->IsChildOf(UAnimationGraphSchema::StaticClass())) + { + // Check to see if we are implementing an interface, and if so, use the signature from that graph instead + // as we may not have yet been conformed to it (it happens later in compilation) + UEdGraph* GraphToUseforSignature = InGraph; + for(const FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) + { + UClass* InterfaceClass = InterfaceDesc.Interface; + if(InterfaceClass) + { + if(UAnimBlueprint* InterfaceAnimBlueprint = Cast(InterfaceClass->ClassGeneratedBy)) + { + TArray AllGraphs; + InterfaceAnimBlueprint->GetAllGraphs(AllGraphs); + UEdGraph** FoundSourceGraph = AllGraphs.FindByPredicate([InGraph](UEdGraph* InGraphToCheck){ return InGraphToCheck->GetFName() == InGraph->GetFName(); }); + if(FoundSourceGraph) + { + GraphToUseforSignature = *FoundSourceGraph; + break; + } + } + } + } + + // Find the root and linked input pose nodes + TArray Roots; + GraphToUseforSignature->GetNodesOfClass(Roots); + + TArray LinkedInputPoseNodes; + GraphToUseforSignature->GetNodesOfClass(LinkedInputPoseNodes); + + if(Roots.Num() > 0) + { + UAnimGraphNode_Root* RootNode = Roots[0]; + + // Make sure there was only one root node + for (int32 RootIndex = 1; RootIndex < Roots.Num(); ++RootIndex) + { + MessageLog.Error( + *LOCTEXT("ExpectedOneRoot_Error", "Expected only one root node in graph @@, but found both @@ and @@").ToString(), + InGraph, + RootNode, + Roots[RootIndex] + ); + } + + // Verify no duplicate inputs + for(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode0 : LinkedInputPoseNodes) + { + for(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode1 : LinkedInputPoseNodes) + { + if(LinkedInputPoseNode0 != LinkedInputPoseNode1) + { + if(LinkedInputPoseNode0->Node.Name == LinkedInputPoseNode1->Node.Name) + { + MessageLog.Error( + *LOCTEXT("DuplicateInputNode_Error", "Found duplicate input node @@ in graph @@").ToString(), + LinkedInputPoseNode1, + InGraph + ); + } + } + } + } + + // Create a simple generated graph for our anim 'function'. Decorate it to avoid naming conflicts with the original graph. + FName NewGraphName(*(GraphToUseforSignature->GetName() + ANIM_FUNC_DECORATOR)); + + UEdGraph* StubGraph = NewObject(Blueprint, NewGraphName); + NewGraphs.Add(StubGraph); + StubGraph->Schema = UEdGraphSchema_K2::StaticClass(); + StubGraph->SetFlags(RF_Transient); + + // Add an entry node + UK2Node_FunctionEntry* EntryNode = SpawnIntermediateNode(RootNode, StubGraph); + EntryNode->NodePosX = -200; + EntryNode->CustomGeneratedFunctionName = GraphToUseforSignature->GetFName(); // Note that the function generated from this temporary graph is undecorated + EntryNode->MetaData.Category = RootNode->Node.Group == NAME_None ? FText::GetEmpty() : FText::FromName(RootNode->Node.Group); + + // Add linked input poses as parameters + for(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode : LinkedInputPoseNodes) + { + // Add user defined pins for each linked input pose + TSharedPtr PosePinInfo = MakeShared(); + PosePinInfo->PinType = UAnimationGraphSchema::MakeLocalSpacePosePin(); + PosePinInfo->PinName = LinkedInputPoseNode->Node.Name; + PosePinInfo->DesiredPinDirection = EGPD_Output; + EntryNode->UserDefinedPins.Add(PosePinInfo); + + // Add user defined pins for each linked input pose parameter + for(UEdGraphPin* LinkedInputPoseNodePin : LinkedInputPoseNode->Pins) + { + if(!LinkedInputPoseNodePin->bOrphanedPin && LinkedInputPoseNodePin->Direction == EGPD_Output && !UAnimationGraphSchema::IsPosePin(LinkedInputPoseNodePin->PinType)) + { + TSharedPtr ParameterPinInfo = MakeShared(); + ParameterPinInfo->PinType = LinkedInputPoseNodePin->PinType; + ParameterPinInfo->PinName = LinkedInputPoseNodePin->PinName; + ParameterPinInfo->DesiredPinDirection = EGPD_Output; + EntryNode->UserDefinedPins.Add(ParameterPinInfo); + } + } + } + EntryNode->AllocateDefaultPins(); + + UEdGraphPin* EntryExecPin = EntryNode->FindPinChecked(UEdGraphSchema_K2::PN_Then, EGPD_Output); + + UK2Node_FunctionResult* ResultNode = SpawnIntermediateNode(RootNode, StubGraph); + ResultNode->NodePosX = 200; + + // Add root as the 'return value' + TSharedPtr PinInfo = MakeShared(); + PinInfo->PinType = UAnimationGraphSchema::MakeLocalSpacePosePin(); + PinInfo->PinName = GraphToUseforSignature->GetFName(); + PinInfo->DesiredPinDirection = EGPD_Input; + ResultNode->UserDefinedPins.Add(PinInfo); + + ResultNode->AllocateDefaultPins(); + + UEdGraphPin* ResultExecPin = ResultNode->FindPinChecked(UEdGraphSchema_K2::PN_Execute, EGPD_Input); + + // Link up entry to exit + EntryExecPin->MakeLinkTo(ResultExecPin); + } + else + { + MessageLog.Error(*LOCTEXT("NoRootNodeFound_Error", "Could not find a root node for the graph @@").ToString(), InGraph); + } + } + }; + + for(UEdGraph* Graph : Blueprint->FunctionGraphs) + { + CreateStubForGraph(Graph); + } + + for(FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) + { + for(UEdGraph* Graph : InterfaceDesc.Graphs) + { + CreateStubForGraph(Graph); + } + } + + Blueprint->FunctionGraphs.Append(NewGraphs); + GeneratedStubGraphs.Append(NewGraphs); +} + +void FAnimBlueprintCompilerContext::DestroyAnimGraphStubFunctions() +{ + Blueprint->FunctionGraphs.RemoveAll([this](UEdGraph* InGraph) + { + return GeneratedStubGraphs.Contains(InGraph); + }); + + GeneratedStubGraphs.Empty(); +} + +void FAnimBlueprintCompilerContext::PrecompileFunction(FKismetFunctionContext& Context, EInternalCompilerFlags InternalFlags) +{ + Super::PrecompileFunction(Context, InternalFlags); + + if(Context.Function) + { + auto CompareEntryPointName = + [Function = Context.Function](UEdGraph* InGraph) + { + if(InGraph) + { + TArray EntryPoints; + InGraph->GetNodesOfClass(EntryPoints); + if(EntryPoints.Num() == 1 && EntryPoints[0]) + { + return EntryPoints[0]->CustomGeneratedFunctionName == Function->GetFName(); + } + } + return true; + }; + + if(GeneratedStubGraphs.ContainsByPredicate(CompareEntryPointName)) + { + Context.Function->SetMetaData(FBlueprintMetadata::MD_BlueprintInternalUseOnly, TEXT("true")); + Context.Function->SetMetaData(FBlueprintMetadata::MD_AnimBlueprintFunction, TEXT("true")); + } + } +} + +void FAnimBlueprintCompilerContext::SetCalculatedMetaDataAndFlags(UFunction* Function, UK2Node_FunctionEntry* EntryNode, const UEdGraphSchema_K2* K2Schema) +{ + Super::SetCalculatedMetaDataAndFlags(Function, EntryNode, K2Schema); + + if(Function) + { + auto CompareEntryPointName = + [Function](UEdGraph* InGraph) + { + if(InGraph) + { + TArray EntryPoints; + InGraph->GetNodesOfClass(EntryPoints); + if(EntryPoints.Num() == 1 && EntryPoints[0]) + { + return EntryPoints[0]->CustomGeneratedFunctionName == Function->GetFName(); + } + } + return true; + }; + + // Match by name to generated graph's entry points + if(GeneratedStubGraphs.ContainsByPredicate(CompareEntryPointName)) + { + Function->SetMetaData(FBlueprintMetadata::MD_BlueprintInternalUseOnly, TEXT("true")); + Function->SetMetaData(FBlueprintMetadata::MD_AnimBlueprintFunction, TEXT("true")); + } + } +} + +void FAnimBlueprintCompilerContext::ForEachSubsystem(TFunctionRef InFunction) +{ + const TArray& Subsystems = AnimBlueprintCompilerSubsystemCollection.GetSubsystemArray(TSubclassOf(UAnimBlueprintCompilerSubsystem::StaticClass())); + for(UAnimBlueprintCompilerSubsystem* Subsystem : Subsystems) + { + InFunction(Subsystem); + } +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompiler.h b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompiler.h new file mode 100644 index 000000000000..88421c398423 --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompiler.h @@ -0,0 +1,181 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "KismetCompiler.h" +#include "Animation/AnimNodeBase.h" +#include "AnimGraphNode_Base.h" +#include "KismetCompilerModule.h" +#include "AnimBlueprintCompilerSubsystemCollection.h" + +class UAnimationGraphSchema; +class UAnimGraphNode_SaveCachedPose; +class UAnimGraphNode_StateMachineBase; +class UAnimGraphNode_StateResult; +class UAnimGraphNode_CustomProperty; + +class UAnimGraphNode_UseCachedPose; +class UAnimStateTransitionNode; +class UK2Node_CallFunction; + +// +// Forward declarations. +// +class UAnimGraphNode_SaveCachedPose; +class UAnimGraphNode_UseCachedPose; +class UAnimGraphNode_LinkedInputPose; +class UAnimGraphNode_LinkedAnimGraphBase; +class UAnimGraphNode_LinkedAnimGraph; +class UAnimGraphNode_Root; + +class FStructProperty; +class UBlueprintGeneratedClass; +struct FPoseLinkMappingRecord; +struct FAnimGraphNodePropertyBinding; +class FAnimBlueprintCompilerContext; + +#define ANIM_FUNC_DECORATOR TEXT("__AnimFunc") + +////////////////////////////////////////////////////////////////////////// +// FAnimBlueprintCompilerContext +class FAnimBlueprintCompilerContext : public FKismetCompilerContext +{ + friend class UAnimBlueprintCompilerSubsystem; + +protected: + typedef FKismetCompilerContext Super; +public: + FAnimBlueprintCompilerContext(UAnimBlueprint* SourceSketch, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompileOptions); + virtual ~FAnimBlueprintCompilerContext(); + + virtual void PostCompile() override; + + // Get a Subsystem of specified type + template + TSubsystemClass* GetSubsystem() const + { + checkSlow(this != nullptr); + return AnimBlueprintCompilerSubsystemCollection.GetSubsystem(TSubsystemClass::StaticClass()); + } + + // Find the first subsystem implementing the specified interface + template + InterfaceClass* FindSubsystemWithInterface() const + { + checkSlow(this != nullptr); + return AnimBlueprintCompilerSubsystemCollection.FindSubsystemWithInterface(InterfaceClass::UClassType::StaticClass()); + } + + // Allow nodes to create variables by exposing this + using FKismetCompilerContext::CreateVariable; + + // Expose expansion functionality + using FKismetCompilerContext::ExpansionStep; + +protected: + // Implementation of FKismetCompilerContext interface + virtual void CreateClassVariablesFromBlueprint() override; + virtual UEdGraphSchema_K2* CreateSchema() override; + virtual void MergeUbergraphPagesIn(UEdGraph* Ubergraph) override; + virtual void ProcessOneFunctionGraph(UEdGraph* SourceGraph, bool bInternalFunction = false) override; + virtual void SpawnNewClass(const FString& NewClassName) override; + virtual void OnNewClassSet(UBlueprintGeneratedClass* ClassToUse) override; + virtual void OnPostCDOCompiled() override; + virtual void CopyTermDefaultsToDefaultObject(UObject* DefaultObject) override; + virtual void PostCompileDiagnostics() override; + virtual void EnsureProperGeneratedClass(UClass*& TargetClass) override; + virtual void CleanAndSanitizeClass(UBlueprintGeneratedClass* ClassToClean, UObject*& InOldCDO) override; + virtual void FinishCompilingClass(UClass* Class) override; + virtual void PrecompileFunction(FKismetFunctionContext& Context, EInternalCompilerFlags InternalFlags) override; + virtual void SetCalculatedMetaDataAndFlags(UFunction* Function, UK2Node_FunctionEntry* EntryNode, const UEdGraphSchema_K2* Schema ) override; + virtual bool ShouldForceKeepNode(const UEdGraphNode* Node) const override; + virtual void PostExpansionStep(UEdGraph* Graph) override; + // End of FKismetCompilerContext interface + +protected: + typedef TArray UEdGraphPinArray; + +protected: + UAnimBlueprintGeneratedClass* NewAnimBlueprintClass; + UAnimBlueprint* AnimBlueprint; + + UAnimationGraphSchema* AnimSchema; + + // Map of allocated v3 nodes that are members of the class + TMap AllocatedAnimNodes; + TMap AllocatedNodePropertiesToNodes; + TMap AllocatedPropertiesByIndex; + + // Map of true source objects (user edited ones) to the cloned ones that are actually compiled + TMap SourceNodeToProcessedNodeMap; + + // Index of the nodes (must match up with the runtime discovery process of nodes, which runs thru the property chain) + int32 AllocateNodeIndexCounter; + TMap AllocatedAnimNodeIndices; + + // Map from pose link LinkID address + //@TODO: Bad structure for a list of these + TArray ValidPoseLinkList; + + // Stub graphs we generated for animation graph functions + TArray GeneratedStubGraphs; + + // True if any parent class is also generated from an animation blueprint + bool bIsDerivedAnimBlueprint; + + // Subsystems that this context is hosting + FAnimBlueprintCompilerSubsystemCollection AnimBlueprintCompilerSubsystemCollection; + + // Expose compile options to subsystems + using FKismetCompilerContext::CompileOptions; + +private: + // Run a function for each subsystem we have registered + void ForEachSubsystem(TFunctionRef InFunction); + + // Run a function on the passed-in graph and each subgraph of it + void ForAllSubGraphs(UEdGraph* InGraph, TFunctionRef InPerGraphFunction); + + // Prunes any nodes that aren't reachable via a pose link + void PruneIsolatedAnimationNodes(const TArray& RootSet, TArray& GraphNodes); + + // Compiles one animation node + void ProcessAnimationNode(UAnimGraphNode_Base* VisualAnimNode); + + // Compiles one root node + void ProcessRoot(UAnimGraphNode_Root* Root); + + // Compiles an entire animation graph + void ProcessAllAnimationNodes(); + + // Processes all the supplied anim nodes + void ProcessAnimationNodes(TArray& AnimNodeList); + + // Gets all anim graph nodes that are piped into the provided node (traverses input pins) + void GetLinkedAnimNodes(UAnimGraphNode_Base* InGraphNode, TArray& LinkedAnimNodes); + void GetLinkedAnimNodes_TraversePin(UEdGraphPin* InPin, TArray& LinkedAnimNodes); + void GetLinkedAnimNodes_ProcessAnimNode(UAnimGraphNode_Base* AnimNode, TArray& LinkedAnimNodes); + + // Returns the allocation index of the specified node, processing it if it was pending + int32 GetAllocationIndexOfNode(UAnimGraphNode_Base* VisualAnimNode); + + // Create transient stub functions for each anim graph we are compiling + void CreateAnimGraphStubFunctions(); + + // Clean up transient stub functions + void DestroyAnimGraphStubFunctions(); + + // Expands split pins for a graph + void ExpandSplitPins(UEdGraph* InGraph); + + // Add the subsystem classes to the currently compiling class + void AddSubsystemClasses(const TArray>& SubsystemClasses); + + // Process all the gathered class subsystems + void ProcessClassSubsystems(); + + // Process a single class subsystem + void ProcessClassSubsystem(UAnimBlueprintClassSubsystem* InSubsystem); +}; + diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem.cpp new file mode 100644 index 000000000000..adbab6304ea6 --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem.cpp @@ -0,0 +1,116 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "AnimBlueprintCompilerSubsystem.h" +#include "AnimBlueprintCompiler.h" +#include "AnimBlueprintCompilerSubsystemCollection.h" + +void UAnimBlueprintCompilerSubsystem::Initialize(FSubsystemCollectionBase& InCollection) +{ + FAnimBlueprintCompilerSubsystemCollection& AnimBlueprintCompilerSubsystemCollection = *static_cast(&InCollection); + CompilerContext = AnimBlueprintCompilerSubsystemCollection.CompilerContext; +} + +UBlueprint* UAnimBlueprintCompilerSubsystem::GetBlueprint() const +{ + return CompilerContext->Blueprint; +} + +UAnimBlueprint* UAnimBlueprintCompilerSubsystem::GetAnimBlueprint() const +{ + return CompilerContext->AnimBlueprint; +} + +UAnimBlueprintGeneratedClass* UAnimBlueprintCompilerSubsystem::GetNewAnimBlueprintClass() const +{ + return CompilerContext->NewAnimBlueprintClass; +} + +FCompilerResultsLog& UAnimBlueprintCompilerSubsystem::GetMessageLog() const +{ + return CompilerContext->MessageLog; +} + +UEdGraph* UAnimBlueprintCompilerSubsystem::GetConsolidatedEventGraph() const +{ + return CompilerContext->ConsolidatedEventGraph; +} + +bool UAnimBlueprintCompilerSubsystem::ValidateGraphIsWellFormed(UEdGraph* Graph) const +{ + return CompilerContext->ValidateGraphIsWellFormed(Graph); +} + +int32 UAnimBlueprintCompilerSubsystem::GetAllocationIndexOfNode(UAnimGraphNode_Base* VisualAnimNode) +{ + return CompilerContext->GetAllocationIndexOfNode(VisualAnimNode); +} + +void UAnimBlueprintCompilerSubsystem::AddPoseLinkMappingRecord(const FPoseLinkMappingRecord& InRecord) +{ + CompilerContext->ValidPoseLinkList.Add(InRecord); +} + +void UAnimBlueprintCompilerSubsystem::GetLinkedAnimNodes(UAnimGraphNode_Base* InGraphNode, TArray &LinkedAnimNodes) +{ + CompilerContext->GetLinkedAnimNodes(InGraphNode, LinkedAnimNodes); +} + +const TMap& UAnimBlueprintCompilerSubsystem::GetAllocatedAnimNodeIndices() const +{ + return CompilerContext->AllocatedAnimNodeIndices; +} + +const TMap& UAnimBlueprintCompilerSubsystem::GetSourceNodeToProcessedNodeMap() const +{ + return CompilerContext->SourceNodeToProcessedNodeMap; +} + +const TMap& UAnimBlueprintCompilerSubsystem::GetAllocatedPropertiesByIndex() const +{ + return CompilerContext->AllocatedPropertiesByIndex; +} + +const TMap& UAnimBlueprintCompilerSubsystem::GetAllocatedPropertiesByNode() const +{ + return CompilerContext->AllocatedAnimNodes; +} + +void UAnimBlueprintCompilerSubsystem::ExpandSplitPins(UEdGraph* InGraph) +{ + CompilerContext->ExpandSplitPins(InGraph); +} + +void UAnimBlueprintCompilerSubsystem::PruneIsolatedAnimationNodes(const TArray& RootSet, TArray& GraphNodes) +{ + CompilerContext->PruneIsolatedAnimationNodes(RootSet, GraphNodes); +} + +void UAnimBlueprintCompilerSubsystem::ExpansionStep(UEdGraph* Graph, bool bAllowUbergraphExpansions) +{ + CompilerContext->ExpansionStep(Graph, bAllowUbergraphExpansions); +} + +void UAnimBlueprintCompilerSubsystem::ProcessAnimationNodes(TArray& AnimNodeList) +{ + CompilerContext->ProcessAnimationNodes(AnimNodeList); +} + +FKismetCompilerContext* UAnimBlueprintCompilerSubsystem::GetKismetCompiler() const +{ + return static_cast(CompilerContext); +} + +UAnimBlueprintCompilerSubsystem* UAnimBlueprintCompilerSubsystem::GetSubsystemInternal(const FKismetCompilerContext* InCompilerContext, TSubclassOf InClass) +{ + return static_cast(InCompilerContext)->AnimBlueprintCompilerSubsystemCollection.GetSubsystem(InClass); +} + +UAnimBlueprintCompilerSubsystem* UAnimBlueprintCompilerSubsystem::FindSubsystemWithInterfaceInternal(const FKismetCompilerContext* InCompilerContext, TSubclassOf InInterfaceClass) +{ + return static_cast(InCompilerContext)->AnimBlueprintCompilerSubsystemCollection.FindSubsystemWithInterface(InInterfaceClass); +} + +const FKismetCompilerOptions& UAnimBlueprintCompilerSubsystem::GetCompileOptions() const +{ + return CompilerContext->CompileOptions; +} \ No newline at end of file diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystemCollection.h b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystemCollection.h new file mode 100644 index 000000000000..12121edee7d7 --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystemCollection.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/SubsystemCollection.h" +#include "AnimBlueprintCompilerSubsystem.h" + +class FAnimBlueprintCompilerContext; + +/** Subsystem collection for the anim blueprint compiler */ +class FAnimBlueprintCompilerSubsystemCollection : public FSubsystemCollection +{ +private: + friend class FAnimBlueprintCompilerContext; + friend class UAnimBlueprintCompilerSubsystem; + + /** Register the compiler with this collection */ + void RegisterContext(FAnimBlueprintCompilerContext* InCompilerContext) { CompilerContext = InCompilerContext; } + + /** Get the first subsystem that implements the requested interface */ + template + InterfaceClass* FindSubsystemWithInterface(TSubclassOf InInterfaceClass) const + { + const TArray& BaseArray = GetSubsystemArray(UAnimBlueprintCompilerSubsystem::StaticClass()); + for(UAnimBlueprintCompilerSubsystem* Subsystem : BaseArray) + { + if(Subsystem->GetClass()->ImplementsInterface(InInterfaceClass.Get())) + { + return Cast(Subsystem); + } + } + + return nullptr; + } + + /** The compiler this collection is registered to */ + FAnimBlueprintCompilerContext* CompilerContext; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_Base.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_Base.cpp new file mode 100644 index 000000000000..594931a916e4 --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_Base.cpp @@ -0,0 +1,1071 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "AnimBlueprintCompilerSubsystem_Base.h" +#include "AnimGraphNode_Base.h" +#include "AnimationGraphSchema.h" +#include "AnimGraphNode_CustomProperty.h" +#include "K2Node_CustomEvent.h" +#include "K2Node_VariableSet.h" +#include "K2Node_StructMemberSet.h" +#include "K2Node_StructMemberGet.h" +#include "K2Node_CallArrayFunction.h" +#include "Kismet/KismetArrayLibrary.h" +#include "K2Node_Knot.h" +#include "String/ParseTokens.h" +#include "K2Node_VariableGet.h" +#include "K2Node_BreakStruct.h" +#include "K2Node_MakeStruct.h" +#include "Kismet/KismetMathLibrary.h" +#include "K2Node_TransitionRuleGetter.h" +#include "Algo/Accumulate.h" +#include "K2Node_GetArrayItem.h" +#include "Animation/AnimNode_LinkedAnimGraph.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "IPropertyAccessCompilerSubsystem.h" +#include "IPropertyAccessEditor.h" +#include "IPropertyAccessCompiler.h" + +#define LOCTEXT_NAMESPACE "AnimBlueprintCompilerSubsystem_Base" + +void UAnimBlueprintCompilerSubsystem_Base::CopyTermDefaultsToDefaultObject(UObject* InDefaultObject) +{ + UAnimInstance* DefaultAnimInstance = Cast(InDefaultObject); + + if(DefaultAnimInstance) + { + // And patch in constant values that don't need to be re-evaluated every frame + for (auto LiteralLinkIt = ValidAnimNodePinConstants.CreateIterator(); LiteralLinkIt; ++LiteralLinkIt) + { + FEffectiveConstantRecord& ConstantRecord = *LiteralLinkIt; + + //const FString ArrayClause = (ConstantRecord.ArrayIndex != INDEX_NONE) ? FString::Printf(TEXT("[%d]"), ConstantRecord.ArrayIndex) : FString(); + //const FString ValueClause = ConstantRecord.LiteralSourcePin->GetDefaultAsString(); + //GetMessageLog().Note(*FString::Printf(TEXT("Want to set %s.%s%s to %s"), *ConstantRecord.NodeVariableProperty->GetName(), *ConstantRecord.ConstantProperty->GetName(), *ArrayClause, *ValueClause)); + + if (!ConstantRecord.Apply(DefaultAnimInstance)) + { + GetMessageLog().Error(TEXT("ICE: Failed to push literal value from @@ into CDO"), ConstantRecord.LiteralSourcePin); + } + } + + for (const FEffectiveConstantRecord& ConstantRecord : ValidAnimNodePinConstants) + { + UAnimGraphNode_Base* Node = CastChecked(ConstantRecord.LiteralSourcePin->GetOwningNode()); + UAnimGraphNode_Base* TrueNode = GetMessageLog().FindSourceObjectTypeChecked(Node); + TrueNode->BlueprintUsage = EBlueprintUsage::DoesNotUseBlueprint; + } + + for(const FEvaluationHandlerRecord& EvaluationHandler : ValidEvaluationHandlerList) + { + if(EvaluationHandler.EvaluationHandlerIdx != INDEX_NONE && EvaluationHandler.ServicedProperties.Num() > 0) + { + const FAnimNodeSinglePropertyHandler& Handler = EvaluationHandler.ServicedProperties.CreateConstIterator()->Value; + check(Handler.CopyRecords.Num() > 0); + if(Handler.CopyRecords[0].DestPin != nullptr) + { + UAnimGraphNode_Base* Node = CastChecked(Handler.CopyRecords[0].DestPin->GetOwningNode()); + UAnimGraphNode_Base* TrueNode = GetMessageLog().FindSourceObjectTypeChecked(Node); + + const FExposedValueHandler& ValueHandler = GetNewAnimBlueprintClass()->GetExposedValueHandlers()[ EvaluationHandler.EvaluationHandlerIdx ]; + TrueNode->BlueprintUsage = ValueHandler.BoundFunction != NAME_None ? EBlueprintUsage::UsesBlueprint : EBlueprintUsage::DoesNotUseBlueprint; + +#if WITH_EDITORONLY_DATA // ANIMINST_PostCompileValidation + const bool bWarnAboutBlueprintUsage = GetAnimBlueprint()->bWarnAboutBlueprintUsage || DefaultAnimInstance->PCV_ShouldWarnAboutNodesNotUsingFastPath(); + const bool bNotifyAboutBlueprintUsage = DefaultAnimInstance->PCV_ShouldNotifyAboutNodesNotUsingFastPath(); +#else + const bool bWarnAboutBlueprintUsage = GetAnimBlueprint()->bWarnAboutBlueprintUsage; + const bool bNotifyAboutBlueprintUsage = false; +#endif + if ((TrueNode->BlueprintUsage == EBlueprintUsage::UsesBlueprint) && (bWarnAboutBlueprintUsage || bNotifyAboutBlueprintUsage)) + { + const FString MessageString = LOCTEXT("BlueprintUsageWarning", "Node @@ uses Blueprint to update its values, access member variables directly or use a constant value for better performance.").ToString(); + if (bWarnAboutBlueprintUsage) + { + GetMessageLog().Warning(*MessageString, Node); + } + else + { + GetMessageLog().Note(*MessageString, Node); + } + } + } + } + } + } +} + +void UAnimBlueprintCompilerSubsystem_Base::PostExpansionStep(UEdGraph* InGraph) +{ + UEdGraph* ConsolidatedEventGraph = GetConsolidatedEventGraph(); + if(InGraph == ConsolidatedEventGraph) + { + IPropertyAccessCompilerSubsystem* PropertyAccessSubsystem = FindSubsystemWithInterface(); + + // Skip fast-path generation if the property access system is unavailable. + // Disable fast-path generation for nativized anim BPs, we dont run the VM anyways and + // the property names are 'decorated' by the backend, so records dont match. + // Note that this wont prevent property access 'binding' copy records from running, only + // old-style 'fast-path' records that are derived from BP pure chains + if(PropertyAccessSubsystem != nullptr && !GetCompileOptions().DoesRequireCppCodeGeneration()) + { + for(FEvaluationHandlerRecord& HandlerRecord : ValidEvaluationHandlerList) + { + HandlerRecord.BuildFastPathCopyRecords(*this); + + if(HandlerRecord.IsFastPath()) + { + for(UEdGraphNode* CustomEventNode : HandlerRecord.CustomEventNodes) + { + // Remove custom event nodes as we dont need it any more + ConsolidatedEventGraph->RemoveNode(CustomEventNode); + } + } + } + } + + // Cull out all anim nodes as they dont contribute to execution at all + for (int32 NodeIndex = 0; NodeIndex < ConsolidatedEventGraph->Nodes.Num(); ++NodeIndex) + { + if(UAnimGraphNode_Base* Node = Cast(ConsolidatedEventGraph->Nodes[NodeIndex])) + { + Node->BreakAllNodeLinks(); + ConsolidatedEventGraph->Nodes.RemoveAtSwap(NodeIndex); + --NodeIndex; + } + } + } +} + +void UAnimBlueprintCompilerSubsystem_Base::StartCompilingClass(UClass* InClass) +{ + IPropertyAccessCompilerSubsystem* PropertyAccessSubsystem = FindSubsystemWithInterface(); + if(PropertyAccessSubsystem) + { + if(!PreLibraryCompiledDelegateHandle.IsValid()) + { + PreLibraryCompiledDelegateHandle = PropertyAccessSubsystem->OnPreLibraryCompiled().AddLambda([this, PropertyAccessSubsystem, InClass]() + { + if(IModularFeatures::Get().IsModularFeatureAvailable("PropertyAccessEditor")) + { + IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); + + // Build the classes property access library before the library is compiled + for(FEvaluationHandlerRecord& HandlerRecord : ValidEvaluationHandlerList) + { + for(TPair& PropertyHandler : HandlerRecord.ServicedProperties) + { + for(FPropertyCopyRecord& Record : PropertyHandler.Value.CopyRecords) + { + if(Record.IsFastPath()) + { + // Check if the resolved copy + FProperty* LeafProperty = nullptr; + int32 ArrayIndex = INDEX_NONE; + EPropertyAccessResolveResult Result = PropertyAccessEditor.ResolveLeafProperty(InClass, Record.SourcePropertyPath, LeafProperty, ArrayIndex); + + // Batch all external accesses, we cant call them safely from a worker thread. + Record.LibraryBatchType = Result == EPropertyAccessResolveResult::SucceededExternal ? EPropertyAccessBatchType::Batched : EPropertyAccessBatchType::Unbatched; + Record.LibraryCopyIndex = PropertyAccessSubsystem->AddCopy(Record.SourcePropertyPath, Record.DestPropertyPath, Record.LibraryBatchType, HandlerRecord.AnimGraphNode); + } + } + } + } + } + }); + } + + if(!PostLibraryCompiledDelegateHandle.IsValid()) + { + PostLibraryCompiledDelegateHandle = PropertyAccessSubsystem->OnPostLibraryCompiled().AddLambda([this, PropertyAccessSubsystem, InClass]() + { + for(FEvaluationHandlerRecord& HandlerRecord : ValidEvaluationHandlerList) + { + // Map global copy index to batched indices + for(TPair& PropertyHandler : HandlerRecord.ServicedProperties) + { + for(FPropertyCopyRecord& CopyRecord : PropertyHandler.Value.CopyRecords) + { + if(CopyRecord.IsFastPath()) + { + CopyRecord.LibraryCopyIndex = PropertyAccessSubsystem->MapCopyIndex(CopyRecord.LibraryCopyIndex); + } + } + } + + // Patch either fast-path copy records or generated function names into the class + HandlerRecord.EvaluationHandlerIdx = GetNewAnimBlueprintClass()->EvaluateGraphExposedInputs.Num(); + FExposedValueHandler& ExposedValueHandler = GetNewAnimBlueprintClass()->EvaluateGraphExposedInputs.AddDefaulted_GetRef(); + HandlerRecord.PatchFunctionNameAndCopyRecordsInto(ExposedValueHandler); + } + }); + } + } +} + +void UAnimBlueprintCompilerSubsystem_Base::FinishCompilingClass(UClass* InClass) +{ + IPropertyAccessCompilerSubsystem* PropertyAccessSubsystem = FindSubsystemWithInterface(); + if(PropertyAccessSubsystem == nullptr) + { + // Without the property access system we need to patch generated function names here + for(FEvaluationHandlerRecord& HandlerRecord : ValidEvaluationHandlerList) + { + HandlerRecord.EvaluationHandlerIdx = GetNewAnimBlueprintClass()->EvaluateGraphExposedInputs.Num(); + FExposedValueHandler& ExposedValueHandler = GetNewAnimBlueprintClass()->EvaluateGraphExposedInputs.AddDefaulted_GetRef(); + HandlerRecord.PatchFunctionNameAndCopyRecordsInto(ExposedValueHandler); + } + } +} + +void UAnimBlueprintCompilerSubsystem_Base::AddStructEvalHandlers(UAnimGraphNode_Base* InNode) +{ + const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault(); + + FEvaluationHandlerRecord& EvalHandler = PerNodeStructEvalHandlers.Add(InNode); + + FStructProperty* NodeProperty = CastFieldChecked(GetAllocatedPropertiesByNode().FindChecked(InNode)); + + for (auto SourcePinIt = InNode->Pins.CreateIterator(); SourcePinIt; ++SourcePinIt) + { + UEdGraphPin* SourcePin = *SourcePinIt; + bool bConsumed = false; + + // Register pose links for future use + if ((SourcePin->Direction == EGPD_Input) && (AnimGraphDefaultSchema->IsPosePin(SourcePin->PinType))) + { + // Input pose pin, going to need to be linked up + FPoseLinkMappingRecord LinkRecord = InNode->GetLinkIDLocation(NodeProperty->Struct, SourcePin); + if (LinkRecord.IsValid()) + { + AddPoseLinkMappingRecord(LinkRecord); + bConsumed = true; + } + } + else + { + // The property source for our data, either the struct property for an anim node, or the + // owning anim instance if using a linked instance node. + FProperty* SourcePinProperty = nullptr; + int32 SourceArrayIndex = INDEX_NONE; + bool bInstancePropertyExists = false; + + // We have special handling below if we're targeting a linked instance instead of our own instance properties + UAnimGraphNode_CustomProperty* CustomPropertyNode = Cast(InNode); + + InNode->GetPinAssociatedProperty(NodeProperty->Struct, SourcePin, /*out*/ SourcePinProperty, /*out*/ SourceArrayIndex); + + // Does this pin have an associated evaluation handler? + if(!SourcePinProperty && CustomPropertyNode) + { + // Custom property nodes use instance properties not node properties as they aren't UObjects + // and we can't store non-native properties there + CustomPropertyNode->GetInstancePinProperty(GetNewAnimBlueprintClass(), SourcePin, SourcePinProperty); + bInstancePropertyExists = true; + } + + if (SourcePinProperty != NULL) + { + if (SourcePin->LinkedTo.Num() == 0) + { + // Literal that can be pushed into the CDO instead of re-evaluated every frame + new (ValidAnimNodePinConstants) FEffectiveConstantRecord(NodeProperty, SourcePin, SourcePinProperty, SourceArrayIndex); + bConsumed = true; + } + else + { + // Dynamic value that needs to be wired up and evaluated each frame + const FString& EvaluationHandlerStr = SourcePinProperty->GetMetaData(AnimGraphDefaultSchema->NAME_OnEvaluate); + FName EvaluationHandlerName(*EvaluationHandlerStr); + if (EvaluationHandlerName != NAME_None) + { + // warn that NAME_OnEvaluate is deprecated: + GetMessageLog().Warning(*LOCTEXT("OnEvaluateDeprecated", "OnEvaluate meta data is deprecated, found on @@").ToString(), SourcePinProperty); + } + + ensure(EvalHandler.NodeVariableProperty == nullptr || EvalHandler.NodeVariableProperty == NodeProperty); + EvalHandler.AnimGraphNode = InNode; + EvalHandler.NodeVariableProperty = NodeProperty; + EvalHandler.RegisterPin(SourcePin, SourcePinProperty, SourceArrayIndex); + // if it's not instance property, ensure we mark it + EvalHandler.bServicesNodeProperties = EvalHandler.bServicesNodeProperties | !bInstancePropertyExists; + + if (CustomPropertyNode) + { + EvalHandler.bServicesInstanceProperties = EvalHandler.bServicesInstanceProperties | bInstancePropertyExists; + + FAnimNodeSinglePropertyHandler* SinglePropHandler = EvalHandler.ServicedProperties.Find(SourcePinProperty->GetFName()); + check(SinglePropHandler); // Should have been added in RegisterPin + + // Flag that the target property is actually on the instance class and not the node + SinglePropHandler->bInstanceIsTarget = bInstancePropertyExists; + } + + bConsumed = true; + } + + UEdGraphPin* TrueSourcePin = GetMessageLog().FindSourcePin(SourcePin); + if (TrueSourcePin) + { + GetNewAnimBlueprintClass()->GetDebugData().RegisterClassPropertyAssociation(TrueSourcePin, SourcePinProperty); + } + } + } + + if (!bConsumed && (SourcePin->Direction == EGPD_Input)) + { + //@TODO: ANIMREFACTOR: It's probably OK to have certain pins ignored eventually, but this is very helpful during development + GetMessageLog().Note(TEXT("@@ was visible but ignored"), SourcePin); + } + } + + // Add any property bindings + for(const TPair& PropertyBinding : InNode->PropertyBindings) + { + if(PropertyBinding.Value.bIsBound) + { + EvalHandler.AnimGraphNode = InNode; + EvalHandler.NodeVariableProperty = NodeProperty; + EvalHandler.bServicesNodeProperties = true; + + if (FProperty* Property = FindFProperty(NodeProperty->Struct, PropertyBinding.Key)) + { + EvalHandler.RegisterPropertyBinding(Property, PropertyBinding.Value); + } + else + { + GetMessageLog().Warning(*FString::Printf(TEXT("ICE: @@ Failed to find a property '%s'"), *PropertyBinding.Key.ToString()), InNode); + } + } + } +} + +void UAnimBlueprintCompilerSubsystem_Base::CreateEvaluationHandlerForNode(UAnimGraphNode_Base* InNode) +{ + if(FEvaluationHandlerRecord* RecordPtr = PerNodeStructEvalHandlers.Find(InNode)) + { + // Generate a new event to update the value of these properties + FEvaluationHandlerRecord& Record = *RecordPtr; + + if (Record.NodeVariableProperty) + { + CreateEvaluationHandler(InNode, Record); + + int32 NewIndex = ValidEvaluationHandlerList.Add(Record); + ValidEvaluationHandlerMap.Add(InNode, NewIndex); + } + } +} + +void UAnimBlueprintCompilerSubsystem_Base::CreateEvaluationHandler(UAnimGraphNode_Base* InNode, FEvaluationHandlerRecord& Record) +{ + // Shouldn't create a handler if there is nothing to work with + check(Record.ServicedProperties.Num() > 0); + check(Record.NodeVariableProperty != NULL); + + const UEdGraphSchema_K2* K2Schema = GetDefault(); + const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault(); + + // Use the node GUID for a stable name across compiles + FString FunctionName = FString::Printf(TEXT("%s_%s_%s_%s"), *AnimGraphDefaultSchema->DefaultEvaluationHandlerName.ToString(), *InNode->GetOuter()->GetName(), *InNode->GetClass()->GetName(), *InNode->NodeGuid.ToString()); + Record.HandlerFunctionName = FName(*FunctionName); + + // check function name isnt already used (data exists that can contain duplicate GUIDs) and apply a numeric extension until it is unique + int32 ExtensionIndex = 0; + FName* ExistingName = HandlerFunctionNames.Find(Record.HandlerFunctionName); + while (ExistingName != nullptr) + { + FunctionName = FString::Printf(TEXT("%s_%s_%s_%s_%d"), *AnimGraphDefaultSchema->DefaultEvaluationHandlerName.ToString(), *InNode->GetOuter()->GetName(), *InNode->GetClass()->GetName(), *InNode->NodeGuid.ToString(), ExtensionIndex); + Record.HandlerFunctionName = FName(*FunctionName); + ExistingName = HandlerFunctionNames.Find(Record.HandlerFunctionName); + ExtensionIndex++; + } + + HandlerFunctionNames.Add(Record.HandlerFunctionName); + + // Add a custom event in the graph + UK2Node_CustomEvent* CustomEventNode = SpawnIntermediateEventNode(InNode, nullptr, GetConsolidatedEventGraph()); + CustomEventNode->bInternalEvent = true; + CustomEventNode->CustomFunctionName = Record.HandlerFunctionName; + CustomEventNode->AllocateDefaultPins(); + Record.CustomEventNodes.Add(CustomEventNode); + + // The ExecChain is the current exec output pin in the linear chain + UEdGraphPin* ExecChain = K2Schema->FindExecutionPin(*CustomEventNode, EGPD_Output); + if (Record.bServicesInstanceProperties) + { + // Need to create a variable set call for each serviced property in the handler + for (TPair& PropHandlerPair : Record.ServicedProperties) + { + FAnimNodeSinglePropertyHandler& PropHandler = PropHandlerPair.Value; + FName PropertyName = PropHandlerPair.Key; + + // Should be true, we only want to deal with instance targets in here + if (PropHandler.bInstanceIsTarget) + { + for (FPropertyCopyRecord& CopyRecord : PropHandler.CopyRecords) + { + // New set node for the property + UK2Node_VariableSet* VarAssignNode = SpawnIntermediateNode(InNode, GetConsolidatedEventGraph()); + VarAssignNode->VariableReference.SetSelfMember(CopyRecord.DestProperty->GetFName()); + VarAssignNode->AllocateDefaultPins(); + Record.CustomEventNodes.Add(VarAssignNode); + + // Wire up the exec line, and update the end of the chain + UEdGraphPin* ExecVariablesIn = K2Schema->FindExecutionPin(*VarAssignNode, EGPD_Input); + ExecChain->MakeLinkTo(ExecVariablesIn); + ExecChain = K2Schema->FindExecutionPin(*VarAssignNode, EGPD_Output); + + // Find the property pin on the set node and configure + for (UEdGraphPin* TargetPin : VarAssignNode->Pins) + { + FName PinPropertyName(TargetPin->PinName); + + if (PinPropertyName == PropertyName) + { + // This is us, wire up the variable + UEdGraphPin* DestPin = CopyRecord.DestPin; + + // Copy the data (link up to the source nodes) + TargetPin->CopyPersistentDataFromOldPin(*DestPin); + GetMessageLog().NotifyIntermediatePinCreation(TargetPin, DestPin); + + break; + } + } + } + } + } + } + + if (Record.bServicesNodeProperties) + { + // Create a struct member write node to store the parameters into the animation node + UK2Node_StructMemberSet* AssignmentNode = SpawnIntermediateNode(InNode, GetConsolidatedEventGraph()); + AssignmentNode->VariableReference.SetSelfMember(Record.NodeVariableProperty->GetFName()); + AssignmentNode->StructType = Record.NodeVariableProperty->Struct; + AssignmentNode->AllocateDefaultPins(); + Record.CustomEventNodes.Add(AssignmentNode); + + // Wire up the variable node execution wires + UEdGraphPin* ExecVariablesIn = K2Schema->FindExecutionPin(*AssignmentNode, EGPD_Input); + ExecChain->MakeLinkTo(ExecVariablesIn); + ExecChain = K2Schema->FindExecutionPin(*AssignmentNode, EGPD_Output); + + // Run thru each property + TSet PropertiesBeingSet; + + for (auto TargetPinIt = AssignmentNode->Pins.CreateIterator(); TargetPinIt; ++TargetPinIt) + { + UEdGraphPin* TargetPin = *TargetPinIt; + FName PropertyName(TargetPin->PinName); + + // Does it get serviced by this handler? + if (FAnimNodeSinglePropertyHandler* SourceInfo = Record.ServicedProperties.Find(PropertyName)) + { + if (TargetPin->PinType.IsArray()) + { + // Grab the array that we need to set members for + UK2Node_StructMemberGet* FetchArrayNode = SpawnIntermediateNode(InNode, GetConsolidatedEventGraph()); + FetchArrayNode->VariableReference.SetSelfMember(Record.NodeVariableProperty->GetFName()); + FetchArrayNode->StructType = Record.NodeVariableProperty->Struct; + FetchArrayNode->AllocatePinsForSingleMemberGet(PropertyName); + Record.CustomEventNodes.Add(FetchArrayNode); + + UEdGraphPin* ArrayVariableNode = FetchArrayNode->FindPin(PropertyName); + + if (SourceInfo->CopyRecords.Num() > 0) + { + // Set each element in the array + for (FPropertyCopyRecord& CopyRecord : SourceInfo->CopyRecords) + { + int32 ArrayIndex = CopyRecord.DestArrayIndex; + if(UEdGraphPin* DestPin = CopyRecord.DestPin) + { + // Create an array element set node + UK2Node_CallArrayFunction* ArrayNode = SpawnIntermediateNode(InNode, GetConsolidatedEventGraph()); + ArrayNode->FunctionReference.SetExternalMember(GET_FUNCTION_NAME_CHECKED(UKismetArrayLibrary, Array_Set), UKismetArrayLibrary::StaticClass()); + ArrayNode->AllocateDefaultPins(); + Record.CustomEventNodes.Add(ArrayNode); + + // Connect the execution chain + ExecChain->MakeLinkTo(ArrayNode->GetExecPin()); + ExecChain = ArrayNode->GetThenPin(); + + // Connect the input array + UEdGraphPin* TargetArrayPin = ArrayNode->FindPinChecked(TEXT("TargetArray")); + TargetArrayPin->MakeLinkTo(ArrayVariableNode); + ArrayNode->PinConnectionListChanged(TargetArrayPin); + + // Set the array index + UEdGraphPin* TargetIndexPin = ArrayNode->FindPinChecked(TEXT("Index")); + TargetIndexPin->DefaultValue = FString::FromInt(ArrayIndex); + + // Wire up the data input + UEdGraphPin* TargetItemPin = ArrayNode->FindPinChecked(TEXT("Item")); + TargetItemPin->CopyPersistentDataFromOldPin(*DestPin); + GetMessageLog().NotifyIntermediatePinCreation(TargetItemPin, DestPin); + } + } + } + } + else + { + check(!TargetPin->PinType.IsContainer()); + // Single property + if (SourceInfo->CopyRecords.Num() > 0 && SourceInfo->CopyRecords[0].DestPin != nullptr) + { + UEdGraphPin* DestPin = SourceInfo->CopyRecords[0].DestPin; + + PropertiesBeingSet.Add(DestPin->PinName); + TargetPin->CopyPersistentDataFromOldPin(*DestPin); + GetMessageLog().NotifyIntermediatePinCreation(TargetPin, DestPin); + } + } + } + } + + // Remove any unused pins from the assignment node to avoid smashing constant values + for (int32 PinIndex = 0; PinIndex < AssignmentNode->ShowPinForProperties.Num(); ++PinIndex) + { + FOptionalPinFromProperty& TestProperty = AssignmentNode->ShowPinForProperties[PinIndex]; + TestProperty.bShowPin = PropertiesBeingSet.Contains(TestProperty.PropertyName); + } + + AssignmentNode->ReconstructNode(); + } +} + +void UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::PatchFunctionNameAndCopyRecordsInto(FExposedValueHandler& Handler) const +{ + Handler.CopyRecords.Empty(); + Handler.ValueHandlerNodeProperty = NodeVariableProperty; + + if (IsFastPath()) + { + for (const TPair& ServicedPropPair : ServicedProperties) + { + const FName& PropertyName = ServicedPropPair.Key; + const FAnimNodeSinglePropertyHandler& PropertyHandler = ServicedPropPair.Value; + + for (const FPropertyCopyRecord& PropertyCopyRecord : PropertyHandler.CopyRecords) + { + // Only unbatched copies can be processed on a per-node basis + // Skip invalid copy indices as these are usually the result of BP errors/warnings + if(PropertyCopyRecord.LibraryCopyIndex != INDEX_NONE && PropertyCopyRecord.LibraryBatchType == EPropertyAccessBatchType::Unbatched) + { + Handler.CopyRecords.Emplace(PropertyCopyRecord.LibraryCopyIndex, PropertyCopyRecord.Operation); + } + } + } + } + else + { + // not all of our pins use copy records so we will need to call our exposed value handler + Handler.BoundFunction = HandlerFunctionName; + } +} + +static UEdGraphPin* FindFirstInputPin(UEdGraphNode* InNode) +{ + const UAnimationGraphSchema* Schema = GetDefault(); + + for(UEdGraphPin* Pin : InNode->Pins) + { + if(Pin && Pin->Direction == EGPD_Input && !Schema->IsExecPin(*Pin) && !Schema->IsSelfPin(*Pin)) + { + return Pin; + } + } + + return nullptr; +} + +static bool ForEachInputPin(UEdGraphNode* InNode, TFunctionRef InFunction) +{ + const UAnimationGraphSchema* Schema = GetDefault(); + bool bResult = false; + + for(UEdGraphPin* Pin : InNode->Pins) + { + if(Pin && Pin->Direction == EGPD_Input && !Schema->IsExecPin(*Pin) && !Schema->IsSelfPin(*Pin)) + { + bResult |= InFunction(Pin); + } + } + + return bResult; +} + +static UEdGraphNode* FollowKnots(UEdGraphPin* FromPin, UEdGraphPin*& ToPin) +{ + if (FromPin->LinkedTo.Num() == 0) + { + return nullptr; + } + + UEdGraphPin* LinkedPin = FromPin->LinkedTo[0]; + ToPin = LinkedPin; + if(LinkedPin) + { + UEdGraphNode* LinkedNode = LinkedPin->GetOwningNode(); + UK2Node_Knot* KnotNode = Cast(LinkedNode); + while(KnotNode) + { + if(UEdGraphPin* InputPin = FindFirstInputPin(KnotNode)) + { + if (InputPin->LinkedTo.Num() > 0 && InputPin->LinkedTo[0]) + { + ToPin = InputPin->LinkedTo[0]; + LinkedNode = InputPin->LinkedTo[0]->GetOwningNode(); + KnotNode = Cast(LinkedNode); + } + else + { + KnotNode = nullptr; + } + } + } + return LinkedNode; + } + + return nullptr; +} + +void UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::RegisterPin(UEdGraphPin* DestPin, FProperty* AssociatedProperty, int32 AssociatedPropertyArrayIndex) +{ + FAnimNodeSinglePropertyHandler& Handler = ServicedProperties.FindOrAdd(AssociatedProperty->GetFName()); + + TArray DestPropertyPath; + + // Prepend the destination property with the node's member property if the property is not on a UClass + if(Cast(AssociatedProperty->Owner.ToUObject()) == nullptr) + { + DestPropertyPath.Add(NodeVariableProperty->GetName()); + } + + if(AssociatedPropertyArrayIndex != INDEX_NONE) + { + DestPropertyPath.Add(FString::Printf(TEXT("%s[%d]"), *AssociatedProperty->GetName(), AssociatedPropertyArrayIndex)); + } + else + { + DestPropertyPath.Add(AssociatedProperty->GetName()); + } + + Handler.CopyRecords.Emplace(DestPin, AssociatedProperty, AssociatedPropertyArrayIndex, MoveTemp(DestPropertyPath)); +} + +void UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::RegisterPropertyBinding(FProperty* InProperty, const FAnimGraphNodePropertyBinding& InBinding) +{ + FAnimNodeSinglePropertyHandler& Handler = ServicedProperties.FindOrAdd(InProperty->GetFName()); + + TArray DestPropertyPath; + + // Prepend the destination property with the node's member property if the property is not on a UClass + if(Cast(InProperty->Owner.ToUObject()) == nullptr) + { + DestPropertyPath.Add(NodeVariableProperty->GetName()); + } + + DestPropertyPath.Add(InProperty->GetName()); + + Handler.CopyRecords.Emplace(InBinding.PropertyPath, DestPropertyPath); +} + +void UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::BuildFastPathCopyRecords(UAnimBlueprintCompilerSubsystem_Base& InSubsystem) +{ + typedef bool (UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::*GraphCheckerFunc)(FCopyRecordGraphCheckContext&, UEdGraphPin*); + + GraphCheckerFunc GraphCheckerFuncs[] = + { + &UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::CheckForMakeStructAccess, + &UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::CheckForVariableGet, + &UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::CheckForLogicalNot, + &UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::CheckForStructMemberAccess, + &UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::CheckForArrayAccess, + }; + + if (GetDefault()->bOptimizeAnimBlueprintMemberVariableAccess) + { + for (TPair& ServicedPropPair : ServicedProperties) + { + TArray AllAdditionalCopyRecords; + + for (FPropertyCopyRecord& CopyRecord : ServicedPropPair.Value.CopyRecords) + { + if(CopyRecord.SourcePropertyPath.Num() == 0) + { + TArray AdditionalCopyRecords; + + FCopyRecordGraphCheckContext Context(CopyRecord, AdditionalCopyRecords); + + for (GraphCheckerFunc& CheckFunc : GraphCheckerFuncs) + { + if ((this->*CheckFunc)(Context, CopyRecord.DestPin)) + { + break; + } + } + + if(AdditionalCopyRecords.Num() > 0) + { + for(FPropertyCopyRecord& AdditionalCopyRecord : AdditionalCopyRecords) + { + CheckForMemberOnlyAccess(AdditionalCopyRecord, AdditionalCopyRecord.DestPin); + } + + CopyRecord = AdditionalCopyRecords[0]; + + for(int32 AdditionalRecordIndex = 1; AdditionalRecordIndex < AdditionalCopyRecords.Num(); ++AdditionalRecordIndex) + { + AllAdditionalCopyRecords.Add(AdditionalCopyRecords[AdditionalRecordIndex]); + } + } + else + { + CheckForMemberOnlyAccess(CopyRecord, CopyRecord.DestPin); + } + } + } + + // Append any additional copy records + ServicedPropPair.Value.CopyRecords.Append(AllAdditionalCopyRecords); + } + } +} + +static void GetFullyQualifiedPathFromPin(const UEdGraphPin* Pin, TArray& OutPath) +{ + FString PinName = Pin->PinName.ToString(); + while (Pin->ParentPin != nullptr) + { + PinName[Pin->ParentPin->PinName.GetStringLength()] = TEXT('.'); + Pin = Pin->ParentPin; + } + + UE::String::ParseTokens(PinName, TEXT('.'), [&OutPath](FStringView InStringView) + { + OutPath.Add(FString(InStringView)); + }); +} + +bool UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::CheckForVariableGet(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin) +{ + if(DestPin) + { + UEdGraphPin* SourcePin = nullptr; + if(UK2Node_VariableGet* VariableGetNode = Cast(FollowKnots(DestPin, SourcePin))) + { + if(VariableGetNode && VariableGetNode->IsNodePure() && VariableGetNode->VariableReference.IsSelfContext()) + { + if(SourcePin) + { + GetFullyQualifiedPathFromPin(SourcePin, Context.CopyRecord->SourcePropertyPath); + return true; + } + } + } + } + + return false; +} + +bool UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::CheckForLogicalNot(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin) +{ + if(DestPin) + { + UEdGraphPin* SourcePin = nullptr; + UK2Node_CallFunction* CallFunctionNode = Cast(FollowKnots(DestPin, SourcePin)); + if(CallFunctionNode && CallFunctionNode->FunctionReference.GetMemberName() == FName(TEXT("Not_PreBool"))) + { + // find and follow input pin + if(UEdGraphPin* InputPin = FindFirstInputPin(CallFunctionNode)) + { + check(InputPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean); + if(CheckForVariableGet(Context, InputPin) || CheckForStructMemberAccess(Context, InputPin) || CheckForArrayAccess(Context, InputPin)) + { + check(Context.CopyRecord->SourcePropertyPath.Num() > 0); // this should have been filled in by CheckForVariableGet() or CheckForStructMemberAccess() above + Context.CopyRecord->Operation = EPostCopyOperation::LogicalNegateBool; + return true; + } + } + } + } + + return false; +} + +/** The functions that we can safely native-break */ +static const FName NativeBreakFunctionNameWhitelist[] = +{ + FName(TEXT("BreakVector")), + FName(TEXT("BreakVector2D")), + FName(TEXT("BreakRotator")), +}; + +/** Check whether a native break function can be safely used in the fast-path copy system (ie. source and dest data will be the same) */ +static bool IsWhitelistedNativeBreak(const FName& InFunctionName) +{ + for(const FName& FunctionName : NativeBreakFunctionNameWhitelist) + { + if(InFunctionName == FunctionName) + { + return true; + } + } + + return false; +} + +/** The functions that we can safely native-make */ +static const FName NativeMakeFunctionNameWhitelist[] = +{ + FName(TEXT("MakeVector")), + FName(TEXT("MakeVector2D")), + FName(TEXT("MakeRotator")), +}; + +/** Check whether a native break function can be safely used in the fast-path copy system (ie. source and dest data will be the same) */ +static bool IsWhitelistedNativeMake(const FName& InFunctionName) +{ + for(const FName& FunctionName : NativeMakeFunctionNameWhitelist) + { + if(InFunctionName == FunctionName) + { + return true; + } + } + + return false; +} + +bool UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::CheckForStructMemberAccess(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin) +{ + if(DestPin) + { + UEdGraphPin* SourcePin = nullptr; + if(UK2Node_BreakStruct* BreakStructNode = Cast(FollowKnots(DestPin, SourcePin))) + { + if(UEdGraphPin* InputPin = FindFirstInputPin(BreakStructNode)) + { + if(CheckForStructMemberAccess(Context, InputPin) || CheckForVariableGet(Context, InputPin) || CheckForArrayAccess(Context, InputPin)) + { + check(Context.CopyRecord->SourcePropertyPath.Num() > 0); // this should have been filled in by CheckForVariableGet() above + Context.CopyRecord->SourcePropertyPath.Add(SourcePin->PinName.ToString()); + return true; + } + } + } + // could be a native break + else if(UK2Node_CallFunction* NativeBreakNode = Cast(FollowKnots(DestPin, SourcePin))) + { + UFunction* Function = NativeBreakNode->FunctionReference.ResolveMember(UKismetMathLibrary::StaticClass()); + if(Function && Function->HasMetaData(TEXT("NativeBreakFunc")) && IsWhitelistedNativeBreak(Function->GetFName())) + { + if(UEdGraphPin* InputPin = FindFirstInputPin(NativeBreakNode)) + { + if(CheckForStructMemberAccess(Context, InputPin) || CheckForVariableGet(Context, InputPin) || CheckForArrayAccess(Context, InputPin)) + { + check(Context.CopyRecord->SourcePropertyPath.Num() > 0); // this should have been filled in by CheckForVariableGet() above + Context.CopyRecord->SourcePropertyPath.Add(SourcePin->PinName.ToString()); + return true; + } + } + } + } + } + + return false; +} + +bool UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::CheckForMakeStructAccess(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin) +{ + if(DestPin) + { + FPropertyCopyRecord OriginalRecord = *Context.CopyRecord; + + UEdGraphPin* SourcePin = nullptr; + if(UK2Node_MakeStruct* MakeStructNode = Cast(FollowKnots(DestPin, SourcePin))) + { + return ForEachInputPin(MakeStructNode, [this, &Context, &OriginalRecord](UEdGraphPin* InputPin) + { + Context.CopyRecord->SourcePropertyPath = OriginalRecord.SourcePropertyPath; + if(CheckForStructMemberAccess(Context, InputPin) || CheckForVariableGet(Context, InputPin) || CheckForArrayAccess(Context, InputPin)) + { + check(Context.CopyRecord->DestPropertyPath.Num() > 0); + FPropertyCopyRecord RecordCopy = *Context.CopyRecord; + FPropertyCopyRecord& NewRecord = Context.AdditionalCopyRecords.Add_GetRef(MoveTemp(RecordCopy)); + + NewRecord.DestPropertyPath = OriginalRecord.DestPropertyPath; + NewRecord.DestPropertyPath.Add(InputPin->PinName.ToString()); + return true; + } + + return false; + }); + } + else if(UK2Node_CallFunction* NativeMakeNode = Cast(FollowKnots(DestPin, SourcePin))) + { + UFunction* Function = NativeMakeNode->FunctionReference.ResolveMember(UKismetMathLibrary::StaticClass()); + if(Function && Function->HasMetaData(TEXT("NativeMakeFunc")) && IsWhitelistedNativeMake(Function->GetFName())) + { + return ForEachInputPin(NativeMakeNode, [this, &Context, &OriginalRecord](UEdGraphPin* InputPin) + { + Context.CopyRecord->SourcePropertyPath = OriginalRecord.SourcePropertyPath; + if(CheckForStructMemberAccess(Context, InputPin) || CheckForVariableGet(Context, InputPin) || CheckForArrayAccess(Context, InputPin)) + { + check(Context.CopyRecord->DestPropertyPath.Num() > 0); + FPropertyCopyRecord RecordCopy = *Context.CopyRecord; + FPropertyCopyRecord& NewRecord = Context.AdditionalCopyRecords.Add_GetRef(MoveTemp(RecordCopy)); + + NewRecord.DestPropertyPath = OriginalRecord.DestPropertyPath; + NewRecord.DestPropertyPath.Add(InputPin->PinName.ToString()); + return true; + } + + return false; + }); + } + } + } + + return false; +} + +bool UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::CheckForArrayAccess(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin) +{ + if(DestPin) + { + UEdGraphPin* SourcePin = nullptr; + if(UK2Node_CallArrayFunction* CallArrayFunctionNode = Cast(FollowKnots(DestPin, SourcePin))) + { + if(CallArrayFunctionNode->GetTargetFunction() == UKismetArrayLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetArrayLibrary, Array_Get))) + { + // Check array index is constant + int32 ArrayIndex = INDEX_NONE; + if(UEdGraphPin* IndexPin = CallArrayFunctionNode->FindPin(TEXT("Index"))) + { + if(IndexPin->LinkedTo.Num() > 0) + { + return false; + } + + ArrayIndex = FCString::Atoi(*IndexPin->DefaultValue); + } + + if(UEdGraphPin* TargetArrayPin = CallArrayFunctionNode->FindPin(TEXT("TargetArray"))) + { + if(CheckForVariableGet(Context, TargetArrayPin) || CheckForStructMemberAccess(Context, TargetArrayPin)) + { + check(Context.CopyRecord->SourcePropertyPath.Num() > 0); // this should have been filled in by CheckForVariableGet() or CheckForStructMemberAccess() above + Context.CopyRecord->SourcePropertyPath.Last().Append(FString::Printf(TEXT("[%d]"), ArrayIndex)); + return true; + } + } + } + + + } + } + + return false; +} + +bool UAnimBlueprintCompilerSubsystem_Base::FEvaluationHandlerRecord::CheckForMemberOnlyAccess(FPropertyCopyRecord& CopyRecord, UEdGraphPin* DestPin) +{ + const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault(); + + if(DestPin) + { + // traverse pins to leaf nodes and check for member access/pure only + TArray PinStack; + PinStack.Add(DestPin); + while(PinStack.Num() > 0) + { + UEdGraphPin* CurrentPin = PinStack.Pop(false); + for(auto& LinkedPin : CurrentPin->LinkedTo) + { + UEdGraphNode* LinkedNode = LinkedPin->GetOwningNode(); + if(LinkedNode) + { + bool bLeafNode = true; + for(auto& Pin : LinkedNode->Pins) + { + if(Pin != LinkedPin && Pin->Direction == EGPD_Input && !AnimGraphDefaultSchema->IsPosePin(Pin->PinType)) + { + bLeafNode = false; + PinStack.Add(Pin); + } + } + + if(bLeafNode) + { + if(UK2Node_VariableGet* LinkedVariableGetNode = Cast(LinkedNode)) + { + if(!LinkedVariableGetNode->IsNodePure() || !LinkedVariableGetNode->VariableReference.IsSelfContext()) + { + // only local variable access is allowed for leaf nodes + CopyRecord.InvalidateFastPath(); + } + } + else if(UK2Node_CallFunction* CallFunctionNode = Cast(LinkedNode)) + { + if(!CallFunctionNode->IsNodePure()) + { + // only allow pure function calls + CopyRecord.InvalidateFastPath(); + } + } + else if(!LinkedNode->IsA()) + { + CopyRecord.InvalidateFastPath(); + } + } + } + } + } + } + + return CopyRecord.IsFastPath(); +} + +bool UAnimBlueprintCompilerSubsystem_Base::FEffectiveConstantRecord::Apply(UObject* Object) +{ + uint8* StructPtr = nullptr; + uint8* PropertyPtr = nullptr; + + if(NodeVariableProperty->Struct->IsChildOf(FAnimNode_LinkedAnimGraph::StaticStruct())) + { + PropertyPtr = ConstantProperty->ContainerPtrToValuePtr(Object); + } + else + { + StructPtr = NodeVariableProperty->ContainerPtrToValuePtr(Object); + PropertyPtr = ConstantProperty->ContainerPtrToValuePtr(StructPtr); + } + + if (ArrayIndex != INDEX_NONE) + { + FArrayProperty* ArrayProperty = CastFieldChecked(ConstantProperty); + + // Peer inside the array + FScriptArrayHelper ArrayHelper(ArrayProperty, PropertyPtr); + + if (ArrayHelper.IsValidIndex(ArrayIndex)) + { + FBlueprintEditorUtils::PropertyValueFromString_Direct(ArrayProperty->Inner, LiteralSourcePin->GetDefaultAsString(), ArrayHelper.GetRawPtr(ArrayIndex)); + } + else + { + return false; + } + } + else + { + FBlueprintEditorUtils::PropertyValueFromString_Direct(ConstantProperty, LiteralSourcePin->GetDefaultAsString(), PropertyPtr); + } + + return true; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_Base.h b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_Base.h new file mode 100644 index 000000000000..a06b6ab9d1f1 --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_Base.h @@ -0,0 +1,271 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AnimBlueprintCompilerSubsystem.h" +#include "IPropertyAccessCompiler.h" +#include "Animation/AnimNodeBase.h" + +#include "AnimBlueprintCompilerSubsystem_Base.generated.h" + +class UAnimGraphNode_Base; +struct FAnimGraphNodePropertyBinding; +class UK2Node_CustomEvent; + +UCLASS() +class UAnimBlueprintCompilerSubsystem_Base : public UAnimBlueprintCompilerSubsystem +{ + GENERATED_BODY() + +public: + // Adds a map of struct eval handlers for the specified node + void AddStructEvalHandlers(UAnimGraphNode_Base* InNode); + + // Create an 'expanded' evaluation handler for the specified node, called in the compiler's node expansion step + void CreateEvaluationHandlerForNode(UAnimGraphNode_Base* InNode); + +private: + // UAnimBlueprintCompilerSubsystem interface + virtual void StartCompilingClass(UClass* InClass) override; + virtual void FinishCompilingClass(UClass* InClass) override; + virtual void PostExpansionStep(UEdGraph* InGraph) override; + virtual void CopyTermDefaultsToDefaultObject(UObject* InDefaultObject) override; + +private: + /** Record of a single copy operation */ + struct FPropertyCopyRecord + { + FPropertyCopyRecord(UEdGraphPin* InDestPin, FProperty* InDestProperty, int32 InDestArrayIndex, TArray&& InDestPropertyPath) + : DestPin(InDestPin) + , DestProperty(InDestProperty) + , DestArrayIndex(InDestArrayIndex) + , DestPropertyPath(MoveTemp(InDestPropertyPath)) + , LibraryCopyIndex(INDEX_NONE) + , LibraryBatchType(EPropertyAccessBatchType::Unbatched) + , Operation(EPostCopyOperation::None) + , bIsFastPath(true) + {} + + FPropertyCopyRecord(const TArray& InSourcePropertyPath, const TArray& InDestPropertyPath) + : DestPin(nullptr) + , DestProperty(nullptr) + , DestArrayIndex(INDEX_NONE) + , SourcePropertyPath(InSourcePropertyPath) + , DestPropertyPath(InDestPropertyPath) + , LibraryCopyIndex(INDEX_NONE) + , LibraryBatchType(EPropertyAccessBatchType::Unbatched) + , Operation(EPostCopyOperation::None) + , bIsFastPath(true) + {} + + bool IsFastPath() const + { + return SourcePropertyPath.Num() > 0 && bIsFastPath; + } + + void InvalidateFastPath() + { + bIsFastPath = false; + } + + /** The destination pin we are copying to */ + UEdGraphPin* DestPin; + + /** The destination property we are copying to (on an animation node) */ + FProperty* DestProperty; + + /** The array index we use if the destination property is an array */ + int32 DestArrayIndex; + + /** The property path relative to the class */ + TArray SourcePropertyPath; + + /** The property path relative to the class */ + TArray DestPropertyPath; + + /** The index of the copy in the property access library */ + int32 LibraryCopyIndex; + + /** the batch type within the property access library */ + EPropertyAccessBatchType LibraryBatchType; + + /** Any operation we want to perform post-copy on the destination data */ + EPostCopyOperation Operation; + + /** Fast-path flag */ + bool bIsFastPath; + }; + + // Context used to build fast-path copy records + struct FCopyRecordGraphCheckContext + { + FCopyRecordGraphCheckContext(FPropertyCopyRecord& InCopyRecord, TArray& InAdditionalCopyRecords) + : CopyRecord(&InCopyRecord) + , AdditionalCopyRecords(InAdditionalCopyRecords) + {} + + // Copy record we are operating on + FPropertyCopyRecord* CopyRecord; + + // Things like split input pins can add additional copy records + TArray& AdditionalCopyRecords; + }; + + // Wireup record for a single anim node property (which might be an array) + struct FAnimNodeSinglePropertyHandler + { + /** Copy records */ + TArray CopyRecords; + + // If the anim instance is the container target instead of the node. + bool bInstanceIsTarget; + + FAnimNodeSinglePropertyHandler() + : bInstanceIsTarget(false) + { + } + }; + + // Record for a property that was exposed as a pin, but wasn't wired up (just a literal) + struct FEffectiveConstantRecord + { + public: + // The node variable that the handler is in + class FStructProperty* NodeVariableProperty; + + // The property within the struct to set + class FProperty* ConstantProperty; + + // The array index if ConstantProperty is an array property, or INDEX_NONE otherwise + int32 ArrayIndex; + + // The pin to pull the DefaultValue/DefaultObject from + UEdGraphPin* LiteralSourcePin; + + FEffectiveConstantRecord() + : NodeVariableProperty(NULL) + , ConstantProperty(NULL) + , ArrayIndex(INDEX_NONE) + , LiteralSourcePin(NULL) + { + } + + FEffectiveConstantRecord(FStructProperty* ContainingNodeProperty, UEdGraphPin* SourcePin, FProperty* SourcePinProperty, int32 SourceArrayIndex) + : NodeVariableProperty(ContainingNodeProperty) + , ConstantProperty(SourcePinProperty) + , ArrayIndex(SourceArrayIndex) + , LiteralSourcePin(SourcePin) + { + } + + bool Apply(UObject* Object); + }; + + /** BP execution handler for Anim node */ + struct FEvaluationHandlerRecord + { + public: + // The Node this record came from + UAnimGraphNode_Base* AnimGraphNode; + + // The node variable that the handler is in + FStructProperty* NodeVariableProperty; + + // The specific evaluation handler inside the specified node + int32 EvaluationHandlerIdx; + + // Whether or not our serviced properties are actually on the anim node + bool bServicesNodeProperties; + + // Whether or not our serviced properties are actually on the instance instead of the node + bool bServicesInstanceProperties; + + // Set of properties serviced by this handler (Map from property name to the record for that property) + TMap ServicedProperties; + + // The generated custom event node + TArray CustomEventNodes; + + // The resulting function name + FName HandlerFunctionName; + + public: + + FEvaluationHandlerRecord() + : AnimGraphNode(nullptr) + , NodeVariableProperty(nullptr) + , EvaluationHandlerIdx(INDEX_NONE) + , bServicesNodeProperties(false) + , bServicesInstanceProperties(false) + , HandlerFunctionName(NAME_None) + {} + + bool IsFastPath() const + { + for(TMap::TConstIterator It(ServicedProperties); It; ++It) + { + const FAnimNodeSinglePropertyHandler& AnimNodeSinglePropertyHandler = It.Value(); + for (const FPropertyCopyRecord& CopyRecord : AnimNodeSinglePropertyHandler.CopyRecords) + { + if (!CopyRecord.IsFastPath()) + { + return false; + } + } + } + + return true; + } + + bool IsValid() const + { + return NodeVariableProperty != nullptr; + } + + void PatchFunctionNameAndCopyRecordsInto(FExposedValueHandler& Handler) const; + + void RegisterPin(UEdGraphPin* DestPin, FProperty* AssociatedProperty, int32 AssociatedPropertyArrayIndex); + + void RegisterPropertyBinding(FProperty* InProperty, const FAnimGraphNodePropertyBinding& InBinding); + + FStructProperty* GetHandlerNodeProperty() const { return NodeVariableProperty; } + + void BuildFastPathCopyRecords(UAnimBlueprintCompilerSubsystem_Base& InSubsystem); + + private: + + bool CheckForVariableGet(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin); + + bool CheckForLogicalNot(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin); + + bool CheckForStructMemberAccess(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin); + + bool CheckForMemberOnlyAccess(FPropertyCopyRecord& Context, UEdGraphPin* DestPin); + + bool CheckForMakeStructAccess(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin); + + bool CheckForArrayAccess(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin); + }; + + // Create an evaluation handler for the specified node/record + void CreateEvaluationHandler(UAnimGraphNode_Base* InNode, FEvaluationHandlerRecord& Record); + +private: + // Records of pose pins for later patchup with an associated evaluation handler + TMap PerNodeStructEvalHandlers; + + // List of successfully created evaluation handlers + TArray ValidEvaluationHandlerList; + TMap ValidEvaluationHandlerMap; + + // List of animation node literals (values exposed as pins but never wired up) that need to be pushed into the CDO + TArray ValidAnimNodePinConstants; + + // Set of used handler function names + TSet HandlerFunctionNames; + + // Delegate handle for registering against library pre/post-compilation + FDelegateHandle PreLibraryCompiledDelegateHandle; + FDelegateHandle PostLibraryCompiledDelegateHandle; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_CachedPose.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_CachedPose.cpp new file mode 100644 index 000000000000..12d717535255 --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_CachedPose.cpp @@ -0,0 +1,169 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "AnimBlueprintCompilerSubsystem_CachedPose.h" +#include "AnimGraphNode_SaveCachedPose.h" +#include "AnimGraphNode_Root.h" +#include "AnimGraphNode_UseCachedPose.h" +#include "Kismet2/CompilerResultsLog.h" +#include "AnimGraphNode_StateMachine.h" +#include "AnimGraphNode_StateResult.h" +#include "AnimationStateMachineGraph.h" + +void UAnimBlueprintCompilerSubsystem_CachedPose::PreProcessAnimationNodes(TArrayView InAnimNodes) +{ + for(UAnimGraphNode_Base* Node : InAnimNodes) + { + if(UAnimGraphNode_SaveCachedPose* SavePoseRoot = Cast(Node)) + { + //@TODO: Ideally we only add these if there is a UseCachedPose node referencing them, but those can be anywhere and are hard to grab + SaveCachedPoseNodes.Add(SavePoseRoot->CacheName, SavePoseRoot); + } + } +} + +void UAnimBlueprintCompilerSubsystem_CachedPose::PostProcessAnimationNodes(TArrayView InAnimNodes) +{ + // Build cached pose map + BuildCachedPoseNodeUpdateOrder(); +} + +TAutoConsoleVariable CVarAnimDebugCachePoseNodeUpdateOrder(TEXT("a.Compiler.CachePoseNodeUpdateOrderDebug.Enable"), 0, TEXT("Toggle debugging for CacheNodeUpdateOrder debug during AnimBP compilation")); + +void UAnimBlueprintCompilerSubsystem_CachedPose::BuildCachedPoseNodeUpdateOrder() +{ + TArray RootNodes; + GetConsolidatedEventGraph()->GetNodesOfClass(RootNodes); + + // State results are also "root" nodes, need to find the true roots + RootNodes.RemoveAll([](UAnimGraphNode_Root* InPossibleRootNode) + { + return InPossibleRootNode->GetClass() != UAnimGraphNode_Root::StaticClass(); + }); + + const bool bEnableDebug = (CVarAnimDebugCachePoseNodeUpdateOrder.GetValueOnAnyThread() == 1); + + for(UAnimGraphNode_Root* RootNode : RootNodes) + { + TArray OrderedSavePoseNodes; + + TArray VisitedRootNodes; + + UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("CachePoseNodeOrdering BEGIN")); + + CachePoseNodeOrdering_StartNewTraversal(RootNode, OrderedSavePoseNodes, VisitedRootNodes); + + UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("CachePoseNodeOrdering END")); + + if (bEnableDebug) + { + UE_LOG(LogAnimation, Display, TEXT("Ordered Save Pose Node List:")); + for (UAnimGraphNode_SaveCachedPose* SavedPoseNode : OrderedSavePoseNodes) + { + UE_LOG(LogAnimation, Display, TEXT("\t%s"), *SavedPoseNode->Node.CachePoseName.ToString()) + } + UE_LOG(LogAnimation, Display, TEXT("End List")); + } + + FCachedPoseIndices& OrderedSavedPoseIndices = GetNewAnimBlueprintClass()->OrderedSavedPoseIndicesMap.FindOrAdd(RootNode->Node.Name); + + for(UAnimGraphNode_SaveCachedPose* PoseNode : OrderedSavePoseNodes) + { + if(const int32* NodeIndex = GetAllocatedAnimNodeIndices().Find(PoseNode)) + { + OrderedSavedPoseIndices.OrderedSavedPoseNodeIndices.Add(*NodeIndex); + } + else + { + GetMessageLog().Error(TEXT("Failed to find index for a saved pose node while building ordered pose list.")); + } + } + } +} + +void UAnimBlueprintCompilerSubsystem_CachedPose::CachePoseNodeOrdering_StartNewTraversal(UAnimGraphNode_Base* InRootNode, TArray &OrderedSavePoseNodes, TArray VisitedRootNodes) +{ + check(InRootNode); + UAnimGraphNode_SaveCachedPose* RootCacheNode = Cast(InRootNode); + FString RootName = RootCacheNode ? RootCacheNode->CacheName : InRootNode->GetName(); + + const bool bEnableDebug = (CVarAnimDebugCachePoseNodeUpdateOrder.GetValueOnAnyThread() == 1); + + UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("StartNewTraversal %s"), *RootName); + + // Track which root nodes we've visited to prevent infinite recursion. + VisitedRootNodes.Add(InRootNode); + + // Need a list of only what we find here to recurse, we can't do that with the total list + TArray InternalOrderedNodes; + + // Traverse whole graph from root collecting SaveCachePose nodes we've touched. + CachePoseNodeOrdering_TraverseInternal(InRootNode, InternalOrderedNodes); + + // Process nodes that we've touched + UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("Process Queue for %s"), *RootName); + for (UAnimGraphNode_SaveCachedPose* QueuedCacheNode : InternalOrderedNodes) + { + if (VisitedRootNodes.Contains(QueuedCacheNode)) + { + UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("Process Queue SaveCachePose %s. ALREADY VISITED, INFINITE RECURSION DETECTED! SKIPPING"), *QueuedCacheNode->CacheName); + GetMessageLog().Error(*FString::Printf(TEXT("Infinite recursion detected with SaveCachePose %s and %s"), *RootName, *QueuedCacheNode->CacheName)); + continue; + } + else + { + OrderedSavePoseNodes.Remove(QueuedCacheNode); + OrderedSavePoseNodes.Add(QueuedCacheNode); + + CachePoseNodeOrdering_StartNewTraversal(QueuedCacheNode, OrderedSavePoseNodes, VisitedRootNodes); + } + } + + UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("EndNewTraversal %s"), *RootName); +} + +void UAnimBlueprintCompilerSubsystem_CachedPose::CachePoseNodeOrdering_TraverseInternal(UAnimGraphNode_Base* InAnimGraphNode, TArray &OrderedSavePoseNodes) +{ + TArray LinkedAnimNodes; + GetLinkedAnimNodes(InAnimGraphNode, LinkedAnimNodes); + + const bool bEnableDebug = (CVarAnimDebugCachePoseNodeUpdateOrder.GetValueOnAnyThread() == 1); + + for (UAnimGraphNode_Base* LinkedNode : LinkedAnimNodes) + { + UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("\t Processing %s"), *LinkedNode->GetName()); + if (UAnimGraphNode_UseCachedPose* UsePoseNode = Cast(LinkedNode)) + { + if (UAnimGraphNode_SaveCachedPose* SaveNode = UsePoseNode->SaveCachedPoseNode.Get()) + { + UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("\t Queueing SaveCachePose %s"), *SaveNode->CacheName); + + // Requeue the node we found + OrderedSavePoseNodes.Remove(SaveNode); + OrderedSavePoseNodes.Add(SaveNode); + } + } + else if (UAnimGraphNode_StateMachine* StateMachineNode = Cast(LinkedNode)) + { + for (UEdGraph* StateGraph : StateMachineNode->EditorStateMachineGraph->SubGraphs) + { + TArray ResultNodes; + StateGraph->GetNodesOfClass(ResultNodes); + + // We should only get one here but doesn't hurt to loop here in case that changes + for (UAnimGraphNode_StateResult* ResultNode : ResultNodes) + { + CachePoseNodeOrdering_TraverseInternal(ResultNode, OrderedSavePoseNodes); + } + } + } + else + { + CachePoseNodeOrdering_TraverseInternal(LinkedNode, OrderedSavePoseNodes); + } + } +} + +const TMap& UAnimBlueprintCompilerSubsystem_CachedPose::GetSaveCachedPoseNodes() const +{ + return SaveCachedPoseNodes; +} \ No newline at end of file diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_CachedPose.h b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_CachedPose.h new file mode 100644 index 000000000000..f5f4a539e84d --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_CachedPose.h @@ -0,0 +1,38 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AnimBlueprintCompilerSubsystem.h" + +#include "AnimBlueprintCompilerSubsystem_CachedPose.generated.h" + +class UAnimGraphNode_SaveCachedPose; + +UCLASS() +class UAnimBlueprintCompilerSubsystem_CachedPose : public UAnimBlueprintCompilerSubsystem +{ + GENERATED_BODY() + +public: + // Get the map of cache name to encountered save cached pose nodes + const TMap& GetSaveCachedPoseNodes() const; + +private: + // UAnimBlueprintCompilerSubsystem interface + virtual void PreProcessAnimationNodes(TArrayView InAnimNodes) override; + virtual void PostProcessAnimationNodes(TArrayView InAnimNodes) override; + + // Builds the update order list for saved pose nodes in this blueprint + void BuildCachedPoseNodeUpdateOrder(); + + // Traverses a graph to collect save pose nodes starting at InRootNode, then processes each node + void CachePoseNodeOrdering_StartNewTraversal(UAnimGraphNode_Base* InRootNode, TArray &OrderedSavePoseNodes, TArray VisitedRootNodes); + + // Traverses a graph to collect save pose nodes starting at InAnimGraphNode, does NOT process saved pose nodes afterwards + void CachePoseNodeOrdering_TraverseInternal(UAnimGraphNode_Base* InAnimGraphNode, TArray &OrderedSavePoseNodes); + +private: + // Map of cache name to encountered save cached pose nodes + TMap SaveCachedPoseNodes; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_LinkedAnimGraph.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_LinkedAnimGraph.cpp new file mode 100644 index 000000000000..96619a60e1c1 --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_LinkedAnimGraph.cpp @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "AnimBlueprintCompilerSubsystem_LinkedAnimGraph.h" +#include "AnimGraphNode_LinkedAnimGraphBase.h" + +void UAnimBlueprintCompilerSubsystem_LinkedAnimGraph::PreProcessAnimationNodes(TArrayView InAnimNodes) +{ + for(UAnimGraphNode_Base* AnimNode : InAnimNodes) + { + if(UAnimGraphNode_LinkedAnimGraphBase* LinkedAnimGraphBase = Cast(AnimNode)) + { + LinkedAnimGraphBase->AllocatePoseLinks(); + } + } +} \ No newline at end of file diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_LinkedAnimGraph.h b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_LinkedAnimGraph.h new file mode 100644 index 000000000000..069629a9af9d --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_LinkedAnimGraph.h @@ -0,0 +1,18 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AnimBlueprintCompilerSubsystem.h" + +#include "AnimBlueprintCompilerSubsystem_LinkedAnimGraph.generated.h" + +UCLASS() +class UAnimBlueprintCompilerSubsystem_LinkedAnimGraph : public UAnimBlueprintCompilerSubsystem +{ + GENERATED_BODY() + +private: + // UAnimBlueprintCompilerSubsystem interface + virtual void PreProcessAnimationNodes(TArrayView InAnimNodes) override; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_StateMachine.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_StateMachine.cpp new file mode 100644 index 000000000000..8d076ede94e7 --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_StateMachine.cpp @@ -0,0 +1,583 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "AnimBlueprintCompilerSubsystem_StateMachine.h" +#include "Animation/AnimBlueprintGeneratedClass.h" +#include "K2Node_AnimGetter.h" +#include "K2Node_TransitionRuleGetter.h" +#include "Kismet2/CompilerResultsLog.h" +#include "AnimGraphNode_Base.h" +#include "K2Node_StructMemberGet.h" +#include "AnimStateNode.h" +#include "AnimStateTransitionNode.h" +#include "AnimGraphNode_StateMachineBase.h" +#include "EdGraphUtilities.h" +#include "AnimationStateMachineSchema.h" +#include "Algo/Transform.h" +#include "Animation/AnimTypes.h" + +#define LOCTEXT_NAMESPACE "StateMachineSubsystem" + +int32 UAnimBlueprintCompilerSubsystem_StateMachine::FindOrAddNotify(FAnimNotifyEvent& Notify) +{ + if ((Notify.NotifyName == NAME_None) && (Notify.Notify == nullptr) && (Notify.NotifyStateClass == nullptr)) + { + // Non event, don't add it + return INDEX_NONE; + } + + UAnimBlueprintGeneratedClass* NewAnimBlueprintClass = GetNewAnimBlueprintClass(); + + int32 NewIndex = INDEX_NONE; + for (int32 NotifyIdx = 0; NotifyIdx < GetNewAnimBlueprintClass()->AnimNotifies.Num(); NotifyIdx++) + { + if( (GetNewAnimBlueprintClass()->AnimNotifies[NotifyIdx].NotifyName == Notify.NotifyName) + && (GetNewAnimBlueprintClass()->AnimNotifies[NotifyIdx].Notify == Notify.Notify) + && (GetNewAnimBlueprintClass()->AnimNotifies[NotifyIdx].NotifyStateClass == Notify.NotifyStateClass) + ) + { + NewIndex = NotifyIdx; + break; + } + } + + if (NewIndex == INDEX_NONE) + { + NewIndex = GetNewAnimBlueprintClass()->AnimNotifies.Add(Notify); + } + return NewIndex; +} + +void UAnimBlueprintCompilerSubsystem_StateMachine::PreProcessAnimationNodes(TArrayView InAnimNodes) +{ + GetConsolidatedEventGraph()->GetNodesOfClass(RootTransitionGetters); + + // Get anim getters from the root anim graph (processing the nodes below will collect them in nested graphs) + GetConsolidatedEventGraph()->GetNodesOfClass(RootGraphAnimGetters); +} + +void UAnimBlueprintCompilerSubsystem_StateMachine::PostProcessAnimationNodes(TArrayView InAnimNodes) +{ + // Process the getter nodes in the graph if there were any + for (auto GetterIt = RootTransitionGetters.CreateIterator(); GetterIt; ++GetterIt) + { + ProcessTransitionGetter(*GetterIt, nullptr); // transition nodes should not appear at top-level + } + + // Wire root getters + for(UK2Node_AnimGetter* RootGraphGetter : RootGraphAnimGetters) + { + AutoWireAnimGetter(RootGraphGetter, nullptr); + } + + // Wire nested getters + for(UK2Node_AnimGetter* Getter : FoundGetterNodes) + { + AutoWireAnimGetter(Getter, nullptr); + } +} + +UK2Node_CallFunction* UAnimBlueprintCompilerSubsystem_StateMachine::SpawnCallAnimInstanceFunction(UEdGraphNode* SourceNode, FName FunctionName) +{ + //@TODO: SKELETON: This is a call on a parent function (UAnimInstance::StaticClass() specifically), should we treat it as self or not? + UK2Node_CallFunction* FunctionCall = SpawnIntermediateNode(SourceNode); + FunctionCall->FunctionReference.SetSelfMember(FunctionName); + FunctionCall->AllocateDefaultPins(); + + return FunctionCall; +} + +void UAnimBlueprintCompilerSubsystem_StateMachine::ProcessTransitionGetter(UK2Node_TransitionRuleGetter* Getter, UAnimStateTransitionNode* TransitionNode) +{ + // Get common elements for multiple getters + UEdGraphPin* OutputPin = Getter->GetOutputPin(); + + UEdGraphPin* SourceTimePin = NULL; + UAnimationAsset* AnimAsset= NULL; + int32 PlayerNodeIndex = INDEX_NONE; + + if (UAnimGraphNode_Base* SourcePlayerNode = Getter->AssociatedAnimAssetPlayerNode) + { + // This check should never fail as the source state is always processed first before handling it's rules + UAnimGraphNode_Base* TrueSourceNode = GetMessageLog().FindSourceObjectTypeChecked(SourcePlayerNode); + UAnimGraphNode_Base* UndertypedPlayerNode = GetSourceNodeToProcessedNodeMap().FindRef(TrueSourceNode); + + if (UndertypedPlayerNode == NULL) + { + GetMessageLog().Error(TEXT("ICE: Player node @@ was not processed prior to handling a transition getter @@ that used it"), SourcePlayerNode, Getter); + return; + } + + // Make sure the node is still relevant + UEdGraph* PlayerGraph = UndertypedPlayerNode->GetGraph(); + if (!PlayerGraph->Nodes.Contains(UndertypedPlayerNode)) + { + GetMessageLog().Error(TEXT("@@ is not associated with a node in @@; please delete and recreate it"), Getter, PlayerGraph); + } + + // Make sure the referenced AnimAsset player has been allocated + PlayerNodeIndex = GetAllocationIndexOfNode(UndertypedPlayerNode); + if (PlayerNodeIndex == INDEX_NONE) + { + GetMessageLog().Error(*LOCTEXT("BadAnimAssetNodeUsedInGetter", "@@ doesn't have a valid associated AnimAsset node. Delete and recreate it").ToString(), Getter); + } + + // Grab the AnimAsset, and time pin if needed + UScriptStruct* TimePropertyInStructType = NULL; + const TCHAR* TimePropertyName = NULL; + if (UndertypedPlayerNode->DoesSupportTimeForTransitionGetter()) + { + AnimAsset = UndertypedPlayerNode->GetAnimationAsset(); + TimePropertyInStructType = UndertypedPlayerNode->GetTimePropertyStruct(); + TimePropertyName = UndertypedPlayerNode->GetTimePropertyName(); + } + else + { + GetMessageLog().Error(TEXT("@@ is associated with @@, which is an unexpected type"), Getter, UndertypedPlayerNode); + } + + bool bNeedTimePin = false; + + // Determine if we need to read the current time variable from the specified sequence player + switch (Getter->GetterType) + { + case ETransitionGetter::AnimationAsset_GetCurrentTime: + case ETransitionGetter::AnimationAsset_GetCurrentTimeFraction: + case ETransitionGetter::AnimationAsset_GetTimeFromEnd: + case ETransitionGetter::AnimationAsset_GetTimeFromEndFraction: + bNeedTimePin = true; + break; + default: + bNeedTimePin = false; + break; + } + + if (bNeedTimePin && (PlayerNodeIndex != INDEX_NONE) && (TimePropertyName != NULL) && (TimePropertyInStructType != NULL)) + { + const FProperty* NodeProperty = GetAllocatedPropertiesByIndex().FindChecked(PlayerNodeIndex); + + // Create a struct member read node to grab the current position of the sequence player node + UK2Node_StructMemberGet* TimeReadNode = SpawnIntermediateNode(Getter, GetConsolidatedEventGraph()); + TimeReadNode->VariableReference.SetSelfMember(NodeProperty->GetFName()); + TimeReadNode->StructType = TimePropertyInStructType; + + TimeReadNode->AllocatePinsForSingleMemberGet(TimePropertyName); + SourceTimePin = TimeReadNode->FindPinChecked(TimePropertyName); + } + } + + // Expand it out + UK2Node_CallFunction* GetterHelper = NULL; + switch (Getter->GetterType) + { + case ETransitionGetter::AnimationAsset_GetCurrentTime: + if ((AnimAsset != NULL) && (SourceTimePin != NULL)) + { + GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceAssetPlayerTime")); + GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); + } + else + { + if (Getter->AssociatedAnimAssetPlayerNode) + { + GetMessageLog().Error(TEXT("Please replace @@ with Get Relevant Anim Time. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); + } + else + { + GetMessageLog().Error(TEXT("@@ is not asscociated with an asset player"), Getter); + } + } + break; + case ETransitionGetter::AnimationAsset_GetLength: + if (AnimAsset != NULL) + { + GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceAssetPlayerLength")); + GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); + } + else + { + if (Getter->AssociatedAnimAssetPlayerNode) + { + GetMessageLog().Error(TEXT("Please replace @@ with Get Relevant Anim Length. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); + } + else + { + GetMessageLog().Error(TEXT("@@ is not asscociated with an asset player"), Getter); + } + } + break; + case ETransitionGetter::AnimationAsset_GetCurrentTimeFraction: + if ((AnimAsset != NULL) && (SourceTimePin != NULL)) + { + GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceAssetPlayerTimeFraction")); + GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); + } + else + { + if (Getter->AssociatedAnimAssetPlayerNode) + { + GetMessageLog().Error(TEXT("Please replace @@ with Get Relevant Anim Time Fraction. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); + } + else + { + GetMessageLog().Error(TEXT("@@ is not asscociated with an asset player"), Getter); + } + } + break; + case ETransitionGetter::AnimationAsset_GetTimeFromEnd: + if ((AnimAsset != NULL) && (SourceTimePin != NULL)) + { + GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceAssetPlayerTimeFromEnd")); + GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); + } + else + { + if (Getter->AssociatedAnimAssetPlayerNode) + { + GetMessageLog().Error(TEXT("Please replace @@ with Get Relevant Anim Time Remaining. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); + } + else + { + GetMessageLog().Error(TEXT("@@ is not asscociated with an asset player"), Getter); + } + } + break; + case ETransitionGetter::AnimationAsset_GetTimeFromEndFraction: + if ((AnimAsset != NULL) && (SourceTimePin != NULL)) + { + GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceAssetPlayerTimeFromEndFraction")); + GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); + } + else + { + if (Getter->AssociatedAnimAssetPlayerNode) + { + GetMessageLog().Error(TEXT("Please replace @@ with Get Relevant Anim Time Remaining Fraction. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); + } + else + { + GetMessageLog().Error(TEXT("@@ is not asscociated with an asset player"), Getter); + } + } + break; + + case ETransitionGetter::CurrentTransitionDuration: + { + check(TransitionNode); + if(UAnimStateNode* SourceStateNode = GetMessageLog().FindSourceObjectTypeChecked(TransitionNode->GetPreviousState())) + { + if(UObject* SourceTransitionNode = GetMessageLog().FindSourceObject(TransitionNode)) + { + if(FStateMachineDebugData* DebugData = GetNewAnimBlueprintClass()->GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) + { + if(int32* pStateIndex = DebugData->NodeToStateIndex.Find(SourceStateNode)) + { + const int32 StateIndex = *pStateIndex; + + // This check should never fail as all animation nodes should be processed before getters are + UAnimGraphNode_Base* CompiledMachineInstanceNode = GetSourceNodeToProcessedNodeMap().FindChecked(DebugData->MachineInstanceNode.Get()); + const int32 MachinePropertyIndex = GetAllocatedAnimNodeIndices().FindChecked(CompiledMachineInstanceNode); + int32 TransitionPropertyIndex = INDEX_NONE; + + for(TMap, int32>::TIterator TransIt(DebugData->NodeToTransitionIndex); TransIt; ++TransIt) + { + UEdGraphNode* CurrTransNode = TransIt.Key().Get(); + + if(CurrTransNode == SourceTransitionNode) + { + TransitionPropertyIndex = TransIt.Value(); + break; + } + } + + if(TransitionPropertyIndex != INDEX_NONE) + { + GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceTransitionCrossfadeDuration")); + GetterHelper->FindPinChecked(TEXT("MachineIndex"))->DefaultValue = FString::FromInt(MachinePropertyIndex); + GetterHelper->FindPinChecked(TEXT("TransitionIndex"))->DefaultValue = FString::FromInt(TransitionPropertyIndex); + } + } + } + } + } + } + break; + + case ETransitionGetter::ArbitraryState_GetBlendWeight: + { + if (Getter->AssociatedStateNode) + { + if (UAnimStateNode* SourceStateNode = GetMessageLog().FindSourceObjectTypeChecked(Getter->AssociatedStateNode)) + { + if (FStateMachineDebugData* DebugData = GetNewAnimBlueprintClass()->GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) + { + if (int32* pStateIndex = DebugData->NodeToStateIndex.Find(SourceStateNode)) + { + const int32 StateIndex = *pStateIndex; + //const int32 MachineIndex = DebugData->MachineIndex; + + // This check should never fail as all animation nodes should be processed before getters are + UAnimGraphNode_Base* CompiledMachineInstanceNode = GetSourceNodeToProcessedNodeMap().FindChecked(DebugData->MachineInstanceNode.Get()); + const int32 MachinePropertyIndex = GetAllocatedAnimNodeIndices().FindChecked(CompiledMachineInstanceNode); + + GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceStateWeight")); + GetterHelper->FindPinChecked(TEXT("MachineIndex"))->DefaultValue = FString::FromInt(MachinePropertyIndex); + GetterHelper->FindPinChecked(TEXT("StateIndex"))->DefaultValue = FString::FromInt(StateIndex); + } + } + } + } + + if (GetterHelper == NULL) + { + GetMessageLog().Error(TEXT("@@ is not associated with a valid state"), Getter); + } + } + break; + + case ETransitionGetter::CurrentState_ElapsedTime: + { + check(TransitionNode); + if (UAnimStateNode* SourceStateNode = GetMessageLog().FindSourceObjectTypeChecked(TransitionNode->GetPreviousState())) + { + if (FStateMachineDebugData* DebugData = GetNewAnimBlueprintClass()->GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) + { + // This check should never fail as all animation nodes should be processed before getters are + UAnimGraphNode_Base* CompiledMachineInstanceNode = GetSourceNodeToProcessedNodeMap().FindChecked(DebugData->MachineInstanceNode.Get()); + const int32 MachinePropertyIndex = GetAllocatedAnimNodeIndices().FindChecked(CompiledMachineInstanceNode); + + GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceCurrentStateElapsedTime")); + GetterHelper->FindPinChecked(TEXT("MachineIndex"))->DefaultValue = FString::FromInt(MachinePropertyIndex); + } + } + if (GetterHelper == NULL) + { + GetMessageLog().Error(TEXT("@@ is not associated with a valid state"), Getter); + } + } + break; + + case ETransitionGetter::CurrentState_GetBlendWeight: + { + check(TransitionNode); + if (UAnimStateNode* SourceStateNode = GetMessageLog().FindSourceObjectTypeChecked(TransitionNode->GetPreviousState())) + { + { + if (FStateMachineDebugData* DebugData = GetNewAnimBlueprintClass()->GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) + { + if (int32* pStateIndex = DebugData->NodeToStateIndex.Find(SourceStateNode)) + { + const int32 StateIndex = *pStateIndex; + //const int32 MachineIndex = DebugData->MachineIndex; + + // This check should never fail as all animation nodes should be processed before getters are + UAnimGraphNode_Base* CompiledMachineInstanceNode = GetSourceNodeToProcessedNodeMap().FindChecked(DebugData->MachineInstanceNode.Get()); + const int32 MachinePropertyIndex = GetAllocatedAnimNodeIndices().FindChecked(CompiledMachineInstanceNode); + + GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceStateWeight")); + GetterHelper->FindPinChecked(TEXT("MachineIndex"))->DefaultValue = FString::FromInt(MachinePropertyIndex); + GetterHelper->FindPinChecked(TEXT("StateIndex"))->DefaultValue = FString::FromInt(StateIndex); + } + } + } + } + if (GetterHelper == NULL) + { + GetMessageLog().Error(TEXT("@@ is not associated with a valid state"), Getter); + } + } + break; + + default: + GetMessageLog().Error(TEXT("Unrecognized getter type on @@"), Getter); + break; + } + + // Finish wiring up a call function if needed + if (GetterHelper != NULL) + { + check(GetterHelper->IsNodePure()); + + UEdGraphPin* NewReturnPin = GetterHelper->FindPinChecked(TEXT("ReturnValue")); + GetMessageLog().NotifyIntermediatePinCreation(NewReturnPin, OutputPin); + + NewReturnPin->CopyPersistentDataFromOldPin(*OutputPin); + } + + // Remove the getter from the equation + Getter->BreakAllNodeLinks(); +} + +void UAnimBlueprintCompilerSubsystem_StateMachine::AutoWireAnimGetter(class UK2Node_AnimGetter* Getter, UAnimStateTransitionNode* InTransitionNode) +{ + UEdGraphPin* ReferencedNodeTimePin = nullptr; + int32 ReferencedNodeIndex = INDEX_NONE; + int32 SubNodeIndex = INDEX_NONE; + + UAnimGraphNode_Base* ProcessedNodeCheck = NULL; + + if(UAnimGraphNode_Base* SourceNode = Getter->SourceNode) + { + UAnimGraphNode_Base* ActualSourceNode = GetMessageLog().FindSourceObjectTypeChecked(SourceNode); + + if(UAnimGraphNode_Base* ProcessedSourceNode = GetSourceNodeToProcessedNodeMap().FindRef(ActualSourceNode)) + { + ProcessedNodeCheck = ProcessedSourceNode; + + ReferencedNodeIndex = GetAllocationIndexOfNode(ProcessedSourceNode); + + if(ProcessedSourceNode->DoesSupportTimeForTransitionGetter()) + { + UScriptStruct* TimePropertyInStructType = ProcessedSourceNode->GetTimePropertyStruct(); + const TCHAR* TimePropertyName = ProcessedSourceNode->GetTimePropertyName(); + + if(ReferencedNodeIndex != INDEX_NONE && TimePropertyName && TimePropertyInStructType) + { + FProperty* NodeProperty = GetAllocatedPropertiesByIndex().FindChecked(ReferencedNodeIndex); + + UK2Node_StructMemberGet* ReaderNode = SpawnIntermediateNode(Getter, GetConsolidatedEventGraph()); + ReaderNode->VariableReference.SetSelfMember(NodeProperty->GetFName()); + ReaderNode->StructType = TimePropertyInStructType; + ReaderNode->AllocatePinsForSingleMemberGet(TimePropertyName); + + ReferencedNodeTimePin = ReaderNode->FindPinChecked(TimePropertyName); + } + } + } + } + + if(Getter->SourceStateNode) + { + UObject* SourceObject = GetMessageLog().FindSourceObject(Getter->SourceStateNode); + if(UAnimStateNode* SourceStateNode = Cast(SourceObject)) + { + if(FStateMachineDebugData* DebugData = GetNewAnimBlueprintClass()->GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) + { + if(int32* StateIndexPtr = DebugData->NodeToStateIndex.Find(SourceStateNode)) + { + SubNodeIndex = *StateIndexPtr; + } + } + } + else if(UAnimStateTransitionNode* TransitionNode = Cast(SourceObject)) + { + if(FStateMachineDebugData* DebugData = GetNewAnimBlueprintClass()->GetAnimBlueprintDebugData().StateMachineDebugData.Find(TransitionNode->GetGraph())) + { + if(int32* TransitionIndexPtr = DebugData->NodeToTransitionIndex.Find(TransitionNode)) + { + SubNodeIndex = *TransitionIndexPtr; + } + } + } + } + + check(Getter->IsNodePure()); + + for(UEdGraphPin* Pin : Getter->Pins) + { + // Hook up autowired parameters / pins + if(Pin->PinName == TEXT("CurrentTime")) + { + Pin->MakeLinkTo(ReferencedNodeTimePin); + } + else if(Pin->PinName == TEXT("AssetPlayerIndex") || Pin->PinName == TEXT("MachineIndex")) + { + Pin->DefaultValue = FString::FromInt(ReferencedNodeIndex); + } + else if(Pin->PinName == TEXT("StateIndex") || Pin->PinName == TEXT("TransitionIndex")) + { + Pin->DefaultValue = FString::FromInt(SubNodeIndex); + } + } +} + +int32 UAnimBlueprintCompilerSubsystem_StateMachine::ExpandGraphAndProcessNodes(UEdGraph* SourceGraph, UAnimGraphNode_Base* SourceRootNode, UAnimStateTransitionNode* TransitionNode, TArray* ClonedNodes) +{ + // Clone the nodes from the source graph + // Note that we outer this graph to the ConsolidatedEventGraph to allow ExpandSplitPins to + // correctly retrieve the context for any expanded function calls (custom make/break structs etc.) + UEdGraph* ClonedGraph = FEdGraphUtilities::CloneGraph(SourceGraph, GetConsolidatedEventGraph(), &GetMessageLog(), true); + + // Grab all the animation nodes and find the corresponding root node in the cloned set + UAnimGraphNode_Base* TargetRootNode = nullptr; + TArray AnimNodeList; + TArray Getters; + TArray AnimGetterNodes; + + for (auto NodeIt = ClonedGraph->Nodes.CreateIterator(); NodeIt; ++NodeIt) + { + UEdGraphNode* Node = *NodeIt; + + if (UK2Node_TransitionRuleGetter* GetterNode = Cast(Node)) + { + Getters.Add(GetterNode); + } + else if(UK2Node_AnimGetter* NewGetterNode = Cast(Node)) + { + AnimGetterNodes.Add(NewGetterNode); + } + else if (UAnimGraphNode_Base* TestNode = Cast(Node)) + { + AnimNodeList.Add(TestNode); + + //@TODO: There ought to be a better way to determine this + if (GetMessageLog().FindSourceObject(TestNode) == GetMessageLog().FindSourceObject(SourceRootNode)) + { + TargetRootNode = TestNode; + } + } + + if (ClonedNodes != NULL) + { + ClonedNodes->Add(Node); + } + } + check(TargetRootNode); + + // Run another expansion pass to catch the graph we just added (this is slightly wasteful + ExpansionStep(ClonedGraph, false); + + // Validate graph now we have expanded/pruned + ValidateGraphIsWellFormed(ClonedGraph); + + // Move the cloned nodes into the consolidated event graph + const bool bIsLoading = GetBlueprint()->bIsRegeneratingOnLoad || IsAsyncLoading(); + const bool bIsCompiling = GetBlueprint()->bBeingCompiled; + ClonedGraph->MoveNodesToAnotherGraph(GetConsolidatedEventGraph(), bIsLoading, bIsCompiling); + + // Process any animation nodes + { + TArray RootSet; + RootSet.Add(TargetRootNode); + + PruneIsolatedAnimationNodes(RootSet, AnimNodeList); + + ProcessAnimationNodes(AnimNodeList); + } + + // Process the getter nodes in the graph if there were any + for (auto GetterIt = Getters.CreateIterator(); GetterIt; ++GetterIt) + { + ProcessTransitionGetter(*GetterIt, TransitionNode); + } + + // Wire anim getter nodes + for(UK2Node_AnimGetter* GetterNode : AnimGetterNodes) + { + FoundGetterNodes.Add(GetterNode); + } + + // Returns the index of the processed cloned version of SourceRootNode + return GetAllocationIndexOfNode(TargetRootNode); +} + +bool UAnimBlueprintCompilerSubsystem_StateMachine::ShouldProcessFunctionGraph(UEdGraph* InGraph) const +{ + if (InGraph->Schema->IsChildOf(UAnimationStateMachineSchema::StaticClass())) + { + return false; + } + + return true; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_StateMachine.h b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_StateMachine.h new file mode 100644 index 000000000000..4794eee8820f --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompilerSubsystem_StateMachine.h @@ -0,0 +1,58 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AnimBlueprintCompilerSubsystem.h" + +#include "AnimBlueprintCompilerSubsystem_StateMachine.generated.h" + +class UK2Node_CallFunction; +class UK2Node_AnimGetter; +class UK2Node_TransitionRuleGetter; +class UAnimGraphNode_Base; +class UAnimStateTransitionNode; +class UEdGraph; +class UEdGraphNode; +struct FAnimNotifyEvent; + +UCLASS() +class UAnimBlueprintCompilerSubsystem_StateMachine : public UAnimBlueprintCompilerSubsystem +{ + GENERATED_BODY() + +public: + // Finds or adds a notify event triggered from a state machine + int32 FindOrAddNotify(FAnimNotifyEvent& Notify); + + // This function does the following steps: + // Clones the nodes in the specified source graph + // Merges them into the ConsolidatedEventGraph + // Processes any animation nodes + // Returns the index of the processed cloned version of SourceRootNode + // If supplied, will also return an array of all cloned nodes + int32 ExpandGraphAndProcessNodes(UEdGraph* SourceGraph, UAnimGraphNode_Base* SourceRootNode, UAnimStateTransitionNode* TransitionNode = nullptr, TArray* ClonedNodes = nullptr); + +private: + // UAnimBlueprintCompilerSubsystem interface + virtual void PreProcessAnimationNodes(TArrayView InAnimNodes) override; + virtual void PostProcessAnimationNodes(TArrayView InAnimNodes) override; + virtual bool ShouldProcessFunctionGraph(UEdGraph* InGraph) const override; + + // Spawns a function call node, calling a function on the anim instance + UK2Node_CallFunction* SpawnCallAnimInstanceFunction(UEdGraphNode* SourceNode, FName FunctionName); + + // Convert transition getters into a function call/etc... + void ProcessTransitionGetter(UK2Node_TransitionRuleGetter* Getter, UAnimStateTransitionNode* TransitionNode); + + // Automatically fill in parameters for the specified Getter node + void AutoWireAnimGetter(UK2Node_AnimGetter* Getter, UAnimStateTransitionNode* InTransitionNode); + +private: + // List of getter node's we've found so the auto-wire can be deferred till after state machine compilation + TArray FoundGetterNodes; + + // Preprocessed lists of getters from the root of the ubergraph + TArray RootTransitionGetters; + TArray RootGraphAnimGetters; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintNodeOptionalPinManager.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintNodeOptionalPinManager.cpp index 5ce55ab31f02..88c6bab6630b 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintNodeOptionalPinManager.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintNodeOptionalPinManager.cpp @@ -36,8 +36,9 @@ void FAnimBlueprintNodeOptionalPinManager::GetRecordDefaults(FProperty* TestProp const bool bOptional_HideByDefault = TestProperty->HasMetaData(Schema->NAME_PinHiddenByDefault); const bool bNeverShow = TestProperty->HasMetaData(Schema->NAME_NeverAsPin); const bool bPropertyIsCustomized = TestProperty->HasMetaData(Schema->NAME_CustomizeProperty); + const bool bCanTreatPropertyAsOptional = CanTreatPropertyAsOptional(TestProperty); - Record.bCanToggleVisibility = bOptional_ShowByDefault || bOptional_HideByDefault; + Record.bCanToggleVisibility = bCanTreatPropertyAsOptional && (bOptional_ShowByDefault || bOptional_HideByDefault); Record.bShowPin = bAlwaysShow || bOptional_ShowByDefault; Record.bPropertyIsCustomized = bPropertyIsCustomized; } diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphModule.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphModule.cpp index 2a7a9d68b5ec..2582dd0f7ec4 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphModule.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphModule.cpp @@ -23,6 +23,7 @@ #include "BlueprintEditorModule.h" #include "AnimGraphDetails.h" #include "AnimationGraphSchema.h" +#include "AnimBlueprintCompiler.h" IMPLEMENT_MODULE(FAnimGraphModule, AnimGraph); @@ -32,6 +33,11 @@ void FAnimGraphModule::StartupModule() { FAnimGraphCommands::Register(); + FKismetCompilerContext::RegisterCompilerForBP(UAnimBlueprint::StaticClass(), [](UBlueprint* InBlueprint, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompileOptions) + { + return MakeShared(CastChecked(InBlueprint), InMessageLog, InCompileOptions); + }); + // Register the editor modes FEditorModeRegistry::Get().RegisterMode(AnimNodeEditModes::AnimNode, LOCTEXT("AnimNodeEditMode", "Anim Node"), FSlateIcon(), false); FEditorModeRegistry::Get().RegisterMode(AnimNodeEditModes::TwoBoneIK, LOCTEXT("TwoBoneIKEditMode", "2-Bone IK"), FSlateIcon(), false); @@ -49,9 +55,15 @@ void FAnimGraphModule::StartupModule() PropertyModule.RegisterCustomPropertyTypeLayout("AnimBlueprintFunctionPinInfo", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAnimBlueprintFunctionPinInfoDetails::MakeInstance)); - // Register BP-editor function customization - FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::LoadModuleChecked("Kismet"); - BlueprintEditorModule.RegisterGraphCustomization(GetDefault(), FOnGetGraphCustomizationInstance::CreateStatic(&FAnimGraphDetails::MakeInstance)); + FModuleManager::Get().OnModulesChanged().AddLambda([](FName InModuleName, EModuleChangeReason InReason) + { + if(InReason == EModuleChangeReason::ModuleLoaded && InModuleName == "Kismet") + { + // Register BP-editor function customization + FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::LoadModuleChecked("Kismet"); + BlueprintEditorModule.RegisterGraphCustomization(GetDefault(), FOnGetGraphCustomizationInstance::CreateStatic(&FAnimGraphDetails::MakeInstance)); + } + }); } void FAnimGraphModule::ShutdownModule() diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_AssetPlayerBase.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_AssetPlayerBase.cpp index 41a611c06773..c53054fcdeef 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_AssetPlayerBase.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_AssetPlayerBase.cpp @@ -16,6 +16,8 @@ #include "AnimGraphNode_PoseByName.h" #include "AnimGraphNode_PoseDriver.h" #include "UObject/UObjectIterator.h" +#include "AnimBlueprintCompiler.h" +#include "Animation/AnimLayerInterface.h" void UAnimGraphNode_AssetPlayerBase::PinConnectionListChanged(UEdGraphPin* Pin) { @@ -45,6 +47,54 @@ void UAnimGraphNode_AssetPlayerBase::PinDefaultValueChanged(UEdGraphPin* Pin) } } +void UAnimGraphNode_AssetPlayerBase::OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) +{ + UBlueprint* Blueprint = GetBlueprint(); + UAnimBlueprintGeneratedClass* NewAnimBlueprintClass = CastChecked(InCompilerContext.NewClass); + + // Process Asset Player nodes to, if necessary cache off their node index for retrieval at runtime (used for evaluating Automatic Rule Transitions when using Layer nodes) + auto ProcessGraph = [this, NewAnimBlueprintClass](UEdGraph* Graph) + { + // Make sure we do not process the default AnimGraph + static const FName DefaultAnimGraphName("AnimGraph"); + if (Graph->GetFName() != DefaultAnimGraphName) + { + FString GraphName = Graph->GetName(); + // Also make sure we do not process any empty stub graphs + if (!GraphName.Contains(ANIM_FUNC_DECORATOR)) + { + if (Graph->Nodes.ContainsByPredicate([this, NewAnimBlueprintClass](UEdGraphNode* Node) { return Node->NodeGuid == NodeGuid; })) + { + if (int32* IndexPtr = NewAnimBlueprintClass->AnimBlueprintDebugData.NodeGuidToIndexMap.Find(NodeGuid)) + { + FGraphAssetPlayerInformation& Info = NewAnimBlueprintClass->GraphAssetPlayerInformation.FindOrAdd(FName(*GraphName)); + Info.PlayerNodeIndices.AddUnique(*IndexPtr); + } + } + } + } + }; + + // Check for any definition of a layer graph + for (UEdGraph* Graph : Blueprint->FunctionGraphs) + { + ProcessGraph(Graph); + } + + // Check for any implemented AnimLayer interface graphs + for (FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) + { + // Only process Anim Layer interfaces + if (InterfaceDesc.Interface->IsChildOf()) + { + for (UEdGraph* Graph : InterfaceDesc.Graphs) + { + ProcessGraph(Graph); + } + } + } +} + UClass* GetNodeClassForAsset(const UClass* AssetClass) { UClass* NodeClass = nullptr; diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_Base.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_Base.cpp index 46815735a208..442f701a2471 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_Base.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_Base.cpp @@ -17,6 +17,9 @@ #include "UObject/UnrealType.h" #include "Kismet2/CompilerResultsLog.h" #include "Subsystems/AssetEditorSubsystem.h" +#include "AnimBlueprintCompiler.h" +#include "AnimBlueprintCompilerSubsystem_Base.h" +#include "FindInBlueprintManager.h" #define LOCTEXT_NAMESPACE "UAnimGraphNode_Base" @@ -28,6 +31,12 @@ UAnimGraphNode_Base::UAnimGraphNode_Base(const FObjectInitializer& ObjectInitial { } +void UAnimGraphNode_Base::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) +{ + UAnimBlueprintCompilerSubsystem_Base* Subsystem = static_cast(&CompilerContext)->GetSubsystem(); + Subsystem->CreateEvaluationHandlerForNode(this); +} + void UAnimGraphNode_Base::PreEditChange(FProperty* PropertyThatWillChange) { Super::PreEditChange(PropertyThatWillChange); @@ -329,6 +338,17 @@ void UAnimGraphNode_Base::GetPinHoverText(const UEdGraphPin& Pin, FString& Hover } } +void UAnimGraphNode_Base::ProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) +{ + UAnimBlueprintCompilerSubsystem_Base* SubsystemBase = InCompilerContext.GetSubsystem(); + + // Record pose pins for later patchup and gather pins that have an associated evaluation handler + SubsystemBase->AddStructEvalHandlers(this); + + // Call the override point + OnProcessDuringCompilation(InCompilerContext); +} + void UAnimGraphNode_Base::HandleAnimReferenceCollection(UAnimationAsset* AnimAsset, TArray& AnimationAssets) const { if(AnimAsset) @@ -383,9 +403,9 @@ FAnimNode_Base* UAnimGraphNode_Base::FindDebugAnimNode(USkeletalMeshComponent * { int32 AnimNodeIndex = *IndexPtr; // reverse node index temporarily because of a bug in NodeGuidToIndexMap - AnimNodeIndex = AnimBlueprintClass->AnimNodeProperties.Num() - AnimNodeIndex - 1; + AnimNodeIndex = AnimBlueprintClass->GetAnimNodeProperties().Num() - AnimNodeIndex - 1; - DebugNode = AnimBlueprintClass->AnimNodeProperties[AnimNodeIndex]->ContainerPtrToValuePtr(PreviewSkelMeshComp->GetAnimInstance()); + DebugNode = AnimBlueprintClass->GetAnimNodeProperties()[AnimNodeIndex]->ContainerPtrToValuePtr(PreviewSkelMeshComp->GetAnimInstance()); } } } @@ -432,6 +452,17 @@ FString UAnimGraphNode_Base::GetPinMetaData(FName InPinName, FName InKey) return MetaData; } +void UAnimGraphNode_Base::AddSearchMetaDataInfo(TArray& OutTaggedMetaData) const +{ + Super::AddSearchMetaDataInfo(OutTaggedMetaData); + + for(const TPair& BindingPair : PropertyBindings) + { + OutTaggedMetaData.Add(FSearchTagDataPair(FFindInBlueprintSearchTags::FiB_Name, FText::FromName(BindingPair.Key))); + OutTaggedMetaData.Add(FSearchTagDataPair(LOCTEXT("Binding", "Binding"), BindingPair.Value.PathAsText)); + } +} + bool UAnimGraphNode_Base::IsPinExposedAndLinked(const FString& InPinName, const EEdGraphPinDirection InDirection) const { UEdGraphPin* Pin = FindPin(InPinName, InDirection); diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_CustomProperty.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_CustomProperty.cpp index e4ebf6459cb9..29319fba0e3a 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_CustomProperty.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_CustomProperty.cpp @@ -14,9 +14,72 @@ #include "Widgets/SBoxPanel.h" #include "DetailWidgetRow.h" #include "PropertyCustomizationHelpers.h" +#include "KismetCompilerMisc.h" +#include "KismetCompiler.h" #define LOCTEXT_NAMESPACE "CustomPropNode" +void UAnimGraphNode_CustomProperty::CreateClassVariablesFromBlueprint(FKismetCompilerContext& InCompilerContext) +{ + for (UEdGraphPin* Pin : Pins) + { + if (!Pin->bOrphanedPin && !UAnimationGraphSchema::IsPosePin(Pin->PinType)) + { + // avoid to add properties which already exist on the custom node. + // for example the ControlRig_CustomNode has a pin called "alpha" which is not custom. + if (FStructProperty* NodeProperty = CastField(GetClass()->FindPropertyByName(TEXT("Node")))) + { + if(NodeProperty->Struct->FindPropertyByName(Pin->GetFName())) + { + continue; + } + } + + // Add prefix to avoid collisions + FString PrefixedName = GetPinTargetVariableName(Pin); + + // Create a property on the new class to hold the pin data + FProperty* NewProperty = FKismetCompilerUtilities::CreatePropertyOnScope(InCompilerContext.NewClass, FName(*PrefixedName), Pin->PinType, InCompilerContext.NewClass, CPF_None, CastChecked(GetSchema()), InCompilerContext.MessageLog); + if (NewProperty) + { + FKismetCompilerUtilities::LinkAddedProperty(InCompilerContext.NewClass, NewProperty); + } + } + } +} + +void UAnimGraphNode_CustomProperty::OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) +{ + for (UEdGraphPin* Pin : Pins) + { + if (!Pin->bOrphanedPin && !UAnimationGraphSchema::IsPosePin(Pin->PinType)) + { + // avoid to add properties which already exist on the custom node. + // for example the ControlRig_CustomNode has a pin called "alpha" which is not custom. + if (FStructProperty* NodeProperty = CastField(GetClass()->FindPropertyByName(TEXT("Node")))) + { + if(NodeProperty->Struct->FindPropertyByName(Pin->GetFName())) + { + continue; + } + } + + FString PrefixedName = GetPinTargetVariableName(Pin); + + // Add mappings to the node + UClass* InstClass = GetTargetSkeletonClass(); + if (FProperty* FoundProperty = FindFProperty(InstClass, Pin->PinName)) + { + AddSourceTargetProperties(*PrefixedName, FoundProperty->GetFName()); + } + else + { + AddSourceTargetProperties(*PrefixedName, Pin->GetFName()); + } + } + } +} + void UAnimGraphNode_CustomProperty::ValidateAnimNodeDuringCompilation(USkeleton* ForSkeleton, FCompilerResultsLog& MessageLog) { Super::ValidateAnimNodeDuringCompilation(ForSkeleton, MessageLog); diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_LinkedAnimGraphBase.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_LinkedAnimGraphBase.cpp index 4b5a4e09970a..117939ac38e4 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_LinkedAnimGraphBase.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_LinkedAnimGraphBase.cpp @@ -24,6 +24,29 @@ namespace LinkedAnimGraphGraphNodeConstants FLinearColor TitleColor(0.2f, 0.2f, 0.8f); } +void UAnimGraphNode_LinkedAnimGraphBase::AllocatePoseLinks() +{ + FAnimNode_LinkedAnimGraph& RuntimeNode = *GetLinkedAnimGraphNode(); + + RuntimeNode.InputPoses.Empty(); + RuntimeNode.InputPoseNames.Empty(); + + for(UEdGraphPin* Pin : Pins) + { + if(!Pin->bOrphanedPin) + { + if (UAnimationGraphSchema::IsPosePin(Pin->PinType)) + { + if(Pin->Direction == EGPD_Input) + { + RuntimeNode.InputPoses.AddDefaulted(); + RuntimeNode.InputPoseNames.Add(Pin->GetFName()); + } + } + } + } +} + FLinearColor UAnimGraphNode_LinkedAnimGraphBase::GetNodeTitleColor() const { return LinkedAnimGraphGraphNodeConstants::TitleColor; diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_LinkedInputPose.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_LinkedInputPose.cpp index c15f719c24cc..4b6b25267747 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_LinkedInputPose.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_LinkedInputPose.cpp @@ -12,6 +12,7 @@ #include "DetailLayoutBuilder.h" #include "Widgets/Images/SImage.h" #include "Widgets/Text/STextBlock.h" +#include "Stats/Stats.h" #include "Animation/AnimBlueprint.h" #include "IAnimationBlueprintEditor.h" @@ -23,6 +24,9 @@ #include "Containers/Ticker.h" #include "Widgets/Input/SCheckBox.h" #include "Subsystems/AssetEditorSubsystem.h" +#include "KismetCompiler.h" +#include "K2Node_VariableGet.h" +#include "AnimBlueprintCompiler.h" #define LOCTEXT_NAMESPACE "LinkedInputPose" @@ -31,6 +35,75 @@ UAnimGraphNode_LinkedInputPose::UAnimGraphNode_LinkedInputPose() { } +void UAnimGraphNode_LinkedInputPose::CreateClassVariablesFromBlueprint(FKismetCompilerContext& InCompilerContext) +{ + IterateFunctionParameters([this, &InCompilerContext](const FName& InName, FEdGraphPinType InPinType) + { + if(!UAnimationGraphSchema::IsPosePin(InPinType)) + { + UEdGraphPin* Pin = FindPin(InName, EGPD_Output); + if(Pin && Pin->LinkedTo.Num() > 0) + { + // create properties for 'local' linked input pose pins + FProperty* NewLinkedInputPoseProperty = static_cast(&InCompilerContext)->CreateVariable(InName, InPinType); + if(NewLinkedInputPoseProperty) + { + NewLinkedInputPoseProperty->SetPropertyFlags(CPF_BlueprintReadOnly); + } + } + } + }); +} + +void UAnimGraphNode_LinkedInputPose::ExpandNode(class FKismetCompilerContext& InCompilerContext, UEdGraph* InSourceGraph) +{ + IterateFunctionParameters([this, &InCompilerContext](const FName& InName, FEdGraphPinType InPinType) + { + if(!UAnimationGraphSchema::IsPosePin(InPinType)) + { + if(InCompilerContext.bIsFullCompile) + { + // Find the property we created in CreateClassVariablesFromBlueprint() + FProperty* LinkedInputPoseProperty = FindFProperty(InCompilerContext.NewClass, InName); + if(LinkedInputPoseProperty) + { + UEdGraphPin* Pin = FindPin(InName, EGPD_Output); + if(Pin && Pin->LinkedTo.Num() > 0) + { + // Create new node for property access + UK2Node_VariableGet* VariableGetNode = InCompilerContext.SpawnIntermediateNode(this, GetGraph()); + VariableGetNode->SetFromProperty(LinkedInputPoseProperty, true, LinkedInputPoseProperty->GetOwnerClass()); + VariableGetNode->AllocateDefaultPins(); + + // Add pin to generated variable association, used for pin watching + UEdGraphPin* TrueSourcePin = InCompilerContext.MessageLog.FindSourcePin(Pin); + if (TrueSourcePin) + { + InCompilerContext.NewClass->GetDebugData().RegisterClassPropertyAssociation(TrueSourcePin, LinkedInputPoseProperty); + } + + // link up to new node - note that this is not a FindPinChecked because if an interface changes without the + // implementing class being loaded, then its graphs will not be conformed until AFTER the skeleton class + // has been compiled, so the variable cannot be created. This also doesnt matter, as there wont be anything connected + // to the pin yet anyways. + UEdGraphPin* VariablePin = VariableGetNode->FindPin(LinkedInputPoseProperty->GetFName()); + if(VariablePin) + { + TArray Links = Pin->LinkedTo; + Pin->BreakAllPinLinks(); + + for(UEdGraphPin* LinkPin : Links) + { + VariablePin->MakeLinkTo(LinkPin); + } + } + } + } + } + } + }); +} + void UAnimGraphNode_LinkedInputPose::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); @@ -269,6 +342,7 @@ void UAnimGraphNode_LinkedInputPose::PostPlacedNewNode() FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([WeakThis = TWeakObjectPtr(this)](float InDeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_UAnimGraphNode_LinkedInputPose_PostPlacedNewNode); if(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode = WeakThis.Get()) { // refresh the BP editor's details panel in case we are viewing the graph diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_Root.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_Root.cpp index e3dcbfa51030..f277f013f50e 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_Root.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_Root.cpp @@ -2,7 +2,7 @@ #include "AnimGraphNode_Root.h" #include "GraphEditorSettings.h" - +#include "AnimBlueprintCompiler.h" ///////////////////////////////////////////////////// // FPoseLinkMappingRecord @@ -85,4 +85,11 @@ FString UAnimGraphNode_Root::GetDocumentationLink() const return TEXT("Shared/GraphNodes/AnimationStateMachine"); } +void UAnimGraphNode_Root::OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) +{ + UAnimGraphNode_Root* TrueNode = InCompilerContext.MessageLog.FindSourceObjectTypeChecked(this); + + Node.Name = TrueNode->GetGraph()->GetFName(); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_StateMachineBase.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_StateMachineBase.cpp index 1f788984bcce..ba8cbc4e0adc 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_StateMachineBase.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_StateMachineBase.cpp @@ -11,6 +11,21 @@ #include "AnimationStateMachineSchema.h" #include "AnimGraphNode_StateMachine.h" #include "Kismet2/KismetEditorUtilities.h" +#include "AnimBlueprintCompiler.h" +#include "AnimStateNode.h" +#include "AnimStateTransitionNode.h" +#include "AnimGraphNode_TransitionResult.h" +#include "AnimGraphNode_StateResult.h" +#include "AnimStateEntryNode.h" +#include "AnimationStateGraph.h" +#include "AnimationTransitionGraph.h" +#include "AnimationCustomTransitionGraph.h" +#include "AnimGraphNode_SequencePlayer.h" +#include "AnimStateConduitNode.h" +#include "AnimGraphNode_LinkedAnimLayer.h" +#include "AnimGraphNode_TransitionPoseEvaluator.h" +#include "AnimGraphNode_CustomTransitionResult.h" +#include "AnimBlueprintCompilerSubsystem_StateMachine.h" ///////////////////////////////////////////////////// // FAnimStateMachineNodeNameValidator @@ -195,4 +210,382 @@ void UAnimGraphNode_StateMachineBase::OnRenameNode(const FString& NewName) FBlueprintEditorUtils::RenameGraph(EditorStateMachineGraph, NewName); } +void UAnimGraphNode_StateMachineBase::OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) +{ + UAnimBlueprintCompilerSubsystem_StateMachine* CompilerSubsystem = InCompilerContext.GetSubsystem(); + check(CompilerSubsystem); + + struct FMachineCreator + { + public: + int32 MachineIndex; + TMap StateIndexTable; + TMap TransitionIndexTable; + UAnimBlueprintGeneratedClass* AnimBlueprintClass; + UAnimGraphNode_StateMachineBase* StateMachineInstance; + FCompilerResultsLog& MessageLog; + public: + FMachineCreator(FCompilerResultsLog& InMessageLog, UAnimGraphNode_StateMachineBase* InStateMachineInstance, int32 InMachineIndex, UAnimBlueprintGeneratedClass* InNewClass) + : MachineIndex(InMachineIndex) + , AnimBlueprintClass(InNewClass) + , StateMachineInstance(InStateMachineInstance) + , MessageLog(InMessageLog) + { + FStateMachineDebugData& MachineInfo = GetMachineSpecificDebugData(); + MachineInfo.MachineIndex = MachineIndex; + MachineInfo.MachineInstanceNode = MessageLog.FindSourceObjectTypeChecked(InStateMachineInstance); + + StateMachineInstance->GetNode().StateMachineIndexInClass = MachineIndex; + + FBakedAnimationStateMachine& BakedMachine = GetMachine(); + BakedMachine.MachineName = StateMachineInstance->EditorStateMachineGraph->GetFName(); + BakedMachine.InitialState = INDEX_NONE; + } + + FBakedAnimationStateMachine& GetMachine() + { + return AnimBlueprintClass->BakedStateMachines[MachineIndex]; + } + + FStateMachineDebugData& GetMachineSpecificDebugData() + { + UAnimationStateMachineGraph* SourceGraph = MessageLog.FindSourceObjectTypeChecked(StateMachineInstance->EditorStateMachineGraph); + return AnimBlueprintClass->GetAnimBlueprintDebugData().StateMachineDebugData.FindOrAdd(SourceGraph); + } + + int32 FindOrAddState(UAnimStateNodeBase* StateNode) + { + if (int32* pResult = StateIndexTable.Find(StateNode)) + { + return *pResult; + } + else + { + FBakedAnimationStateMachine& BakedMachine = GetMachine(); + + const int32 StateIndex = BakedMachine.States.Num(); + StateIndexTable.Add(StateNode, StateIndex); + new (BakedMachine.States) FBakedAnimationState(); + + UAnimStateNodeBase* SourceNode = MessageLog.FindSourceObjectTypeChecked(StateNode); + GetMachineSpecificDebugData().NodeToStateIndex.Add(SourceNode, StateIndex); + if (UAnimStateNode* SourceStateNode = Cast(SourceNode)) + { + AnimBlueprintClass->GetAnimBlueprintDebugData().StateGraphToNodeMap.Add(SourceStateNode->BoundGraph, SourceStateNode); + } + + return StateIndex; + } + } + + int32 FindOrAddTransition(UAnimStateTransitionNode* TransitionNode) + { + if (int32* pResult = TransitionIndexTable.Find(TransitionNode)) + { + return *pResult; + } + else + { + FBakedAnimationStateMachine& BakedMachine = GetMachine(); + + const int32 TransitionIndex = BakedMachine.Transitions.Num(); + TransitionIndexTable.Add(TransitionNode, TransitionIndex); + new (BakedMachine.Transitions) FAnimationTransitionBetweenStates(); + + UAnimStateTransitionNode* SourceTransitionNode = MessageLog.FindSourceObjectTypeChecked(TransitionNode); + GetMachineSpecificDebugData().NodeToTransitionIndex.Add(SourceTransitionNode, TransitionIndex); + AnimBlueprintClass->GetAnimBlueprintDebugData().TransitionGraphToNodeMap.Add(SourceTransitionNode->BoundGraph, SourceTransitionNode); + + if (SourceTransitionNode->CustomTransitionGraph != NULL) + { + AnimBlueprintClass->GetAnimBlueprintDebugData().TransitionBlendGraphToNodeMap.Add(SourceTransitionNode->CustomTransitionGraph, SourceTransitionNode); + } + + return TransitionIndex; + } + } + + void Validate() + { + FBakedAnimationStateMachine& BakedMachine = GetMachine(); + + // Make sure there is a valid entry point + if (BakedMachine.InitialState == INDEX_NONE) + { + MessageLog.Warning(*LOCTEXT("NoEntryNode", "There was no entry state connection in @@").ToString(), StateMachineInstance); + BakedMachine.InitialState = 0; + } + else + { + // Make sure the entry node is a state and not a conduit + if (BakedMachine.States[BakedMachine.InitialState].bIsAConduit) + { + UEdGraphNode* StateNode = GetMachineSpecificDebugData().FindNodeFromStateIndex(BakedMachine.InitialState); + MessageLog.Error(*LOCTEXT("BadStateEntryNode", "A conduit (@@) cannot be used as the entry node for a state machine").ToString(), StateNode); + } + } + } + }; + + if (EditorStateMachineGraph == NULL) + { + CompilerSubsystem->GetMessageLog().Error(*LOCTEXT("BadStateMachineNoGraph", "@@ does not have a corresponding graph").ToString(), this); + return; + } + + TMap AlreadyMergedTransitionList; + + const int32 MachineIndex = CompilerSubsystem->GetNewAnimBlueprintClass()->GetBakedStateMachines().Num(); + new (CompilerSubsystem->GetNewAnimBlueprintClass()->BakedStateMachines) FBakedAnimationStateMachine(); + FMachineCreator Oven(CompilerSubsystem->GetMessageLog(), this, MachineIndex, CompilerSubsystem->GetNewAnimBlueprintClass()); + + // Map of states that contain a single player node (from state root node index to associated sequence player) + TMap SimplePlayerStatesMap; + + // Process all the states/transitions + for (auto StateNodeIt = EditorStateMachineGraph->Nodes.CreateIterator(); StateNodeIt; ++StateNodeIt) + { + UEdGraphNode* Node = *StateNodeIt; + + if (UAnimStateEntryNode* EntryNode = Cast(Node)) + { + // Handle the state graph entry + FBakedAnimationStateMachine& BakedMachine = Oven.GetMachine(); + if (BakedMachine.InitialState != INDEX_NONE) + { + CompilerSubsystem->GetMessageLog().Error(*LOCTEXT("TooManyStateMachineEntryNodes", "Found an extra entry node @@").ToString(), EntryNode); + } + else if (UAnimStateNodeBase* StartState = Cast(EntryNode->GetOutputNode())) + { + BakedMachine.InitialState = Oven.FindOrAddState(StartState); + } + else + { + CompilerSubsystem->GetMessageLog().Warning(*LOCTEXT("NoConnection", "Entry node @@ is not connected to state").ToString(), EntryNode); + } + } + else if (UAnimStateTransitionNode* TransitionNode = Cast(Node)) + { + TransitionNode->ValidateNodeDuringCompilation(CompilerSubsystem->GetMessageLog()); + + const int32 TransitionIndex = Oven.FindOrAddTransition(TransitionNode); + FAnimationTransitionBetweenStates& BakedTransition = Oven.GetMachine().Transitions[TransitionIndex]; + + BakedTransition.CrossfadeDuration = TransitionNode->CrossfadeDuration; + BakedTransition.StartNotify = CompilerSubsystem->FindOrAddNotify(TransitionNode->TransitionStart); + BakedTransition.EndNotify = CompilerSubsystem->FindOrAddNotify(TransitionNode->TransitionEnd); + BakedTransition.InterruptNotify = CompilerSubsystem->FindOrAddNotify(TransitionNode->TransitionInterrupt); + BakedTransition.BlendMode = TransitionNode->BlendMode; + BakedTransition.CustomCurve = TransitionNode->CustomBlendCurve; + BakedTransition.BlendProfile = TransitionNode->BlendProfile; + BakedTransition.LogicType = TransitionNode->LogicType; + + UAnimStateNodeBase* PreviousState = TransitionNode->GetPreviousState(); + UAnimStateNodeBase* NextState = TransitionNode->GetNextState(); + + if ((PreviousState != NULL) && (NextState != NULL)) + { + const int32 PreviousStateIndex = Oven.FindOrAddState(PreviousState); + const int32 NextStateIndex = Oven.FindOrAddState(NextState); + + if (TransitionNode->Bidirectional) + { + CompilerSubsystem->GetMessageLog().Warning(*LOCTEXT("BidirectionalTransWarning", "Bidirectional transitions aren't supported yet @@").ToString(), TransitionNode); + } + + BakedTransition.PreviousState = PreviousStateIndex; + BakedTransition.NextState = NextStateIndex; + } + else + { + CompilerSubsystem->GetMessageLog().Warning(*LOCTEXT("BogusTransition", "@@ is incomplete, without a previous and next state").ToString(), TransitionNode); + BakedTransition.PreviousState = INDEX_NONE; + BakedTransition.NextState = INDEX_NONE; + } + } + else if (UAnimStateNode* StateNode = Cast(Node)) + { + StateNode->ValidateNodeDuringCompilation(CompilerSubsystem->GetMessageLog()); + + const int32 StateIndex = Oven.FindOrAddState(StateNode); + FBakedAnimationState& BakedState = Oven.GetMachine().States[StateIndex]; + + if (StateNode->BoundGraph != NULL) + { + BakedState.StateName = StateNode->BoundGraph->GetFName(); + BakedState.StartNotify = CompilerSubsystem->FindOrAddNotify(StateNode->StateEntered); + BakedState.EndNotify = CompilerSubsystem->FindOrAddNotify(StateNode->StateLeft); + BakedState.FullyBlendedNotify = CompilerSubsystem->FindOrAddNotify(StateNode->StateFullyBlended); + BakedState.bIsAConduit = false; + BakedState.bAlwaysResetOnEntry = StateNode->bAlwaysResetOnEntry; + + // Process the inner graph of this state + if (UAnimGraphNode_StateResult* AnimGraphResultNode = CastChecked(StateNode->BoundGraph)->GetResultNode()) + { + CompilerSubsystem->ValidateGraphIsWellFormed(StateNode->BoundGraph); + + BakedState.StateRootNodeIndex = CompilerSubsystem->ExpandGraphAndProcessNodes(StateNode->BoundGraph, AnimGraphResultNode); + + // See if the state consists of a single sequence player node, and remember the index if so + for (UEdGraphPin* TestPin : AnimGraphResultNode->Pins) + { + if ((TestPin->Direction == EGPD_Input) && (TestPin->LinkedTo.Num() == 1)) + { + if (UAnimGraphNode_SequencePlayer* SequencePlayer = Cast(TestPin->LinkedTo[0]->GetOwningNode())) + { + SimplePlayerStatesMap.Add(BakedState.StateRootNodeIndex, CompilerSubsystem->GetMessageLog().FindSourceObject(SequencePlayer)); + } + } + } + } + else + { + BakedState.StateRootNodeIndex = INDEX_NONE; + CompilerSubsystem->GetMessageLog().Error(*LOCTEXT("StateWithNoResult", "@@ has no result node").ToString(), StateNode); + } + } + else + { + BakedState.StateName = NAME_None; + CompilerSubsystem->GetMessageLog().Error(*LOCTEXT("StateWithBadGraph", "@@ has no bound graph").ToString(), StateNode); + } + + // If this check fires, then something in the machine has changed causing the states array to not + // be a separate allocation, and a state machine inside of this one caused stuff to shift around + checkSlow(&BakedState == &(Oven.GetMachine().States[StateIndex])); + } + else if (UAnimStateConduitNode* ConduitNode = Cast(Node)) + { + ConduitNode->ValidateNodeDuringCompilation(CompilerSubsystem->GetMessageLog()); + + const int32 StateIndex = Oven.FindOrAddState(ConduitNode); + FBakedAnimationState& BakedState = Oven.GetMachine().States[StateIndex]; + + BakedState.StateName = ConduitNode->BoundGraph ? ConduitNode->BoundGraph->GetFName() : TEXT("OLD CONDUIT"); + BakedState.bIsAConduit = true; + + if (ConduitNode->BoundGraph != NULL) + { + if (UAnimGraphNode_TransitionResult* EntryRuleResultNode = CastChecked(ConduitNode->BoundGraph)->GetResultNode()) + { + BakedState.EntryRuleNodeIndex = CompilerSubsystem->ExpandGraphAndProcessNodes(ConduitNode->BoundGraph, EntryRuleResultNode); + } + } + + // If this check fires, then something in the machine has changed causing the states array to not + // be a separate allocation, and a state machine inside of this one caused stuff to shift around + checkSlow(&BakedState == &(Oven.GetMachine().States[StateIndex])); + } + } + + // Process transitions after all the states because getters within custom graphs may want to + // reference back to other states, which are only valid if they have already been baked + for (auto StateNodeIt = Oven.StateIndexTable.CreateIterator(); StateNodeIt; ++StateNodeIt) + { + UAnimStateNodeBase* StateNode = StateNodeIt.Key(); + const int32 StateIndex = StateNodeIt.Value(); + + FBakedAnimationState& BakedState = Oven.GetMachine().States[StateIndex]; + + // Add indices to all player and layer nodes + TArray GraphsToCheck; + GraphsToCheck.Add(StateNode->GetBoundGraph()); + StateNode->GetBoundGraph()->GetAllChildrenGraphs(GraphsToCheck); + + TArray LinkedAnimLayerNodes; + TArray AssetPlayerNodes; + for (UEdGraph* ChildGraph : GraphsToCheck) + { + ChildGraph->GetNodesOfClass(AssetPlayerNodes); + ChildGraph->GetNodesOfClass(LinkedAnimLayerNodes); + } + + for (UAnimGraphNode_AssetPlayerBase* Node : AssetPlayerNodes) + { + if (int32* IndexPtr = CompilerSubsystem->GetNewAnimBlueprintClass()->AnimBlueprintDebugData.NodeGuidToIndexMap.Find(Node->NodeGuid)) + { + BakedState.PlayerNodeIndices.Add(*IndexPtr); + } + } + + for (UAnimGraphNode_LinkedAnimLayer* Node : LinkedAnimLayerNodes) + { + if (int32* IndexPtr = CompilerSubsystem->GetNewAnimBlueprintClass()->AnimBlueprintDebugData.NodeGuidToIndexMap.Find(Node->NodeGuid)) + { + BakedState.LayerNodeIndices.Add(*IndexPtr); + } + } + // Handle all the transitions out of this node + TArray TransitionList; + StateNode->GetTransitionList(/*out*/ TransitionList, /*bWantSortedList=*/ true); + + for (auto TransitionIt = TransitionList.CreateIterator(); TransitionIt; ++TransitionIt) + { + UAnimStateTransitionNode* TransitionNode = *TransitionIt; + const int32 TransitionIndex = Oven.FindOrAddTransition(TransitionNode); + + // Validate the blend profile for this transition - incase the skeleton of the node has + // changed or the blend profile no longer exists. + TransitionNode->ValidateBlendProfile(); + + FBakedStateExitTransition& Rule = *new (BakedState.Transitions) FBakedStateExitTransition(); + Rule.bDesiredTransitionReturnValue = (TransitionNode->GetPreviousState() == StateNode); + Rule.TransitionIndex = TransitionIndex; + + if (UAnimGraphNode_TransitionResult* TransitionResultNode = CastChecked(TransitionNode->BoundGraph)->GetResultNode()) + { + if (int32* pIndex = AlreadyMergedTransitionList.Find(TransitionResultNode)) + { + Rule.CanTakeDelegateIndex = *pIndex; + } + else + { + Rule.CanTakeDelegateIndex = CompilerSubsystem->ExpandGraphAndProcessNodes(TransitionNode->BoundGraph, TransitionResultNode, TransitionNode); + AlreadyMergedTransitionList.Add(TransitionResultNode, Rule.CanTakeDelegateIndex); + } + } + else + { + Rule.CanTakeDelegateIndex = INDEX_NONE; + CompilerSubsystem->GetMessageLog().Error(*LOCTEXT("TransitionWithNoResult", "@@ has no result node").ToString(), TransitionNode); + } + + // Handle automatic time remaining rules + Rule.bAutomaticRemainingTimeRule = TransitionNode->bAutomaticRuleBasedOnSequencePlayerInState; + + // Handle custom transition graphs + Rule.CustomResultNodeIndex = INDEX_NONE; + if (UAnimationCustomTransitionGraph* CustomTransitionGraph = Cast(TransitionNode->CustomTransitionGraph)) + { + TArray ClonedNodes; + if (CustomTransitionGraph->GetResultNode()) + { + Rule.CustomResultNodeIndex = CompilerSubsystem->ExpandGraphAndProcessNodes(TransitionNode->CustomTransitionGraph, CustomTransitionGraph->GetResultNode(), NULL, &ClonedNodes); + } + + // Find all the pose evaluators used in this transition, save handles to them because we need to populate some pose data before executing + TArray TransitionPoseList; + for (auto ClonedNodesIt = ClonedNodes.CreateIterator(); ClonedNodesIt; ++ClonedNodesIt) + { + UEdGraphNode* Node = *ClonedNodesIt; + if (UAnimGraphNode_TransitionPoseEvaluator* TypedNode = Cast(Node)) + { + TransitionPoseList.Add(TypedNode); + } + } + + Rule.PoseEvaluatorLinks.Empty(TransitionPoseList.Num()); + + for (auto TransitionPoseListIt = TransitionPoseList.CreateIterator(); TransitionPoseListIt; ++TransitionPoseListIt) + { + UAnimGraphNode_TransitionPoseEvaluator* TransitionPoseNode = *TransitionPoseListIt; + Rule.PoseEvaluatorLinks.Add( CompilerSubsystem->GetAllocationIndexOfNode(TransitionPoseNode) ); + } + } + } + } + + Oven.Validate(); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_StateResult.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_StateResult.cpp index f1c6d68aff35..8f027a40cb04 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_StateResult.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_StateResult.cpp @@ -2,6 +2,7 @@ #include "AnimGraphNode_StateResult.h" #include "GraphEditorSettings.h" +#include "AnimBlueprintCompiler.h" #define LOCTEXT_NAMESPACE "A3Nodes" @@ -43,4 +44,11 @@ FString UAnimGraphNode_StateResult::GetDocumentationLink() const return TEXT("Shared/GraphNodes/AnimationStateMachine"); } +void UAnimGraphNode_StateResult::OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) +{ + UAnimGraphNode_StateResult* TrueNode = InCompilerContext.MessageLog.FindSourceObjectTypeChecked(this); + + Node.Name = TrueNode->GetGraph()->GetFName(); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_UseCachedPose.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_UseCachedPose.cpp index e1a590045d1b..d9514d026014 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_UseCachedPose.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_UseCachedPose.cpp @@ -8,6 +8,8 @@ #include "BlueprintActionFilter.h" #include "BlueprintNodeSpawner.h" #include "BlueprintActionDatabaseRegistrar.h" +#include "AnimBlueprintCompiler.h" +#include "AnimBlueprintCompilerSubsystem_CachedPose.h" ///////////////////////////////////////////////////// // UAnimGraphNode_UseCachedPose @@ -176,4 +178,39 @@ bool UAnimGraphNode_UseCachedPose::IsActionFilteredOut(class FBlueprintActionFil return bIsFilteredOut; } +void UAnimGraphNode_UseCachedPose::OnProcessDuringCompilation(FAnimBlueprintCompilerContext& InCompilerContext) +{ + UAnimBlueprintCompilerSubsystem_CachedPose* CompilerSubsystem = InCompilerContext.GetSubsystem(); + check(CompilerSubsystem); + + bool bSuccessful = false; + + // Link to the saved cached pose + if(SaveCachedPoseNode.IsValid()) + { + if (UAnimGraphNode_SaveCachedPose* AssociatedSaveNode = CompilerSubsystem->GetSaveCachedPoseNodes().FindRef(SaveCachedPoseNode->CacheName)) + { + FStructProperty* LinkProperty = FindFProperty(FAnimNode_UseCachedPose::StaticStruct(), TEXT("LinkToCachingNode")); + check(LinkProperty); + + FPoseLinkMappingRecord LinkRecord = FPoseLinkMappingRecord::MakeFromMember(this, AssociatedSaveNode, LinkProperty); + if (LinkRecord.IsValid()) + { + CompilerSubsystem->AddPoseLinkMappingRecord(LinkRecord); + } + bSuccessful = true; + + // Save CachePoseName for debug + FName CachePoseName = FName(*SaveCachedPoseNode->CacheName); + SaveCachedPoseNode->Node.CachePoseName = CachePoseName; + Node.CachePoseName = CachePoseName; + } + } + + if(!bSuccessful) + { + CompilerSubsystem->GetMessageLog().Error(*LOCTEXT("NoAssociatedSaveNode", "@@ does not have an associated Save Cached Pose node").ToString(), this); + } +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimStateTransitionNode.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimStateTransitionNode.cpp index 89a30a545355..7908c611744f 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimStateTransitionNode.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimStateTransitionNode.cpp @@ -598,31 +598,34 @@ void UAnimStateTransitionNode::ValidateNodeDuringCompilation(class FCompilerResu UAnimGraphNode_TransitionResult* ResultNode = TransGraph->GetResultNode(); check(ResultNode); - UEdGraphPin* BoolResultPin = ResultNode->Pins[0]; - if ((BoolResultPin->LinkedTo.Num() == 0) && (BoolResultPin->DefaultValue.ToBool() == false)) + if(ResultNode->Pins.Num() > 0) { - // check for native transition rule before warning - bool bHasNative = false; - UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(this); - if(Blueprint && Blueprint->ParentClass) + UEdGraphPin* BoolResultPin = ResultNode->Pins[0]; + if (BoolResultPin && (BoolResultPin->LinkedTo.Num() == 0) && (BoolResultPin->DefaultValue.ToBool() == false)) { - UAnimInstance* AnimInstance = CastChecked(Blueprint->ParentClass->GetDefaultObject()); - if(AnimInstance) + // check for native transition rule before warning + bool bHasNative = false; + UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(this); + if(Blueprint && Blueprint->ParentClass) { - UEdGraph* ParentGraph = GetGraph(); - UAnimStateNodeBase* PrevState = GetPreviousState(); - UAnimStateNodeBase* NextState = GetNextState(); - if(PrevState != nullptr && NextState != nullptr && ParentGraph != nullptr) + UAnimInstance* AnimInstance = CastChecked(Blueprint->ParentClass->GetDefaultObject()); + if(AnimInstance) { - FName FunctionName; - bHasNative = AnimInstance->HasNativeTransitionBinding(ParentGraph->GetFName(), FName(*PrevState->GetStateName()), FName(*NextState->GetStateName()), FunctionName); + UEdGraph* ParentGraph = GetGraph(); + UAnimStateNodeBase* PrevState = GetPreviousState(); + UAnimStateNodeBase* NextState = GetNextState(); + if(PrevState != nullptr && NextState != nullptr && ParentGraph != nullptr) + { + FName FunctionName; + bHasNative = AnimInstance->HasNativeTransitionBinding(ParentGraph->GetFName(), FName(*PrevState->GetStateName()), FName(*NextState->GetStateName()), FunctionName); + } } } - } - if (!bHasNative && !bAutomaticRuleBasedOnSequencePlayerInState) - { - MessageLog.Warning(TEXT("@@ will never be taken, please connect something to @@"), this, BoolResultPin); + if (!bHasNative && !bAutomaticRuleBasedOnSequencePlayerInState) + { + MessageLog.Warning(TEXT("@@ will never be taken, please connect something to @@"), this, BoolResultPin); + } } } } diff --git a/Engine/Source/Editor/AnimGraph/Public/AnimBlueprintCompilerSubsystem.h b/Engine/Source/Editor/AnimGraph/Public/AnimBlueprintCompilerSubsystem.h new file mode 100644 index 000000000000..ab41d7f573a8 --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Public/AnimBlueprintCompilerSubsystem.h @@ -0,0 +1,167 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/Subsystem.h" +#include "KismetCompiler.h" +#include "Containers/ArrayView.h" + +#include "AnimBlueprintCompilerSubsystem.generated.h" + +struct FPoseLinkMappingRecord; +class FAnimBlueprintCompilerContext; +class UAnimBlueprint; +class UAnimGraphNode_Base; +class UAnimBlueprintGeneratedClass; +class UAnimBlueprintClassSubsystem; + +UCLASS() +class ANIMGRAPH_API UAnimBlueprintCompilerSubsystem : public USubsystem +{ + GENERATED_BODY() + +public: + + /** Begin ordered calls - these functions are called int he order presented here */ + + /** Start compiling the class */ + virtual void StartCompilingClass(UClass* InClass) {} + + /** Give the subsystem a chance to perform processing before all animation nodes are processed */ + virtual void PreProcessAnimationNodes(TArrayView InAnimNodes) {} + + /** Give the subsystem a chance to perform processing once all animation nodes have been processed */ + virtual void PostProcessAnimationNodes(TArrayView InAnimNodes) {} + + /** Give the subsystem a chance to perform processing post-expansion step */ + virtual void PostExpansionStep(UEdGraph* InGraph) {} + + /** Finish compiling the class */ + virtual void FinishCompilingClass(UClass* InClass) {} + + /** Copy any data into the CDO */ + virtual void CopyTermDefaultsToDefaultObject(UObject* InDefaultObject) {} + + /** End ordered calls */ + + /** Gives a subsystem the option to skip the processing of a function graph (in general because it is expected to process the function graph itself somehow) */ + virtual bool ShouldProcessFunctionGraph(UEdGraph* InGraph) const { return true; } + + // Get all the class subsystems that we want to add to the class to support this subsystem + // Note that this is called regardless of anim graph node connectivity so the subsystem will + // always be added even for isolated nodes + virtual void GetRequiredClassSubsystems(TArray>& OutSubsystemClasses) const {} + + // Get the currently-compiled blueprint + UBlueprint* GetBlueprint() const; + + // Get the currently-compiled anim blueprint + UAnimBlueprint* GetAnimBlueprint() const; + + // Get the currently-compiled anim blueprint class + UAnimBlueprintGeneratedClass* GetNewAnimBlueprintClass() const; + + // Get the message log for the current compilation + FCompilerResultsLog& GetMessageLog() const; + + // Get the consolidated uber graph during compilation + UEdGraph* GetConsolidatedEventGraph() const; + + // Performs standard validation on the graph (outputs point to inputs, no more than one connection to each input, types match on both ends, etc...) + bool ValidateGraphIsWellFormed(UEdGraph* Graph) const; + + // Returns the allocation index of the specified node, processing it if it was pending + int32 GetAllocationIndexOfNode(UAnimGraphNode_Base* VisualAnimNode); + + // Adds a pose link mapping record + void AddPoseLinkMappingRecord(const FPoseLinkMappingRecord& InRecord); + + // Gets all anim graph nodes that are piped into the provided node (traverses input pins) + void GetLinkedAnimNodes(UAnimGraphNode_Base* InGraphNode, TArray &LinkedAnimNodes); + + // Index of the nodes (must match up with the runtime discovery process of nodes, which runs thru the property chain) + const TMap& GetAllocatedAnimNodeIndices() const; + + // Map of true source objects (user edited ones) to the cloned ones that are actually compiled + const TMap& GetSourceNodeToProcessedNodeMap() const; + + // Map of anim node indices to node properties + const TMap& GetAllocatedPropertiesByIndex() const; + + // Map of anim node indices to node properties + const TMap& GetAllocatedPropertiesByNode() const; + + // Spawns an intermediate node associated with the source node (for error purposes) + template + NodeType* SpawnIntermediateNode(UEdGraphNode* SourceNode, UEdGraph* ParentGraph = nullptr) + { + return GetKismetCompiler()->SpawnIntermediateNode(SourceNode, ParentGraph); + } + + // Spawns an intermediate event node associated with the source node (for error purposes) + template + NodeType* SpawnIntermediateEventNode(UEdGraphNode* SourceNode, UEdGraphPin* SourcePin = nullptr, UEdGraph* ParentGraph = nullptr) + { + return GetKismetCompiler()->SpawnIntermediateEventNode(SourceNode, SourcePin, ParentGraph); + } + + // Expands split pins for a graph + void ExpandSplitPins(UEdGraph* InGraph); + + // Process the passed-in list of nodes + void ProcessAnimationNodes(TArray& AnimNodeList); + + // Prunes any nodes that aren't reachable via a pose link + void PruneIsolatedAnimationNodes(const TArray& RootSet, TArray& GraphNodes); + + // Perform an expansion step for the specified graph + void ExpansionStep(UEdGraph* Graph, bool bAllowUbergraphExpansions); + + // Get another subsystem of the specified type + template + TSubsystemClass* GetSubsystem() const + { + return Cast(GetSubsystemInternal(GetKismetCompiler(), TSubsystemClass::StaticClass())); + } + + // Get another subsystem of the specified type, assuming that the supplied context is an anim BP context + template + static TSubsystemClass* GetSubsystem(const FKismetCompilerContext& InCompilerContext) + { + return Cast(GetSubsystemInternal(&InCompilerContext, TSubsystemClass::StaticClass())); + } + + // Find the first subsystem implementing the specified interface + template + InterfaceClass* FindSubsystemWithInterface() const + { + return Cast(FindSubsystemWithInterfaceInternal(GetKismetCompiler(), InterfaceClass::UClassType::StaticClass())); + } + + // Find the first subsystem implementing the specified interface, assuming that the supplied context is an anim BP context + template + static InterfaceClass* FindSubsystemWithInterface(const FKismetCompilerContext& InCompilerContext) + { + return Cast(FindSubsystemWithInterfaceInternal(&InCompilerContext, InterfaceClass::UClassType::StaticClass())); + } + + // Get the compiler options we are currently using + const FKismetCompilerOptions& GetCompileOptions() const; + +private: + // Subsystem helper functions + static UAnimBlueprintCompilerSubsystem* GetSubsystemInternal(const FKismetCompilerContext* CompilerContext, TSubclassOf InClass); + static UAnimBlueprintCompilerSubsystem* FindSubsystemWithInterfaceInternal(const FKismetCompilerContext* CompilerContext, TSubclassOf InInterfaceClass); + +private: + /** USubsystem interface */ + virtual void Initialize(FSubsystemCollectionBase& InCollection) override; + + // Get the compiler as a base class to avoid circular include issues with templated functions/classes + FKismetCompilerContext* GetKismetCompiler() const; + +private: + /** The compiler context that hosts this subsystem */ + FAnimBlueprintCompilerContext* CompilerContext; +}; diff --git a/Engine/Source/Editor/AnimGraph/Public/IClassVariableCreator.h b/Engine/Source/Editor/AnimGraph/Public/IClassVariableCreator.h new file mode 100644 index 000000000000..d96e6824840c --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Public/IClassVariableCreator.h @@ -0,0 +1,29 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "IClassVariableCreator.generated.h" + +class FKismetCompilerContext; + +UINTERFACE(MinimalAPI) +class UClassVariableCreator : public UInterface +{ + GENERATED_BODY() +}; + +class IClassVariableCreator +{ + GENERATED_BODY() + +public: + /** + * Implement this in a graph node and the anim BP compiler will call this expecting to generate + * class variables. + * @param InCompilerContext The compiler context for the current BP compilation + */ + virtual void CreateClassVariablesFromBlueprint(FKismetCompilerContext& InCompilerContext) = 0; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/AnimGraph/Public/IPropertyAccessCompilerSubsystem.h b/Engine/Source/Editor/AnimGraph/Public/IPropertyAccessCompilerSubsystem.h new file mode 100644 index 000000000000..962d11d95642 --- /dev/null +++ b/Engine/Source/Editor/AnimGraph/Public/IPropertyAccessCompilerSubsystem.h @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "Containers/ArrayView.h" +#include "Delegates/Delegate.h" +#include "IPropertyAccessCompilerSubsystem.generated.h" + +enum class EPropertyAccessBatchType : uint8; + +UINTERFACE(MinimalAPI) +class UPropertyAccessCompilerSubsystem : public UInterface +{ + GENERATED_BODY() +}; + +class IPropertyAccessCompilerSubsystem +{ + GENERATED_BODY() + +public: + // Add a copy to the property access library we are compiling + // @return an integer handle to the pending copy. This can be resolved to a true copy index by calling MapCopyIndex + virtual int32 AddCopy(TArrayView InSourcePath, TArrayView InDestPath, EPropertyAccessBatchType InBatchType, UObject* InObject = nullptr) = 0; + + // Delegate called when the library is compiled (whether successfully or not) + virtual FSimpleMulticastDelegate& OnPreLibraryCompiled() = 0; + + // Delegate called when the library is compiled (whether successfully or not) + virtual FSimpleMulticastDelegate& OnPostLibraryCompiled() = 0; + + // Maps the initial integer copy handle to a true handle, post compilation + virtual int32 MapCopyIndex(int32 InIndex) const = 0; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimGraphConnectionDrawingPolicy.cpp b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimGraphConnectionDrawingPolicy.cpp index 09c953ea7b5c..9087888f0aa3 100644 --- a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimGraphConnectionDrawingPolicy.cpp +++ b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimGraphConnectionDrawingPolicy.cpp @@ -41,11 +41,11 @@ void FAnimGraphConnectionDrawingPolicy::BuildExecutionRoadmap() { const FAnimBlueprintDebugData::FNodeVisit& VisitRecord = *VisitIt; - if ((VisitRecord.SourceID >= 0) && (VisitRecord.SourceID < AnimBlueprintClass->AnimNodeProperties.Num()) && (VisitRecord.TargetID >= 0) && (VisitRecord.TargetID < AnimBlueprintClass->AnimNodeProperties.Num())) + if ((VisitRecord.SourceID >= 0) && (VisitRecord.SourceID < AnimBlueprintClass->GetAnimNodeProperties().Num()) && (VisitRecord.TargetID >= 0) && (VisitRecord.TargetID < AnimBlueprintClass->AnimNodeProperties.Num())) { - if (UAnimGraphNode_Base* SourceNode = Cast(PropertySourceMap.FindRef(AnimBlueprintClass->AnimNodeProperties[VisitRecord.SourceID]))) + if (UAnimGraphNode_Base* SourceNode = Cast(PropertySourceMap.FindRef(AnimBlueprintClass->GetAnimNodeProperties()[VisitRecord.SourceID]))) { - if (UAnimGraphNode_Base* TargetNode = Cast(PropertySourceMap.FindRef(AnimBlueprintClass->AnimNodeProperties[VisitRecord.TargetID]))) + if (UAnimGraphNode_Base* TargetNode = Cast(PropertySourceMap.FindRef(AnimBlueprintClass->GetAnimNodeProperties()[VisitRecord.TargetID]))) { UEdGraphPin* PoseNet = NULL; diff --git a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SAnimationGraphNode.cpp b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SAnimationGraphNode.cpp index 28c950e1b399..fd7f56cf45a6 100644 --- a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SAnimationGraphNode.cpp +++ b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SAnimationGraphNode.cpp @@ -12,6 +12,11 @@ #include "AnimationEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Animation/AnimInstance.h" +#include "GraphEditorSettings.h" +#include "SLevelOfDetailBranchNode.h" +#include "Widgets/Layout/SSpacer.h" + +#define LOCTEXT_NAMESPACE "AnimationGraphNode" class SPoseViewColourPickerPopup : public SCompoundWidget { @@ -63,7 +68,7 @@ public: .Padding(5.f, 2.f) [ SNew(SButton) - .Text(NSLOCTEXT("AnimationGraphNode", "RemovePoseWatch", "Remove Pose Watch")) + .Text(LOCTEXT("RemovePoseWatch", "Remove Pose Watch")) .OnClicked(this, &SPoseViewColourPickerPopup::RemovePoseWatch) ]; @@ -115,12 +120,12 @@ void SAnimationGraphNode::Construct(const FArguments& InArgs, UAnimGraphNode_Bas IndicatorWidget = SNew(SImage) .Image(ImageBrush) - .ToolTip(IDocumentation::Get()->CreateToolTip(NSLOCTEXT("AnimationGraphNode", "AnimGraphNodeIndicatorTooltip", "Fast path enabled: This node is not using any Blueprint calls to update its data."), NULL, TEXT("Shared/GraphNodes/Animation"), TEXT("GraphNode_FastPathInfo"))) + .ToolTip(IDocumentation::Get()->CreateToolTip(LOCTEXT("AnimGraphNodeIndicatorTooltip", "Fast path enabled: This node is not using any Blueprint calls to update its data."), NULL, TEXT("Shared/GraphNodes/Animation"), TEXT("GraphNode_FastPathInfo"))) .Visibility(EVisibility::Visible); PoseViewWidget = SNew(SButton) - .ToolTipText(NSLOCTEXT("AnimationGraphNode", "SpawnColourPicker", "Pose watch active. Click to spawn the pose watch colour picker")) + .ToolTipText(LOCTEXT("SpawnColourPicker", "Pose watch active. Click to spawn the pose watch colour picker")) .OnClicked(this, &SAnimationGraphNode::SpawnColourPicker) .ButtonColorAndOpacity(this, &SAnimationGraphNode::GetPoseViewColour) [ @@ -128,6 +133,61 @@ void SAnimationGraphNode::Construct(const FArguments& InArgs, UAnimGraphNode_Bas ]; } +void SAnimationGraphNode::CreatePinWidgets() +{ + SGraphNodeK2Base::CreatePinWidgets(); + + if (UAnimGraphNode_Base* AnimNode = CastChecked(GraphNode, ECastCheckedType::NullAllowed)) + { + static FName FunctionIcon(TEXT("GraphEditor.Function_16x")); + const UEdGraphSchema_K2* Schema = GetDefault(); + + // Add binding widgets + for(FOptionalPinFromProperty& OptionalPin : AnimNode->ShowPinForProperties) + { + if(FAnimGraphNodePropertyBinding* BindingPtr = AnimNode->PropertyBindings.Find(OptionalPin.PropertyName)) + { + LeftNodeBox->AddSlot() + .AutoHeight() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .Padding(Settings->GetInputPinPadding()) + [ + SNew(SLevelOfDetailBranchNode) + .UseLowDetailSlot(this, &SAnimationGraphNode::UseLowDetailNodeTitles) + .LowDetail() + [ + SNew(SSpacer) + .Size(FVector2D(17.0f, 17.f)) + ] + .HighDetail() + [ + SNew(SHorizontalBox) + .ToolTipText(FText::Format(LOCTEXT("BindingTooltipFormat", "Property '{0}' is bound via property access path to '{1}'"), FText::FromString(OptionalPin.PropertyFriendlyName), BindingPtr->PathAsText)) + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(1.0f) + [ + SNew(SImage) + .Image(BindingPtr->Type == EAnimGraphNodePropertyBindingType::Property ? FBlueprintEditorUtils::GetIconFromPin(BindingPtr->PinType, true) : FEditorStyle::GetBrush(FunctionIcon)) + .ColorAndOpacity(Schema->GetPinTypeColor(BindingPtr->PinType)) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(6.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("BindingPinFormat", "{0}: {1}"), FText::FromString(OptionalPin.PropertyFriendlyName), BindingPtr->PathAsText)) + ] + ] + ]; + } + } + } +} + void SAnimationGraphNode::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { SGraphNodeK2Base::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); @@ -227,13 +287,13 @@ void SAnimationGraphNode::GetNodeInfoPopups(FNodeInfoContext* Context, TArrayAnimNodeProperties.Num()) + if (Class->GetAnimNodeProperties().Num()) { if(int32* NodeIndexPtr = Class->GetAnimBlueprintDebugData().NodePropertyToIndexMap.Find(TWeakObjectPtr(Cast(GraphNode)))) { int32 AnimNodeIndex = *NodeIndexPtr; // reverse node index temporarily because of a bug in NodeGuidToIndexMap - AnimNodeIndex = Class->AnimNodeProperties.Num() - AnimNodeIndex - 1; + AnimNodeIndex = Class->GetAnimNodeProperties().Num() - AnimNodeIndex - 1; if (FAnimBlueprintDebugData::FNodeValue* DebugInfo = Class->GetAnimBlueprintDebugData().NodeValuesThisFrame.FindByPredicate([AnimNodeIndex](const FAnimBlueprintDebugData::FNodeValue& InValue){ return InValue.NodeID == AnimNodeIndex; })) { @@ -243,4 +303,6 @@ void SAnimationGraphNode::GetNodeInfoPopups(FNodeInfoContext* Context, TArray GetOverlayWidgets(bool bSelected, const FVector2D& WidgetSize) const override; virtual TSharedRef CreateTitleWidget(TSharedPtr InNodeTitle) override; virtual void GetNodeInfoPopups(FNodeInfoContext* Context, TArray& Popups) const override; + virtual void CreatePinWidgets() override; // End of SGraphNode interface private: diff --git a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SGraphNodeBlendSpacePlayer.cpp b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SGraphNodeBlendSpacePlayer.cpp index b940b5d0821c..4b98a1ff0fa4 100644 --- a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SGraphNodeBlendSpacePlayer.cpp +++ b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SGraphNodeBlendSpacePlayer.cpp @@ -94,7 +94,7 @@ bool SGraphNodeBlendSpacePlayer::GetBlendSpaceInfo(TWeakObjectPtrAnimNodeProperties.Num() - AnimNodeIndex - 1; + AnimNodeIndex = Class->GetAnimNodeProperties().Num() - AnimNodeIndex - 1; if (FAnimBlueprintDebugData::FBlendSpacePlayerRecord* DebugInfo = Class->GetAnimBlueprintDebugData().BlendSpacePlayerRecordsThisFrame.FindByPredicate([AnimNodeIndex](const FAnimBlueprintDebugData::FBlendSpacePlayerRecord& InRecord){ return InRecord.NodeID == AnimNodeIndex; })) { diff --git a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SGraphNodeSequencePlayer.cpp b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SGraphNodeSequencePlayer.cpp index f59ac5cccb9a..6d6d69827839 100644 --- a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SGraphNodeSequencePlayer.cpp +++ b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SGraphNodeSequencePlayer.cpp @@ -130,7 +130,7 @@ bool SGraphNodeSequencePlayer::GetSequencePositionInfo(float& Out_Position, floa { int32 AnimNodeIndex = *NodeIndexPtr; // reverse node index temporarily because of a bug in NodeGuidToIndexMap - AnimNodeIndex = Class->AnimNodeProperties.Num() - AnimNodeIndex - 1; + AnimNodeIndex = Class->GetAnimNodeProperties().Num() - AnimNodeIndex - 1; if (FAnimBlueprintDebugData::FSequencePlayerRecord* DebugInfo = Class->GetAnimBlueprintDebugData().SequencePlayerRecordsThisFrame.FindByPredicate([AnimNodeIndex](const FAnimBlueprintDebugData::FSequencePlayerRecord& InRecord){ return InRecord.NodeID == AnimNodeIndex; })) { diff --git a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationStateNodes/SGraphNodeAnimState.cpp b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationStateNodes/SGraphNodeAnimState.cpp index 36c37a80c1b6..fb95ae7bc6fc 100644 --- a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationStateNodes/SGraphNodeAnimState.cpp +++ b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationStateNodes/SGraphNodeAnimState.cpp @@ -96,7 +96,7 @@ void SGraphNodeAnimState::GetStateInfoPopup(UEdGraphNode* GraphNode, TArrayAnimNodeProperties.Num()) + if (Class->GetAnimNodeProperties().Num()) { if (FStateMachineDebugData* DebugInfo = Class->GetAnimBlueprintDebugData().StateMachineDebugData.Find(GraphNode->GetGraph())) { diff --git a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationStateNodes/SGraphNodeAnimTransition.cpp b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationStateNodes/SGraphNodeAnimTransition.cpp index c40662e1abd8..3208a7a0a072 100644 --- a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationStateNodes/SGraphNodeAnimTransition.cpp +++ b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationStateNodes/SGraphNodeAnimTransition.cpp @@ -282,7 +282,7 @@ FLinearColor SGraphNodeAnimTransition::StaticGetTransitionColor(UAnimStateTransi { const int32 TransIndex = *pTransIndex; - if (Class->AnimNodeProperties.Num()) + if (Class->GetAnimNodeProperties().Num()) { UAnimationStateMachineGraph* TypedGraph = CastChecked(StateMachineGraph); diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2.h b/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2.h index 126186411fee..9aac198017f8 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2.h @@ -833,8 +833,21 @@ public: /** returns friendly signature name if possible or Removes any mangling to get the unmangled signature name of the function */ static FText GetFriendlySignatureName(const UFunction* Function); + /** Returns true if this enum is safe to be used as a variable in blueprints */ static bool IsAllowableBlueprintVariableType(const UEnum* InEnum); - static bool IsAllowableBlueprintVariableType(const UClass* InClass); + + /** + * Returns true if this class is safe to be used as a variable in blueprints + * + * @param bImpliedBlueprintType If true, the class is implied to be a blueprint type and will be allowed unless it is specifically forbidden + */ + static bool IsAllowableBlueprintVariableType(const UClass* InClass, bool bAssumeBlueprintType = false); + + /** + * Returns true if this struct is safe to to be used as a variable in blueprints + * + * @param bForInternalUse If true, the struct is for internal usage so BlueprintInternalUseOnly is allowed + */ static bool IsAllowableBlueprintVariableType(const UScriptStruct *InStruct, bool bForInternalUse = false); static bool IsPropertyExposedOnSpawn(const FProperty* Property); diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CallParentFunction.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CallParentFunction.h index d7c82f43172a..7fd2176ee0e1 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CallParentFunction.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CallParentFunction.h @@ -21,5 +21,11 @@ class UK2Node_CallParentFunction : public UK2Node_CallFunction //~ End EdGraphNode Interface virtual void SetFromFunction(const UFunction* Function) override; + +protected: + + //~ Begin K2Node_CallFunction Interface + virtual void FixupSelfMemberContext() override; + //~ End K2Node_CallFunction Interface }; diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EditablePinBase.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EditablePinBase.h index 424317046cce..633e77530574 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EditablePinBase.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EditablePinBase.h @@ -17,6 +17,9 @@ struct FUserPinInfo : DesiredPinDirection(EGPD_MAX) {} + /** Constructs a FUserPinInfo to match an existing Pin */ + explicit FUserPinInfo(const UEdGraphPin& InPin); + /** The name of the pin, as defined by the user */ UPROPERTY() FName PinName; diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionEntry.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionEntry.h index f379ea8c720f..deca1e408039 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionEntry.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionEntry.h @@ -49,6 +49,8 @@ class UK2Node_FunctionEntry : public UK2Node_FunctionTerminator virtual FEdGraphNodeDeprecationResponse GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const override; virtual FText GetTooltipText() const override; virtual void FindDiffs(UEdGraphNode* OtherNode, struct FDiffResults& Results) override; + virtual bool IsCompatibleWithGraph(const UEdGraph* InGraph) const override; + virtual void PostPasteNode() override; //~ End UEdGraphNode Interface //~ Begin UK2Node Interface diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionResult.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionResult.h index 2c0d8c5232fc..76e3fff98455 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionResult.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionResult.h @@ -27,11 +27,9 @@ class UK2Node_FunctionResult : public UK2Node_FunctionTerminator virtual bool DrawNodeAsExit() const override { return true; } virtual bool ShouldShowNodeProperties() const override { return true; } virtual class FNodeHandlingFunctor* CreateNodeHandler(class FKismetCompilerContext& CompilerContext) const override; - virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; virtual bool IsCompatibleWithGraph(UEdGraph const* Graph) const; virtual void PostPlacedNewNode(); - virtual bool CanDuplicateNode() const { return true; } virtual void PostPasteNode() override; virtual bool CanUserDeleteNode() const override; virtual void FixupPinStringDataReferences(FArchive* SavingArchive) override; diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionTerminator.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionTerminator.h index 24f810dd9069..59b72535db0a 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionTerminator.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionTerminator.h @@ -24,7 +24,6 @@ class UK2Node_FunctionTerminator : public UK2Node_EditablePinBase //~ End UObject Interface //~ Begin UEdGraphNode Interface - virtual bool CanDuplicateNode() const override { return false; } virtual FLinearColor GetNodeTitleColor() const override; virtual FName CreateUniquePinName(FName SourcePinName) const override; virtual void ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const override; diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_GetSubsystem.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_GetSubsystem.h index d96bc4e5c9fd..39857d26c63f 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_GetSubsystem.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_GetSubsystem.h @@ -9,7 +9,7 @@ class USubsystem; class FKismetCompilerContext; UCLASS() -class UK2Node_GetSubsystem : public UK2Node +class BLUEPRINTGRAPH_API UK2Node_GetSubsystem : public UK2Node { GENERATED_BODY() public: diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_MakeStruct.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_MakeStruct.h index 34dd4cd163dc..8fe30015bd86 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_MakeStruct.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_MakeStruct.h @@ -34,7 +34,7 @@ class UK2Node_MakeStruct : public UK2Node_StructMemberSet BLUEPRINTGRAPH_API static bool CanBeMade(const UScriptStruct* Struct, bool bForInternalUse = false); /** Can this struct be used as a split pin */ - BLUEPRINTGRAPH_API static bool CanBeSplit(const UScriptStruct* Struct); + BLUEPRINTGRAPH_API static bool CanBeSplit(const UScriptStruct* Struct, UBlueprint* InBP); // UObject interface virtual void Serialize(FArchive& Ar) override; @@ -66,8 +66,9 @@ protected: struct FMakeStructPinManager : public FStructOperationOptionalPinManager { const uint8* const SampleStructMemory; + UBlueprint* OwningBP; public: - FMakeStructPinManager(const uint8* InSampleStructMemory); + FMakeStructPinManager(const uint8* InSampleStructMemory, UBlueprint* InOwningBP); bool HasAdvancedPins() const { return bHasAdvancedPins; } protected: diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_SetFieldsInStruct.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_SetFieldsInStruct.h index 5c39b94966ec..e06bbbd6b249 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_SetFieldsInStruct.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_SetFieldsInStruct.h @@ -48,7 +48,7 @@ protected: struct FSetFieldsInStructPinManager : public FMakeStructPinManager { public: - FSetFieldsInStructPinManager(const uint8* InSampleStructMemory) : FMakeStructPinManager(InSampleStructMemory) + FSetFieldsInStructPinManager(const uint8* InSampleStructMemory, UBlueprint* BP) : FMakeStructPinManager(InSampleStructMemory, BP) {} virtual void GetRecordDefaults(FProperty* TestProperty, FOptionalPinFromProperty& Record) const override; diff --git a/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionDatabase.cpp b/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionDatabase.cpp index ebbadd17bdd8..deb50c982c5f 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionDatabase.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionDatabase.cpp @@ -890,9 +890,9 @@ static void BlueprintActionDatabaseImpl::AddAnimBlueprintGraphActions(UAnimBluep { if (UAnimBlueprintGeneratedClass* GeneratedClass = AnimBlueprint->GetAnimBlueprintGeneratedClass()) { - for (int32 NotifyIdx = 0; NotifyIdx < GeneratedClass->AnimNotifies.Num(); NotifyIdx++) + for (int32 NotifyIdx = 0; NotifyIdx < GeneratedClass->GetAnimNotifies().Num(); NotifyIdx++) { - FName NotifyName = GeneratedClass->AnimNotifies[NotifyIdx].NotifyName; + FName NotifyName = GeneratedClass->GetAnimNotifies()[NotifyIdx].NotifyName; if (NotifyName != NAME_None) { FString Label = NotifyName.ToString(); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionFilter.cpp b/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionFilter.cpp index 25c70ff5265a..cd0c4a2c1021 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionFilter.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionFilter.cpp @@ -1744,7 +1744,8 @@ static bool BlueprintActionFilterImpl::IsHiddenInNonEditorBlueprint(FBlueprintAc { // Leaving this check here (even though its done in CanEditorOnlyFunctionBeCalled) as an early exit const bool bIsEditorOnlyFunction = IsEditorOnlyObject(Function) || Function->HasAnyFunctionFlags(FUNC_EditorOnly); - + const bool bIsUncookedOnlyFunction = Function->GetOutermost()->HasAnyPackageFlags(PKG_UncookedOnly); + if (bIsEditorOnlyFunction) { for (const UBlueprint* Blueprint : Filter.Context.Blueprints) @@ -1752,6 +1753,17 @@ static bool BlueprintActionFilterImpl::IsHiddenInNonEditorBlueprint(FBlueprintAc bVisible &= UK2Node_CallFunction::CanEditorOnlyFunctionBeCalled(Function, Blueprint->ParentClass); } } + + if (bIsUncookedOnlyFunction) + { + for (const UBlueprint* Blueprint : Filter.Context.Blueprints) + { + const UClass* BlueprintClass = Blueprint->ParentClass; + const bool bIsEditorBlueprintClass = (BlueprintClass != nullptr) && IsEditorOnlyObject(BlueprintClass); + const bool bIsUncookedBlueprintClass = (BlueprintClass != nullptr) && BlueprintClass->GetOutermost()->HasAnyPackageFlags(PKG_UncookedOnly); + bVisible &= (bIsEditorBlueprintClass || bIsUncookedBlueprintClass); + } + } } return !bVisible; diff --git a/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp b/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp index 0f74239f9163..ae5d0e4ecc4c 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp @@ -1191,7 +1191,7 @@ bool UEdGraphSchema_K2::IsAllowableBlueprintVariableType(const UEnum* InEnum) return InEnum && (InEnum->GetBoolMetaData(FBlueprintMetadata::MD_AllowableBlueprintVariableType) || InEnum->IsA()); } -bool UEdGraphSchema_K2::IsAllowableBlueprintVariableType(const UClass* InClass) +bool UEdGraphSchema_K2::IsAllowableBlueprintVariableType(const UClass* InClass, bool bAssumeBlueprintType) { if (InClass) { @@ -1237,7 +1237,7 @@ bool UEdGraphSchema_K2::IsAllowableBlueprintVariableType(const UClass* InClass) } } - return false; + return bAssumeBlueprintType; } bool UEdGraphSchema_K2::IsAllowableBlueprintVariableType(const UScriptStruct* InStruct, const bool bForInternalUse) @@ -1332,7 +1332,8 @@ bool UEdGraphSchema_K2::PinHasSplittableStructType(const UEdGraphPin* InGraphPin { if (InGraphPin->Direction == EGPD_Input) { - bCanSplit = UK2Node_MakeStruct::CanBeSplit(StructType); + UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(InGraphPin->GetOwningNode()); + bCanSplit = UK2Node_MakeStruct::CanBeSplit(StructType, Blueprint); if (!bCanSplit) { const FString& MetaData = StructType->GetMetaData(FBlueprintMetadata::MD_NativeMakeFunction); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_AddComponentByClass.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_AddComponentByClass.cpp index b2b378f89e0c..9d87bb041dd6 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_AddComponentByClass.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_AddComponentByClass.cpp @@ -209,9 +209,9 @@ void UK2Node_AddComponentByClass::ExpandNode(class FKismetCompilerContext& Compi ////////////////////////////////////////////////////////////////////////// // create 'set var' nodes - UEdGraphPin* LastAssignmentThen = FKismetCompilerUtilities::GenerateAssignmentNodes(CompilerContext, SourceGraph, CallAddComponentByClassNode, this, CallAddComponentByClassResult, ClassToSpawn); + UEdGraphPin* LastThen = FKismetCompilerUtilities::GenerateAssignmentNodes(CompilerContext, SourceGraph, CallAddComponentByClassNode, this, CallAddComponentByClassResult, ClassToSpawn); - if (LastAssignmentThen != CallAddComponentByClassNode->GetThenPin()) + if (LastThen != CallAddComponentByClassNode->GetThenPin()) { UEdGraphPin* CallAddComponentByClassDeferredFinishPin = CallAddComponentByClassNode->FindPinChecked(FK2Node_AddComponentByClassHelper::DeferredFinishPinName); CallAddComponentByClassDeferredFinishPin->DefaultValue = TEXT("true"); @@ -220,11 +220,8 @@ void UK2Node_AddComponentByClass::ExpandNode(class FKismetCompilerContext& Compi CallRegisterComponentNode->SetFromFunction(AActor::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(AActor, FinishAddComponent))); CallRegisterComponentNode->AllocateDefaultPins(); - // Get last 'then' of assignments - UEdGraphPin* LastThen = CallRegisterComponentNode->GetThenPin(); - // Links execution from last assignment to 'RegisterComponent ' - LastAssignmentThen->MakeLinkTo(CallRegisterComponentNode->GetExecPin()); + LastThen->MakeLinkTo(CallRegisterComponentNode->GetExecPin()); // Link the pins to RegisterComponent node UEdGraphPin* CallRegisterComponentByClassOwnerPin = K2Schema->FindSelfPin(*CallRegisterComponentNode, EGPD_Input); @@ -242,10 +239,12 @@ void UK2Node_AddComponentByClass::ExpandNode(class FKismetCompilerContext& Compi CallRegisterComponentByClassComponentPin->MakeLinkTo(CallAddComponentByClassResult); - // Move 'then' connection from AddComponent node to the last 'then' - CompilerContext.MovePinLinksToIntermediate(*SpawnNodeThen, *LastThen); + LastThen = CallRegisterComponentNode->GetThenPin(); } + // Move 'then' connection from AddComponent node to the last 'then' + CompilerContext.MovePinLinksToIntermediate(*SpawnNodeThen, *LastThen); + // Break any links to the expanded node BreakAllNodeLinks(); } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallFunction.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallFunction.cpp index 2d9b5e99cc71..75423246b7f1 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallFunction.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallFunction.cpp @@ -2066,12 +2066,28 @@ void UK2Node_CallFunction::ValidateNodeDuringCompilation(class FCompilerResultsL MessageLog.Warning(*FText::Format(LOCTEXT("EnumToExecExpansionFailedFmt", "Unable to find enum parameter with name '{0}' to expand for @@"), FText::FromString(EnumParamName)).ToString(), this); } - if (Function) - { - // Ensure that editor module BP exposed UFunctions can only be called in blueprints for which the base class is also part of an editor module - // Also check for functions wrapped in WITH_EDITOR - bool bIsEditorOnlyBlueprintBaseClass = CanEditorOnlyFunctionBeCalled(Function, Blueprint->ParentClass); + const UClass* BlueprintClass = Blueprint ? Blueprint->ParentClass : nullptr; + const bool bIsEditorOnlyBlueprintBaseClass = !BlueprintClass || IsEditorOnlyObject(BlueprintClass); + // This error is disabled while we figure out how we can identify uncooked only + // blueprints that want to make use of uncooked only APIs: + #if 0 + const bool bIsUncookedOnlyFunction = Function && Function->GetOutermost()->HasAllPackagesFlags(PKG_UncookedOnly); + if ( bIsUncookedOnlyFunction && + // Only allow calls to uncooked only functions from editor only/uncooked only + // contexts: + !( GetOutermost()->HasAnyPackageFlags(PKG_UncookedOnly|PKG_EditorOnly) || + bIsEditorOnlyBlueprintBaseClass )) + { + MessageLog.Error(*LOCTEXT("UncookedOnlyError", "Attempting to call uncooked only function @@ in runtime blueprint").ToString(), this); + } + #endif //0 + + // Ensure that editor module BP exposed UFunctions can only be called in blueprints for which the base class is also part of an editor module + // Also check for functions wrapped in WITH_EDITOR + if (Function && Blueprint && + (IsEditorOnlyObject(Function) || Function->HasAnyFunctionFlags(FUNC_EditorOnly))) + { if (!bIsEditorOnlyBlueprintBaseClass) { FString const FunctName = Function->GetName(); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallParentFunction.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallParentFunction.cpp index edbb5c061fe6..55b5b7157894 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallParentFunction.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallParentFunction.cpp @@ -71,6 +71,11 @@ void UK2Node_CallParentFunction::SetFromFunction(const UFunction* Function) } } +void UK2Node_CallParentFunction::FixupSelfMemberContext() +{ + // Do nothing. We want the context to continue to be our parent class. +} + void UK2Node_CallParentFunction::PostPlacedNewNode() { // We don't want to check if our function exists in the current scope diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ComponentBoundEvent.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ComponentBoundEvent.cpp index 7fa749d7034f..9cb6b3096bf4 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ComponentBoundEvent.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ComponentBoundEvent.cpp @@ -122,18 +122,13 @@ void UK2Node_ComponentBoundEvent::ValidateNodeDuringCompilation(FCompilerResults bool UK2Node_ComponentBoundEvent::IsDelegateValid() const { - // If there is a property with the correct name, then we are valid - for (TFieldIterator It(GetBlueprint()->GeneratedClass); It; ++It) - { - FObjectProperty* Prop = *It; - if (Prop && Prop->GetFName() == ComponentPropertyName) - { - return true; - } - } - - // Otherwise, the property that we were bound to has been deleted - return false; + const UBlueprint* const BP = GetBlueprint(); + // Validate that the property has not been renamed or deleted via the SCS tree + return BP && FindFProperty(BP->GeneratedClass, ComponentPropertyName) + // Validate that the actual declaration for this event has not been deleted + // either from a native base class or a BP multicast delegate. The Delegate could have been + // renamed/redirected, so also check for a remapped field if we need to + && (GetTargetDelegateProperty() || FMemberReference::FindRemappedField(DelegateOwnerClass, DelegatePropertyName)); } bool UK2Node_ComponentBoundEvent::IsUsedByAuthorityOnlyDelegate() const diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EaseFunction.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EaseFunction.cpp index ec3bd800a0bc..f2ca2498b8dd 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EaseFunction.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EaseFunction.cpp @@ -126,6 +126,8 @@ void UK2Node_EaseFunction::AllocateDefaultPins() SetPinToolTip(*StepsPin, LOCTEXT("StepsPinDescription", "Number of steps required to go from A to B")); K2Schema->SetPinAutogeneratedDefaultValue(StepsPin, TEXT("2")); + GenerateExtraPins(); + RefreshPinVisibility(); } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EditablePinBase.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EditablePinBase.cpp index 7872c02bcc58..03030e5859f7 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EditablePinBase.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EditablePinBase.cpp @@ -434,3 +434,11 @@ bool UK2Node_EditablePinBase::CreateUserDefinedPinsForFunctionEntryExit(const UF return bAllPinsGood; } + +FUserPinInfo::FUserPinInfo(const UEdGraphPin& InPin) + : PinName(InPin.GetFName()) + , PinType(InPin.PinType) + , DesiredPinDirection(InPin.Direction) + , PinDefaultValue(InPin.DefaultValue) +{ +} diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FormatText.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FormatText.cpp index d1cf3ca1de16..c04b3c5d8556 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FormatText.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FormatText.cpp @@ -528,7 +528,8 @@ bool UK2Node_FormatText::IsConnectionDisallowed(const UEdGraphPin* MyPin, const bool bIsValidType = false; if (OtherPinCategory == UEdGraphSchema_K2::PC_Int || OtherPinCategory == UEdGraphSchema_K2::PC_Float || OtherPinCategory == UEdGraphSchema_K2::PC_Text || (OtherPinCategory == UEdGraphSchema_K2::PC_Byte && !OtherPin->PinType.PinSubCategoryObject.IsValid()) || - OtherPinCategory == UEdGraphSchema_K2::PC_Boolean || OtherPinCategory == UEdGraphSchema_K2::PC_String || OtherPinCategory == UEdGraphSchema_K2::PC_Name || OtherPinCategory == UEdGraphSchema_K2::PC_Object) + OtherPinCategory == UEdGraphSchema_K2::PC_Boolean || OtherPinCategory == UEdGraphSchema_K2::PC_String || OtherPinCategory == UEdGraphSchema_K2::PC_Name || OtherPinCategory == UEdGraphSchema_K2::PC_Object || + OtherPinCategory == UEdGraphSchema_K2::PC_Wildcard) { bIsValidType = true; } @@ -543,7 +544,7 @@ bool UK2Node_FormatText::IsConnectionDisallowed(const UEdGraphPin* MyPin, const if (!bIsValidType) { - OutReason = LOCTEXT("Error_InvalidArgumentType", "Format arguments may only be Byte, Integer, Float, Text, String, Name, Boolean, Object, or ETextGender.").ToString(); + OutReason = LOCTEXT("Error_InvalidArgumentType", "Format arguments may only be Byte, Integer, Float, Text, String, Name, Boolean, Object, Wildcard or ETextGender.").ToString(); return true; } } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionEntry.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionEntry.cpp index 1f49d8223e19..8ebedcabde0d 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionEntry.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionEntry.cpp @@ -723,6 +723,36 @@ void UK2Node_FunctionEntry::FindDiffs(UEdGraphNode* OtherNode, struct FDiffResul } } +bool UK2Node_FunctionEntry::IsCompatibleWithGraph(const UEdGraph* InGraph) const +{ + if (CanCreateUnderSpecifiedSchema(InGraph->GetSchema())) + { + if (InGraph->GetSchema()->GetGraphType(InGraph) == GT_Function) + { + TArray Nodes; + InGraph->GetNodesOfClass(Nodes); + return Nodes.Num() == 0; + } + } + + return false; +} + +void UK2Node_FunctionEntry::PostPasteNode() +{ + // ensure there are UserDefinedPins for all pins except the 'then' pin + for (int32 PinIdx = 1; PinIdx < Pins.Num(); ++PinIdx) + { + UEdGraphPin* Pin = Pins[PinIdx]; + if (Pin && !UserDefinedPinExists(Pin->GetFName())) + { + UserDefinedPins.Add(MakeShared(*Pin)); + } + } + + ReconstructNode(); +} + int32 UK2Node_FunctionEntry::GetFunctionFlags() const { int32 ReturnFlags = 0; diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GetSubsystem.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GetSubsystem.cpp index d16c301d9b25..c646abca7549 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GetSubsystem.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GetSubsystem.cpp @@ -226,8 +226,13 @@ void UK2Node_GetSubsystem::GetMenuActions(FBlueprintActionDatabaseRegistrar& Act UClass* ActionKey = GetClass(); if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { - for (auto& Iter : Subclasses) + for (UClass* Iter : Subclasses) { + if (!UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Iter, true)) + { + continue; + } + UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(ActionKey); check(Spawner); @@ -419,8 +424,13 @@ void UK2Node_GetSubsystemFromPC::GetMenuActions(FBlueprintActionDatabaseRegistra UClass* ActionKey = GetClass(); if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { - for (auto& Iter : Subclasses) + for (UClass* Iter : Subclasses) { + if (!UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Iter, true)) + { + continue; + } + UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(ActionKey); check(Spawner); @@ -549,8 +559,13 @@ void UK2Node_GetEngineSubsystem::GetMenuActions(FBlueprintActionDatabaseRegistra UClass* ActionKey = GetClass(); if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { - for (auto& Iter : Subclasses) + for (UClass* Iter : Subclasses) { + if (!UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Iter, true)) + { + continue; + } + UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(ActionKey); check(Spawner); @@ -671,8 +686,13 @@ void UK2Node_GetEditorSubsystem::GetMenuActions(FBlueprintActionDatabaseRegistra UClass* ActionKey = GetClass(); if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { - for (auto& Iter : Subclasses) + for (UClass* Iter : Subclasses) { + if (!UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Iter, true)) + { + continue; + } + UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(ActionKey); check(Spawner); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeStruct.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeStruct.cpp index dc369b71188d..5397f069cd3c 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeStruct.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeStruct.cpp @@ -23,9 +23,10 @@ // UK2Node_MakeStruct -UK2Node_MakeStruct::FMakeStructPinManager::FMakeStructPinManager(const uint8* InSampleStructMemory) +UK2Node_MakeStruct::FMakeStructPinManager::FMakeStructPinManager(const uint8* InSampleStructMemory, UBlueprint* InOwningBP) : FStructOperationOptionalPinManager() , SampleStructMemory(InSampleStructMemory) + , OwningBP(InOwningBP) , bHasAdvancedPins(false) { } @@ -83,14 +84,19 @@ void UK2Node_MakeStruct::FMakeStructPinManager::CustomizePinData(UEdGraphPin* Pi } } -static bool CanBeExposed(const FProperty* Property) +static bool CanBeExposed(const FProperty* Property, UBlueprint* BP) { if (Property) { const UEdGraphSchema_K2* Schema = GetDefault(); check(Schema); - if (!Property->HasAllPropertyFlags(CPF_BlueprintReadOnly)) + const bool bIsEditorBP = IsEditorOnlyObject(BP); + const bool bIsEditAnywhereProperty = Property->HasAllPropertyFlags(CPF_Edit) && + !Property->HasAnyPropertyFlags(CPF_EditConst); + + if (!Property->HasAllPropertyFlags(CPF_BlueprintReadOnly) || + (bIsEditorBP && bIsEditAnywhereProperty) ) { if (Property->HasAllPropertyFlags(CPF_BlueprintVisible) && !(Property->ArrayDim > 1)) { @@ -107,7 +113,7 @@ static bool CanBeExposed(const FProperty* Property) bool UK2Node_MakeStruct::FMakeStructPinManager::CanTreatPropertyAsOptional(FProperty* TestProperty) const { - return CanBeExposed(TestProperty); + return CanBeExposed(TestProperty, OwningBP); } UK2Node_MakeStruct::UK2Node_MakeStruct(const FObjectInitializer& ObjectInitializer) @@ -126,7 +132,7 @@ void UK2Node_MakeStruct::AllocateDefaultPins() bool bHasAdvancedPins = false; { FStructOnScope StructOnScope(StructType); - FMakeStructPinManager OptionalPinManager(StructOnScope.GetStructMemory()); + FMakeStructPinManager OptionalPinManager(StructOnScope.GetStructMemory(), GetBlueprint()); OptionalPinManager.RebuildPropertyList(ShowPinForProperties, StructType); OptionalPinManager.CreateVisiblePins(ShowPinForProperties, StructType, EGPD_Input, this); @@ -176,10 +182,11 @@ void UK2Node_MakeStruct::ValidateNodeDuringCompilation(class FCompilerResultsLog } else { + UBlueprint* BP = GetBlueprint(); for (TFieldIterator It(StructType); It; ++It) { const FProperty* Property = *It; - if (CanBeExposed(Property)) + if (CanBeExposed(Property, BP)) { if (Property->ArrayDim > 1) { @@ -253,13 +260,13 @@ bool UK2Node_MakeStruct::CanBeMade(const UScriptStruct* Struct, const bool bForI return (Struct && !Struct->HasMetaData(FBlueprintMetadata::MD_NativeMakeFunction) && UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Struct, bForInternalUse)); } -bool UK2Node_MakeStruct::CanBeSplit(const UScriptStruct* Struct) +bool UK2Node_MakeStruct::CanBeSplit(const UScriptStruct* Struct, UBlueprint* InBP) { if (CanBeMade(Struct)) { for (TFieldIterator It(Struct); It; ++It) { - if (CanBeExposed(*It)) + if (CanBeExposed(*It, InBP)) { return true; } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_SetFieldsInStruct.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_SetFieldsInStruct.cpp index 3c0e7a015ab4..0b8a9487d76c 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_SetFieldsInStruct.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_SetFieldsInStruct.cpp @@ -143,7 +143,7 @@ void UK2Node_SetFieldsInStruct::AllocateDefaultPins() OutPin->PinToolTip = LOCTEXT("SetFieldsInStruct_OutPinTooltip", "Reference to the input struct").ToString(); { FStructOnScope StructOnScope(StructType); - FSetFieldsInStructPinManager OptionalPinManager(StructOnScope.GetStructMemory()); + FSetFieldsInStructPinManager OptionalPinManager(StructOnScope.GetStructMemory(), GetBlueprint()); OptionalPinManager.RebuildPropertyList(ShowPinForProperties, StructType); OptionalPinManager.CreateVisiblePins(ShowPinForProperties, StructType, EGPD_Input, this); } diff --git a/Engine/Source/Editor/ComponentVisualizers/Private/SplineComponentVisualizer.cpp b/Engine/Source/Editor/ComponentVisualizers/Private/SplineComponentVisualizer.cpp index 9edd3650da41..ef71501803ea 100644 --- a/Engine/Source/Editor/ComponentVisualizers/Private/SplineComponentVisualizer.cpp +++ b/Engine/Source/Editor/ComponentVisualizers/Private/SplineComponentVisualizer.cpp @@ -60,9 +60,12 @@ public: UI_COMMAND(SnapToNearestSplinePoint, "Snap to Nearest Spline Point", "Snap to nearest spline point.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(AlignToNearestSplinePoint, "Align to Nearest Spline Point", "Align to nearest spline point.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(AlignPerpendicularToNearestSplinePoint, "Align Perpendicular to Nearest Spline Point", "Align perpendicular to nearest spline point.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(SnapAllToSelectedX, "Snap All To Selected X", "Snap all spline points to selected spline point X.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(SnapAllToSelectedY, "Snap All To Selected Y", "Snap all spline points to selected spline point Y.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(SnapAllToSelectedZ, "Snap All To Selected Z", "Snap all spline points to selected spline point Z.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(SnapAllToSelectedX, "Snap All To Selected X", "Snap all spline points to selected spline point world X position.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(SnapAllToSelectedY, "Snap All To Selected Y", "Snap all spline points to selected spline point world Y position.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(SnapAllToSelectedZ, "Snap All To Selected Z", "Snap all spline points to selected spline point world Z position.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(SnapToLastSelectedX, "Snap To Last Selected X", "Snap selected spline points to world X position of last selected spline point.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(SnapToLastSelectedY, "Snap To Last Selected Y", "Snap selected spline points to world Y position of last selected spline point.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(SnapToLastSelectedZ, "Snap To Last Selected Z", "Snap selected spline points to world Z position of last selected spline point.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(SetLockedAxisNone, "None", "New spline point axis is not fixed.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(SetLockedAxisX, "X", "Fix X axis when adding new spline points.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(SetLockedAxisY, "Y", "Fix Y axis when adding new spline points.", EUserInterfaceActionType::RadioButton, FInputChord()); @@ -112,15 +115,24 @@ public: /** Align perpendicular to nearest spline point on another spline component */ TSharedPtr AlignPerpendicularToNearestSplinePoint; - /** Snap all spline points to selected point X */ + /** Snap all spline points to selected point world X position*/ TSharedPtr SnapAllToSelectedX; - /** Snap all spline points to selected point Y */ + /** Snap all spline points to selected point world Y position */ TSharedPtr SnapAllToSelectedY; - /** Snap all spline points to selected point Z */ + /** Snap all spline points to selected point world Z position */ TSharedPtr SnapAllToSelectedZ; + /** Snap selected spline points to last selected point world X position */ + TSharedPtr SnapToLastSelectedX; + + /** Snap selected spline points to last selected point world Y position */ + TSharedPtr SnapToLastSelectedY; + + /** Snap selected spline points to last selected point world Z position */ + TSharedPtr SnapToLastSelectedZ; + /** No axis is locked when adding new spline points */ TSharedPtr SetLockedAxisNone; @@ -240,24 +252,33 @@ void FSplineComponentVisualizer::OnRegister() SplineComponentVisualizerActions->MapAction( Commands.SnapAllToSelectedX, - FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnSnapAll, EAxis::X), - FCanExecuteAction::CreateSP(this, &FSplineComponentVisualizer::CanSnapAll)); + FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnSnapAllToAxis, EAxis::X), + FCanExecuteAction::CreateSP(this, &FSplineComponentVisualizer::CanSnapAllToAxis)); SplineComponentVisualizerActions->MapAction( Commands.SnapAllToSelectedY, - FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnSnapAll, EAxis::Y), - FCanExecuteAction::CreateSP(this, &FSplineComponentVisualizer::CanSnapAll)); + FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnSnapAllToAxis, EAxis::Y), + FCanExecuteAction::CreateSP(this, &FSplineComponentVisualizer::CanSnapAllToAxis)); SplineComponentVisualizerActions->MapAction( Commands.SnapAllToSelectedZ, - FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnSnapAll, EAxis::Z), - FCanExecuteAction::CreateSP(this, &FSplineComponentVisualizer::CanSnapAll)); + FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnSnapAllToAxis, EAxis::Z), + FCanExecuteAction::CreateSP(this, &FSplineComponentVisualizer::CanSnapAllToAxis)); SplineComponentVisualizerActions->MapAction( - Commands.SetLockedAxisNone, - FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnLockAxis, EAxis::None), - FCanExecuteAction(), - FIsActionChecked::CreateSP(this, &FSplineComponentVisualizer::IsLockAxisSet, EAxis::None)); + Commands.SnapToLastSelectedX, + FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnSnapSelectedToAxis, EAxis::X), + FCanExecuteAction::CreateSP(this, &FSplineComponentVisualizer::CanSnapSelectedToAxis)); + + SplineComponentVisualizerActions->MapAction( + Commands.SnapToLastSelectedY, + FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnSnapSelectedToAxis, EAxis::Y), + FCanExecuteAction::CreateSP(this, &FSplineComponentVisualizer::CanSnapSelectedToAxis)); + + SplineComponentVisualizerActions->MapAction( + Commands.SnapToLastSelectedZ, + FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnSnapSelectedToAxis, EAxis::Z), + FCanExecuteAction::CreateSP(this, &FSplineComponentVisualizer::CanSnapSelectedToAxis)); SplineComponentVisualizerActions->MapAction( Commands.SetLockedAxisX, @@ -277,7 +298,6 @@ void FSplineComponentVisualizer::OnRegister() FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FSplineComponentVisualizer::IsLockAxisSet, EAxis::Z)); - SplineComponentVisualizerActions->MapAction( Commands.VisualizeRollAndScale, FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnSetVisualizeRollAndScale), @@ -310,7 +330,6 @@ void FSplineComponentVisualizer::OnRegister() bUseBounds = false; bUsePivot = false; SplineComponentVisualizerActions->MapAction( - //Commands.AlignToFloor, FLevelEditorCommands::Get().AlignToFloor, FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::SnapToFloor_Clicked, bAlign, bUseLineTrace, bUseBounds, bUsePivot), FCanExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::ActorSelected_CanExecute) @@ -349,8 +368,8 @@ void FSplineComponentVisualizer::DrawVisualization(const UActorComponent* Compon const FColor ReadOnlyColor = FColor(255, 0, 255, 255); const FColor NormalColor = bIsSplineEditable ? FColor(SplineComp->EditorUnselectedSplineSegmentColor.ToFColor(true)) : ReadOnlyColor; const FColor SelectedColor = bIsSplineEditable ? FColor(SplineComp->EditorSelectedSplineSegmentColor.ToFColor(true)) : ReadOnlyColor; - const float GrabHandleSize = 10.0f; - const float TangentHandleSize = 8.0f; + const FColor TangentColor = bIsSplineEditable ? FColor(SplineComp->EditorTangentColor.ToFColor(true)) : ReadOnlyColor; + const float GrabHandleSize = 10.0f + (bIsSplineEditable ? GetDefault()->SelectedSplinePointSizeAdjustment : 0.0f); // Draw the tangent handles before anything else so they will not overdraw the rest of the spline if (SplineComp == EditedSplineComp) @@ -374,27 +393,30 @@ void FSplineComponentVisualizer::DrawVisualization(const UActorComponent* Compon if (SplineInfo.Points[SelectedKey].IsCurveKey()) { + const float TangentHandleSize = 8.0f + (bIsSplineEditable ? GetDefault()->SplineTangentHandleSizeAdjustment : 0.0f); + const float TangentScale = GetDefault()->SplineTangentScale; + const FVector Location = SplineComp->GetLocationAtSplinePoint(SelectedKey, ESplineCoordinateSpace::World); - const FVector LeaveTangent = SplineComp->GetLeaveTangentAtSplinePoint(SelectedKey, ESplineCoordinateSpace::World); + const FVector LeaveTangent = SplineComp->GetLeaveTangentAtSplinePoint(SelectedKey, ESplineCoordinateSpace::World) * TangentScale; const FVector ArriveTangent = SplineComp->bAllowDiscontinuousSpline ? - SplineComp->GetArriveTangentAtSplinePoint(SelectedKey, ESplineCoordinateSpace::World) : LeaveTangent; + SplineComp->GetArriveTangentAtSplinePoint(SelectedKey, ESplineCoordinateSpace::World) * TangentScale : LeaveTangent; PDI->SetHitProxy(NULL); - PDI->DrawLine(Location, Location + LeaveTangent, SelectedColor, SDPG_Foreground); - PDI->DrawLine(Location, Location - ArriveTangent, SelectedColor, SDPG_Foreground); + PDI->DrawLine(Location, Location + LeaveTangent, TangentColor, SDPG_Foreground); + PDI->DrawLine(Location, Location - ArriveTangent, TangentColor, SDPG_Foreground); if (bIsSplineEditable) { PDI->SetHitProxy(new HSplineTangentHandleProxy(Component, SelectedKey, false)); } - PDI->DrawPoint(Location + LeaveTangent, SelectedColor, TangentHandleSize, SDPG_Foreground); + PDI->DrawPoint(Location + LeaveTangent, TangentColor, TangentHandleSize, SDPG_Foreground); if (bIsSplineEditable) { PDI->SetHitProxy(new HSplineTangentHandleProxy(Component, SelectedKey, true)); } - PDI->DrawPoint(Location - ArriveTangent, SelectedColor, TangentHandleSize, SDPG_Foreground); + PDI->DrawPoint(Location - ArriveTangent, TangentColor, TangentHandleSize, SDPG_Foreground); PDI->SetHitProxy(NULL); } @@ -479,6 +501,7 @@ void FSplineComponentVisualizer::DrawVisualization(const UActorComponent* Compon // Then draw a line for each substep. const int32 NumSteps = 20; + const float SegmentLineThickness = GetDefault()->SplineLineThicknessAdjustment; for (int32 StepIdx = 1; StepIdx <= NumSteps; StepIdx++) { @@ -487,7 +510,7 @@ void FSplineComponentVisualizer::DrawVisualization(const UActorComponent* Compon const FVector NewRightVector = SplineComp->GetRightVectorAtSplineInputKey(Key, ESplineCoordinateSpace::World); const FVector NewScale = SplineComp->GetScaleAtSplineInputKey(Key) * DefaultScale; - PDI->DrawLine(OldPos, NewPos, LineColor, SDPG_Foreground); + PDI->DrawLine(OldPos, NewPos, LineColor, SDPG_Foreground, SegmentLineThickness); if (bShouldVisualizeScale) { PDI->DrawLine(OldPos - OldRightVector * OldScale.Y, NewPos - NewRightVector * NewScale.Y, LineColor, SDPG_Foreground); @@ -709,13 +732,15 @@ bool FSplineComponentVisualizer::GetWidgetLocation(const FEditorViewportClient* const auto& Point = Position.Points[SelectedTangentHandle]; check(SelectedTangentHandleType != ESelectedTangentHandle::None); + const float TangentScale = GetDefault()->SplineTangentScale; + if (SelectedTangentHandleType == ESelectedTangentHandle::Leave) { - OutLocation = SplineComp->GetComponentTransform().TransformPosition(Point.OutVal + Point.LeaveTangent); + OutLocation = SplineComp->GetComponentTransform().TransformPosition(Point.OutVal + Point.LeaveTangent * TangentScale); } else if (SelectedTangentHandleType == ESelectedTangentHandle::Arrive) { - OutLocation = SplineComp->GetComponentTransform().TransformPosition(Point.OutVal - Point.ArriveTangent); + OutLocation = SplineComp->GetComponentTransform().TransformPosition(Point.OutVal - Point.ArriveTangent * TangentScale); } return true; @@ -772,6 +797,13 @@ bool FSplineComponentVisualizer::IsAnySelectedKeyIndexOutOfRange(const USplineCo return Algo::AnyOf(SelectedKeys, [NumPoints](int32 Index) { return Index >= NumPoints; }); } +bool FSplineComponentVisualizer::IsSingleKeySelected() const +{ + USplineComponent* SplineComp = GetEditedSplineComponent(); + return (SplineComp != nullptr && + SelectedKeys.Num() == 1 && + LastKeyIndexSelected != INDEX_NONE); +} bool FSplineComponentVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) { @@ -850,22 +882,24 @@ bool FSplineComponentVisualizer::TransformSelectedTangent(const FVector& DeltaTr { SplineComp->Modify(); + const float TangentScale = GetDefault()->SplineTangentScale; + FInterpCurvePoint& EditedPoint = SplinePosition.Points[SelectedTangentHandle]; if (SplineComp->bAllowDiscontinuousSpline) { if (SelectedTangentHandleType == ESelectedTangentHandle::Leave) { - EditedPoint.LeaveTangent += SplineComp->GetComponentTransform().InverseTransformVector(DeltaTranslate); + EditedPoint.LeaveTangent += SplineComp->GetComponentTransform().InverseTransformVector(DeltaTranslate) / TangentScale; } else { - EditedPoint.ArriveTangent += SplineComp->GetComponentTransform().InverseTransformVector(-DeltaTranslate); + EditedPoint.ArriveTangent += SplineComp->GetComponentTransform().InverseTransformVector(-DeltaTranslate) / TangentScale; } } else { const FVector Delta = (SelectedTangentHandleType == ESelectedTangentHandle::Leave) ? DeltaTranslate : -DeltaTranslate; - const FVector Tangent = EditedPoint.LeaveTangent + SplineComp->GetComponentTransform().InverseTransformVector(Delta); + const FVector Tangent = EditedPoint.LeaveTangent + SplineComp->GetComponentTransform().InverseTransformVector(Delta) / TangentScale; EditedPoint.LeaveTangent = Tangent; EditedPoint.ArriveTangent = Tangent; @@ -1437,7 +1471,7 @@ bool FSplineComponentVisualizer::CanSnapToNearestSplinePoint() const LastKeyIndexSelected != INDEX_NONE); } -void FSplineComponentVisualizer::OnSnapAll(EAxis::Type InAxis) +void FSplineComponentVisualizer::OnSnapAllToAxis(EAxis::Type InAxis) { const FScopedTransaction Transaction(LOCTEXT("SnapAllToSelectedAxis", "Snap All To Selected Axis")); USplineComponent* SplineComp = GetEditedSplineComponent(); @@ -1449,6 +1483,49 @@ void FSplineComponentVisualizer::OnSnapAll(EAxis::Type InAxis) check(SelectedKeys.Contains(LastKeyIndexSelected)); check(InAxis == EAxis::X || InAxis == EAxis::Y || InAxis == EAxis::Z); + TArray SnapKeys; + for (int32 KeyIdx = 0; KeyIdx < SplineComp->GetNumberOfSplinePoints(); KeyIdx++) + { + if (KeyIdx != LastKeyIndexSelected) + { + SnapKeys.Add(KeyIdx); + } + } + + SnapToLastSelectedAxisPosition(InAxis, SnapKeys); +} + +void FSplineComponentVisualizer::OnSnapSelectedToAxis(EAxis::Type InAxis) +{ + const FScopedTransaction Transaction(LOCTEXT("SnapSelectedToLastAxis", "Snap Selected To Axis")); + + USplineComponent* SplineComp = GetEditedSplineComponent(); + check(SplineComp != nullptr); check(SelectedKeys.Num() > 1); + check(LastKeyIndexSelected != INDEX_NONE); + check(LastKeyIndexSelected >= 0); + check(LastKeyIndexSelected < SplineComp->GetNumberOfSplinePoints()); + + TArray SnapKeys; + for (int32 KeyIdx : SelectedKeys) + { + if (KeyIdx != LastKeyIndexSelected) + { + SnapKeys.Add(KeyIdx); + } + } + + SnapToLastSelectedAxisPosition(InAxis, SnapKeys); +} + +void FSplineComponentVisualizer::SnapToLastSelectedAxisPosition(const EAxis::Type InAxis, TArray InSnapKeys) +{ + USplineComponent* SplineComp = GetEditedSplineComponent(); + check(SplineComp != nullptr); + check(InAxis == EAxis::X || InAxis == EAxis::Y || InAxis == EAxis::Z); + check(LastKeyIndexSelected != INDEX_NONE); + check(LastKeyIndexSelected >= 0); + check(LastKeyIndexSelected < SplineComp->GetNumberOfSplinePoints()); + SplineComp->Modify(); if (AActor* Owner = SplineComp->GetOwner()) { @@ -1460,69 +1537,51 @@ void FSplineComponentVisualizer::OnSnapAll(EAxis::Type InAxis) const FVector WorldPos = SplineComp->GetComponentTransform().TransformPosition(SplinePositions.Points[LastKeyIndexSelected].OutVal); - FVector NewUpVector; float WorldSnapAxisValue = 0.0f; if (InAxis == EAxis::X) { WorldSnapAxisValue = WorldPos.X; - NewUpVector = FVector::ForwardVector; } else if (InAxis == EAxis::Y) { WorldSnapAxisValue = WorldPos.Y; - NewUpVector = FVector::RightVector; } else { WorldSnapAxisValue = WorldPos.Z; - NewUpVector = FVector::UpVector; } - + int32 NumPoints = SplinePositions.Points.Num(); - for (int32 KeyIdx = 0; KeyIdx < NumPoints; KeyIdx++) + for (int32 KeyIdx : InSnapKeys) { - FInterpCurvePoint& EditedPosition = SplinePositions.Points[KeyIdx]; - FInterpCurvePoint& EditedRotation = SplineRotations.Points[KeyIdx]; - - // Copy position - FVector NewWorldPos = SplineComp->GetComponentTransform().TransformPosition(EditedPosition.OutVal); // convert local-space position to world-space - if (InAxis == EAxis::X) + if (KeyIdx >= 0 && KeyIdx < SplineComp->GetNumberOfSplinePoints()) { - NewWorldPos.X = WorldSnapAxisValue; + FInterpCurvePoint& EditedPosition = SplinePositions.Points[KeyIdx]; + FInterpCurvePoint& EditedRotation = SplineRotations.Points[KeyIdx]; + + // Copy position + FVector NewWorldPos = SplineComp->GetComponentTransform().TransformPosition(EditedPosition.OutVal); // convert local-space position to world-space + if (InAxis == EAxis::X) + { + NewWorldPos.X = WorldSnapAxisValue; + } + else if (InAxis == EAxis::Y) + { + NewWorldPos.Y = WorldSnapAxisValue; + } + else + { + NewWorldPos.Z = WorldSnapAxisValue; + } + + EditedPosition.OutVal = SplineComp->GetComponentTransform().InverseTransformPosition(NewWorldPos); // convert world-space position to local-space + + // Set point to auto so its tangents will be auto-adjusted after snapping + EditedPosition.InterpMode = CIM_CurveAuto; } - else if (InAxis == EAxis::Y) - { - NewWorldPos.Y = WorldSnapAxisValue; - } - else - { - NewWorldPos.Z = WorldSnapAxisValue; - } - - EditedPosition.OutVal = SplineComp->GetComponentTransform().InverseTransformPosition(NewWorldPos); // convert world-space position to local-space - - // Set point tangent as user controlled - EditedPosition.InterpMode = CIM_CurveUser; - - // Get delta rotation between current up vector and new up vector - FVector WorldUpVector = SplineComp->GetUpVectorAtSplineInputKey(KeyIdx, ESplineCoordinateSpace::World); - FQuat DeltaRotate = FQuat::FindBetweenNormals(WorldUpVector, NewUpVector); - - // Rotate tangent according to delta rotation - FVector NewTangent = SplineComp->GetComponentTransform().GetRotation().RotateVector(EditedPosition.LeaveTangent); // convert local-space tangent vector to world-space - NewTangent = DeltaRotate.RotateVector(NewTangent); // apply world-space delta rotation to world-space tangent - NewTangent = SplineComp->GetComponentTransform().GetRotation().Inverse().RotateVector(NewTangent); // convert world-space tangent vector back into local-space - EditedPosition.LeaveTangent = NewTangent; - EditedPosition.ArriveTangent = NewTangent; - - // Rotate spline rotation according to delta rotation - FQuat NewRot = SplineComp->GetComponentTransform().GetRotation() * EditedRotation.OutVal; // convert local-space rotation to world-space - NewRot = DeltaRotate * NewRot; // apply world-space rotation - NewRot = SplineComp->GetComponentTransform().GetRotation().Inverse() * NewRot; // convert world-space rotation to local-space - EditedRotation.OutVal = NewRot; } - + SplineComp->UpdateSpline(); SplineComp->bSplineHasBeenEdited = true; @@ -1533,7 +1592,7 @@ void FSplineComponentVisualizer::OnSnapAll(EAxis::Type InAxis) GEditor->RedrawLevelEditingViewports(true); } -bool FSplineComponentVisualizer::CanSnapAll() const +bool FSplineComponentVisualizer::CanSnapAllToAxis() const { USplineComponent* SplineComp = GetEditedSplineComponent(); return (SplineComp != nullptr && @@ -1541,6 +1600,13 @@ bool FSplineComponentVisualizer::CanSnapAll() const LastKeyIndexSelected != INDEX_NONE); } +bool FSplineComponentVisualizer::CanSnapSelectedToAxis() const +{ + USplineComponent* SplineComp = GetEditedSplineComponent(); + return (SplineComp != nullptr && + SelectedKeys.Num() > 1 && + LastKeyIndexSelected != INDEX_NONE); +} void FSplineComponentVisualizer::EndEditing() { @@ -1682,6 +1748,10 @@ bool FSplineComponentVisualizer::DuplicateKeyForAltDrag(const FVector& InDrag) check(SelectedKeys.Num() == 1); check(SelectedKeys.Contains(LastKeyIndexSelected)); + // When dragging from end point, maximum angle is 60 degrees from attached segment + // to determine whether to split existing segment or create a new point + static const float Angle60 = 1.0472; + // Insert duplicates into the list, highest index first, so that the lower indices remain the same FInterpCurveVector& SplinePosition = SplineComp->GetSplinePointsPosition(); @@ -1704,7 +1774,7 @@ bool FSplineComponentVisualizer::DuplicateKeyForAltDrag(const FVector& InDrag) } else { - PrevAngle = HALF_PI; + PrevAngle = Angle60; } } @@ -1722,16 +1792,16 @@ bool FSplineComponentVisualizer::DuplicateKeyForAltDrag(const FVector& InDrag) } else { - NextAngle = HALF_PI; + NextAngle = Angle60; } } // Set key index to which the drag will be applied after duplication int32 SegmentIndex = CurrentIndex; - // Note dragging off first or last key in non-closed loop spline always adds a new segment if ((bHasPrevKey && bHasNextKey && PrevAngle < NextAngle) || - (!bHasPrevKey && bHasNextKey)) + (bHasPrevKey && !bHasNextKey && PrevAngle < Angle60) || + (!bHasPrevKey && bHasNextKey && NextAngle >= Angle60)) { SegmentIndex--; } @@ -2557,8 +2627,8 @@ void FSplineComponentVisualizer::GenerateContextMenuSections(FMenuBuilder& InMen InMenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().FocusViewportToSelection); InMenuBuilder.AddSubMenu( - LOCTEXT("SnapAlign", "Snap/Align"), - LOCTEXT("SnapAlignTooltip", "Snap align options."), + LOCTEXT("SplineSnapAlign", "Snap/Align"), + LOCTEXT("SplineSnapAlignTooltip", "Snap align options."), FNewMenuDelegate::CreateSP(this, &FSplineComponentVisualizer::GenerateSnapAlignSubMenu)); /* temporarily disabled @@ -2601,12 +2671,18 @@ void FSplineComponentVisualizer::GenerateSnapAlignSubMenu(FMenuBuilder& MenuBuil { MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().SnapToFloor); MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().AlignToFloor); + MenuBuilder.AddSeparator(); MenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().SnapToNearestSplinePoint); MenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().AlignToNearestSplinePoint); MenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().AlignPerpendicularToNearestSplinePoint); + MenuBuilder.AddSeparator(); MenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().SnapAllToSelectedX); MenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().SnapAllToSelectedY); MenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().SnapAllToSelectedZ); + MenuBuilder.AddSeparator(); + MenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().SnapToLastSelectedX); + MenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().SnapToLastSelectedY); + MenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().SnapToLastSelectedZ); } void FSplineComponentVisualizer::GenerateLockAxisSubMenu(FMenuBuilder& MenuBuilder) const diff --git a/Engine/Source/Editor/ComponentVisualizers/Public/SplineComponentVisualizer.h b/Engine/Source/Editor/ComponentVisualizers/Public/SplineComponentVisualizer.h index 0f6b64d126b4..72e1a32d1d67 100644 --- a/Engine/Source/Editor/ComponentVisualizers/Public/SplineComponentVisualizer.h +++ b/Engine/Source/Editor/ComponentVisualizers/Public/SplineComponentVisualizer.h @@ -126,6 +126,9 @@ protected: /** Determine if any selected key index is out of range (perhaps because something external has modified the spline) */ bool IsAnySelectedKeyIndexOutOfRange(const USplineComponent* Comp) const; + /** Whether a single spline key is currently selected */ + bool IsSingleKeySelected() const; + /** Transforms selected tangent by given translation */ bool TransformSelectedTangent(const FVector& DeltaTranslate); @@ -159,6 +162,9 @@ protected: /** Alt-drag: duplicates the selected spline key */ virtual void ResetAllowDuplication(); + /** Snapping: snap keys to axis position of last selected key */ + void SnapToLastSelectedAxisPosition(const EAxis::Type InAxis, TArray InSnapKeys); + void OnDeleteKey(); bool CanDeleteKey() const; @@ -172,12 +178,15 @@ protected: void OnSnapToNearestSplinePoint(ESplineComponentSnapMode::Type InSnapMode); bool CanSnapToNearestSplinePoint() const; - void OnSnapAll(EAxis::Type InAxis); - bool CanSnapAll() const; + void OnSnapAllToAxis(EAxis::Type InAxis); + bool CanSnapAllToAxis() const; + + void OnSnapSelectedToAxis(EAxis::Type InAxis); + bool CanSnapSelectedToAxis() const; void OnLockAxis(EAxis::Type InAxis); bool IsLockAxisSet(EAxis::Type InAxis) const; - + void OnResetToAutomaticTangent(EInterpCurveMode Mode); bool CanResetToAutomaticTangent(EInterpCurveMode Mode) const; diff --git a/Engine/Source/Editor/ContentBrowser/Private/AssetViewTypes.cpp b/Engine/Source/Editor/ContentBrowser/Private/AssetViewTypes.cpp index 5ffc0a565d0d..6419b4feee0e 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/AssetViewTypes.cpp +++ b/Engine/Source/Editor/ContentBrowser/Private/AssetViewTypes.cpp @@ -129,7 +129,7 @@ bool FAssetViewItem::GetTagValue(const FName Tag, FString& OutString, UObject::F return true; } - FContentBrowserItemDataAttributeValue TagValue = Item.GetItemAttribute(Tag); + FContentBrowserItemDataAttributeValue TagValue = Item.GetItemAttribute(Tag, true); if (TagValue.IsValid()) { OutString = TagValue.GetValue(); diff --git a/Engine/Source/Editor/ContentBrowser/Private/ContentBrowserUtils.cpp b/Engine/Source/Editor/ContentBrowser/Private/ContentBrowserUtils.cpp index 5232b12d3d88..afac59fa4da3 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/ContentBrowserUtils.cpp +++ b/Engine/Source/Editor/ContentBrowser/Private/ContentBrowserUtils.cpp @@ -334,7 +334,7 @@ void ContentBrowserUtils::CopyFilePathsToClipboard(const TArraySetEnabled(false, false); + SetFrontendFilterActive(FrontendFilter.ToSharedRef(), false); ExecuteOnFilteChanged = true; } } diff --git a/Engine/Source/Editor/ContentBrowserData/Private/ContentBrowserDataSubsystem.cpp b/Engine/Source/Editor/ContentBrowserData/Private/ContentBrowserDataSubsystem.cpp index 684ee76a15d2..4b59585dff3c 100644 --- a/Engine/Source/Editor/ContentBrowserData/Private/ContentBrowserDataSubsystem.cpp +++ b/Engine/Source/Editor/ContentBrowserData/Private/ContentBrowserDataSubsystem.cpp @@ -5,6 +5,7 @@ #include "Containers/Ticker.h" #include "Misc/PackageName.h" #include "Features/IModularFeatures.h" +#include "Stats/Stats.h" void UContentBrowserDataSubsystem::Initialize(FSubsystemCollectionBase& Collection) { @@ -530,6 +531,7 @@ void UContentBrowserDataSubsystem::HandleDataSourceUnregistered(const FName& Typ void UContentBrowserDataSubsystem::Tick(const float InDeltaTime) { + QUICK_SCOPE_CYCLE_COUNTER(STAT_UContentBrowserDataSubsystem_Tick); for (const auto& AvailableDataSourcePair : AvailableDataSources) { AvailableDataSourcePair.Value->Tick(InDeltaTime); diff --git a/Engine/Source/Editor/CurveEditor/Private/RichCurveEditorModel.cpp b/Engine/Source/Editor/CurveEditor/Private/RichCurveEditorModel.cpp index 6156b9839f29..3bc1026bad60 100644 --- a/Engine/Source/Editor/CurveEditor/Private/RichCurveEditorModel.cpp +++ b/Engine/Source/Editor/CurveEditor/Private/RichCurveEditorModel.cpp @@ -27,7 +27,7 @@ void RefineCurvePoints(const FRichCurve& RichCurve, double TimeThreshold, float { bool bSegmentIsLinear = true; - TTuple Evaluated[UE_ARRAY_COUNT(InterpTimes)]; + TTuple Evaluated[UE_ARRAY_COUNT(InterpTimes)] = { TTuple(0, 0) }; for (int32 InterpIndex = 0; InterpIndex < UE_ARRAY_COUNT(InterpTimes); ++InterpIndex) { diff --git a/Engine/Source/Editor/CurveEditor/Private/Tree/CurveEditorTree.cpp b/Engine/Source/Editor/CurveEditor/Private/Tree/CurveEditorTree.cpp index cacdadd171e3..fc5382803137 100644 --- a/Engine/Source/Editor/CurveEditor/Private/Tree/CurveEditorTree.cpp +++ b/Engine/Source/Editor/CurveEditor/Private/Tree/CurveEditorTree.cpp @@ -113,6 +113,7 @@ FScopedCurveEditorTreeEventGuard::~FScopedCurveEditorTreeEventGuard() { if (Tree->Events.OnItemsChanged.SerialNumber != CachedItemSerialNumber) { + Tree->Compact(); Tree->Events.OnItemsChanged.Broadcast(); } @@ -237,6 +238,12 @@ void FCurveEditorTree::RemoveChildrenRecursive(TArray&& } } +void FCurveEditorTree::Compact() +{ + Items.Compact(); + ChildItemIDs.Compact(); +} + bool FCurveEditorTree::PerformFilterPass(TArrayView FilterPtrs, TArrayView ItemsToFilter, ECurveEditorTreeFilterState InheritedState) { bool bAnyMatched = false; diff --git a/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTree.h b/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTree.h index a0ea643fc4b8..1c4a4bdbbf22 100644 --- a/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTree.h +++ b/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTree.h @@ -455,6 +455,11 @@ public: return FScopedCurveEditorTreeEventGuard(this); } + /** + * Compact the memory used by this tree (does not modify any meaningful state) + */ + void Compact(); + private: // Recursively removes children without removing them from the parent (assuming the parent is also being removed) diff --git a/Engine/Source/Editor/DetailCustomizations/Private/ActorDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/ActorDetails.cpp index f610528e2d25..f5467a12a0c7 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/ActorDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/ActorDetails.cpp @@ -571,7 +571,7 @@ void FActorDetails::AddActorCategory( IDetailLayoutBuilder& DetailBuilder, const [ SNew(STextBlock) .Text(LOCTEXT("ActorPackagingMode", "Packaging Mode")) - .ToolTipText(LOCTEXT("ActorPackagingMode_ToolTip", "Change the actor packaging mode. This will indicate if the actor is packaged alongside the its level or in an external package.")) + .ToolTipText(LOCTEXT("ActorPackagingMode_ToolTip", "Change the actor packaging mode. This will indicate if the actor is packaged alongside its level or in an external package.")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() diff --git a/Engine/Source/Editor/DetailCustomizations/Private/DetailCustomizations.cpp b/Engine/Source/Editor/DetailCustomizations/Private/DetailCustomizations.cpp index 42a41fc4fb4b..10ba6e448483 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/DetailCustomizations.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/DetailCustomizations.cpp @@ -140,6 +140,7 @@ #include "MaterialShadingModelCustomization.h" #include "DebugCameraControllerSettingsCustomization.h" #include "BoundsCopyComponentDetails.h" +#include "SupportedRangeTypes.h" // StructsSupportingRangeVisibility IMPLEMENT_MODULE( FDetailCustomizationsModule, DetailCustomizations ); @@ -185,6 +186,10 @@ void FDetailCustomizationsModule::ShutdownModule() } } +/** Helper that will flag this struct name as supporting the UIMin and UIMax meta data types */ +#define REGISTER_UIMINMAX_CUSTOMIZATION( StructName, CallbackFunc ) \ + RangeVisibilityUtils::StructsSupportingRangeVisibility.Add( StructName ); \ + RegisterCustomPropertyTypeLayout( StructName, FOnGetPropertyTypeCustomizationInstance::CreateStatic( CallbackFunc )); void FDetailCustomizationsModule::RegisterPropertyTypeCustomizations() { @@ -193,12 +198,12 @@ void FDetailCustomizationsModule::RegisterPropertyTypeCustomizations() RegisterCustomPropertyTypeLayout("DataTableRowHandle", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FDataTableCustomizationLayout::MakeInstance)); RegisterCustomPropertyTypeLayout("DataTableCategoryHandle", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FDataTableCategoryCustomizationLayout::MakeInstance)); RegisterCustomPropertyTypeLayout("CurveTableRowHandle", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FCurveTableCustomizationLayout::MakeInstance)); - RegisterCustomPropertyTypeLayout(NAME_Vector, FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FVectorStructCustomization::MakeInstance)); - RegisterCustomPropertyTypeLayout("IntVector", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FVectorStructCustomization::MakeInstance)); - RegisterCustomPropertyTypeLayout(NAME_Vector4, FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FVector4StructCustomization::MakeInstance)); - RegisterCustomPropertyTypeLayout(NAME_Vector2D, FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FMathStructCustomization::MakeInstance)); - RegisterCustomPropertyTypeLayout(NAME_IntPoint, FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FMathStructCustomization::MakeInstance)); - RegisterCustomPropertyTypeLayout(NAME_Rotator, FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FRotatorStructCustomization::MakeInstance)); + REGISTER_UIMINMAX_CUSTOMIZATION(NAME_Vector, &FVectorStructCustomization::MakeInstance); + REGISTER_UIMINMAX_CUSTOMIZATION("IntVector", &FVectorStructCustomization::MakeInstance); + REGISTER_UIMINMAX_CUSTOMIZATION(NAME_Vector4, &FVector4StructCustomization::MakeInstance); + REGISTER_UIMINMAX_CUSTOMIZATION(NAME_Vector2D, &FMathStructCustomization::MakeInstance); + REGISTER_UIMINMAX_CUSTOMIZATION(NAME_IntPoint, &FMathStructCustomization::MakeInstance); + REGISTER_UIMINMAX_CUSTOMIZATION(NAME_Rotator, &FRotatorStructCustomization::MakeInstance); RegisterCustomPropertyTypeLayout(NAME_LinearColor, FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FColorStructCustomization::MakeInstance)); RegisterCustomPropertyTypeLayout(NAME_Color, FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FColorStructCustomization::MakeInstance)); RegisterCustomPropertyTypeLayout(NAME_Matrix, FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FMatrixStructCustomization::MakeInstance)); @@ -277,6 +282,8 @@ void FDetailCustomizationsModule::RegisterPropertyTypeCustomizations() RegisterCustomPropertyTypeLayout("DebugCameraControllerSettingsViewModeIndex", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FDebugCameraControllerSettingsViewModeIndexCustomization::MakeInstance)); } +#undef REGISTER_UIMINMAX_CUSTOMIZATION + void FDetailCustomizationsModule::RegisterObjectCustomizations() { // Note: By default properties are displayed in script defined order (i.e the order in the header). These layout detail classes are called in the order seen here which will display properties @@ -412,4 +419,4 @@ void FDetailCustomizationsModule::RegisterCustomPropertyTypeLayout(FName Propert static FName PropertyEditor("PropertyEditor"); FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked(PropertyEditor); PropertyModule.RegisterCustomPropertyTypeLayout(PropertyTypeName, PropertyTypeLayoutDelegate); -} +} \ No newline at end of file diff --git a/Engine/Source/Editor/DetailCustomizations/Private/HardwareTargetingSettingsDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/HardwareTargetingSettingsDetails.cpp index 2b78ad2042c9..ac95caa6461c 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/HardwareTargetingSettingsDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/HardwareTargetingSettingsDetails.cpp @@ -192,7 +192,7 @@ public: SettingRegions.FindChecked(Settings.SettingsObject)->SetText(Settings.Description); // @todo userconfig: Should we check for GlobalUserConfig here? It's not ever going to be checked in... - if (!Settings.SettingsObject->GetClass()->HasAnyClassFlags(CLASS_Config | CLASS_DefaultConfig /*| CLASS_GlobalUserConfig*/)) + if (!Settings.SettingsObject->GetClass()->HasAnyClassFlags(CLASS_Config | CLASS_DefaultConfig /*| CLASS_GlobalUserConfig | CLASS_ProjectUserConfig*/)) { continue; } diff --git a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp index 64d9fbba76da..5454781b110c 100644 --- a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp +++ b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp @@ -4845,33 +4845,19 @@ void FSlateEditorStyle::FStyle::SetupLevelEditorStyle() Set("LevelEditor.SourceControl.Problem.Small", new IMAGE_BRUSH("Icons/icon_source_control_40x_problem", Icon20x20)); Set("LevelEditor.PreviewMode.Enabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_SM5_Enabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.Enabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_SM5_Enabled_40x", Icon20x20)); Set("LevelEditor.PreviewMode.Disabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_SM5_Disabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.Disabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_SM5_Disabled_40x", Icon20x20)); Set("LevelEditor.PreviewMode.SM5.Enabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_SM5_Enabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.SM5.Enabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_SM5_Enabled_40x", Icon20x20)); Set("LevelEditor.PreviewMode.SM5.Disabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_SM5_Enabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.SM5.Disabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_SM5_Enabled_40x", Icon20x20)); - Set("LevelEditor.PreviewMode.SM4.Enabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_SM4_Enabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.SM4.Enabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_SM4_Enabled_40x", Icon20x20)); - Set("LevelEditor.PreviewMode.SM4.Disabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_SM4_Disabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.SM4.Disabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_SM4_Disabled_40x", Icon20x20)); - Set("LevelEditor.PreviewMode.AndroidES2.Enabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidES2_Enabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.AndroidES2.Enabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidES2_Enabled_40x", Icon20x20)); - Set("LevelEditor.PreviewMode.AndroidES2.Disabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidES2_Disabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.AndroidES2.Disabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidES2_Disabled_40x", Icon20x20)); Set("LevelEditor.PreviewMode.AndroidES31.Enabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidES31_Enabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.AndroidES31.Enabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidES31_Enabled_40x", Icon20x20)); Set("LevelEditor.PreviewMode.AndroidES31.Disabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidES31_Disabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.AndroidES31.Disabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidES31_Disabled_40x", Icon20x20)); Set("LevelEditor.PreviewMode.AndroidVulkan.Enabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidVulkan_Enabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.AndroidVulkan.Enabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidVulkan_Enabled_40x", Icon20x20)); Set("LevelEditor.PreviewMode.AndroidVulkan.Disabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidVulkan_Disabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.AndroidVulkan.Disabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidVulkan_Disabled_40x", Icon20x20)); + Set("LevelEditor.PreviewMode.AndroidVulkanSM5.Enabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidVulkanSM5_Enabled_40x", Icon40x40)); + Set("LevelEditor.PreviewMode.AndroidVulkanSM5.Disabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_AndroidVulkanSM5_Disabled_40x", Icon40x40)); Set("LevelEditor.PreviewMode.iOS.Enabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_iOS_Enabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.iOS.Enabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_iOS_Enabled_40x", Icon20x20)); Set("LevelEditor.PreviewMode.iOS.Disabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_iOS_Disabled_40x", Icon40x40)); - Set("LevelEditor.PreviewMode.iOS.Disabled.Small", new IMAGE_BRUSH("Icons/icon_PreviewMode_iOS_Disabled_40x", Icon20x20)); + Set("LevelEditor.PreviewMode.iOSSM5.Enabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_iOSSM5_Enabled_40x", Icon40x40)); + Set("LevelEditor.PreviewMode.iOSSM5.Disabled", new IMAGE_BRUSH("Icons/icon_PreviewMode_iOSSM5_Disabled_40x", Icon40x40)); Set("LevelEditor.ViewOptions", new IMAGE_BRUSH("Icons/icon_view_40x", Icon40x40)); Set( "LevelEditor.ViewOptions.Small", new IMAGE_BRUSH( "Icons/icon_view_40x", Icon20x20 ) ); @@ -8094,6 +8080,19 @@ void FSlateEditorStyle::FStyle::SetupAutomationStyles() Set(PlatformInfo.GetIconStyleName(EPlatformIconSize::XLarge), new IMAGE_BRUSH(*PlatformInfo.GetIconPath(EPlatformIconSize::XLarge), Icon128x128)); } } + + for (auto It = PlatformInfo::GetPreviewPlatformMenuItems().CreateConstIterator(); It; ++It) + { + if(!It.Value().ActiveIconPath.IsEmpty()) + { + Set(It.Value().ActiveIconName, new PLATFORM_IMAGE_BRUSH(It.Value().ActiveIconPath, Icon40x40)); + } + if (!It.Value().InactiveIconPath.IsEmpty()) + { + Set(It.Value().InactiveIconName, new PLATFORM_IMAGE_BRUSH(It.Value().InactiveIconPath, Icon40x40)); + } + } + #endif Set("Launcher.NoHoverTableRow", FTableRowStyle(NormalTableRowStyle) diff --git a/Engine/Source/Editor/Experimental/EditorInteractiveToolsFramework/Private/EdModeInteractiveToolsContext.cpp b/Engine/Source/Editor/Experimental/EditorInteractiveToolsFramework/Private/EdModeInteractiveToolsContext.cpp index d09c1c2d7767..033717060dcc 100644 --- a/Engine/Source/Editor/Experimental/EditorInteractiveToolsFramework/Private/EdModeInteractiveToolsContext.cpp +++ b/Engine/Source/Editor/Experimental/EditorInteractiveToolsFramework/Private/EdModeInteractiveToolsContext.cpp @@ -78,7 +78,12 @@ public: // ViewTransform rotation is only initialized for perspective! if (CachedViewState.bIsOrthographic == false) { - CachedViewState.Orientation = ViewTransform.GetRotation().Quaternion(); + // if using Orbit camera, the rotation in the ViewTransform is not the current camera rotation, it + // is set to a different rotation based on the Orbit. So we have to convert back to camera rotation. + FRotator ViewRotation = (ViewportClient->bUsingOrbitCamera) ? + ViewTransform.ComputeOrbitMatrix().InverseFast().Rotator() : ViewTransform.GetRotation(); + + CachedViewState.Orientation = ViewRotation.Quaternion(); } else { @@ -662,7 +667,12 @@ public: // ViewTransform rotation is only initialized for perspective! if (ViewCameraState.bIsOrthographic == false) { - ViewCameraState.Orientation = ViewTransform.GetRotation().Quaternion(); + // if using Orbit camera, the rotation in the ViewTransform is not the current camera rotation, it + // is set to a different rotation based on the Orbit. So we have to convert back to camera rotation. + FRotator ViewRotation = (ViewportClient->bUsingOrbitCamera) ? + ViewTransform.ComputeOrbitMatrix().InverseFast().Rotator() : ViewTransform.GetRotation(); + + ViewCameraState.Orientation = ViewRotation.Quaternion(); } else { @@ -799,7 +809,7 @@ bool UEdModeInteractiveToolsContext::InputKey(FEditorViewportClient* ViewportCli { if (ToolManager->HasAnyActiveTool()) { - if (ToolManager->HasActiveTool(EToolSide::Mouse) && ToolManager->CanCancelActiveTool(EToolSide::Mouse)) + if (ToolManager->HasActiveTool(EToolSide::Mouse)) { DeactivateActiveTool(EToolSide::Mouse, EToolShutdownType::Cancel); } diff --git a/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.cpp b/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.cpp index 7cc4e0c926b3..4108dac15db7 100644 --- a/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.cpp +++ b/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.cpp @@ -3675,7 +3675,7 @@ void FEdModeFoliage::ForceRealTimeViewports(const bool bEnable) } else { - Viewport.RemoveRealtimeOverride(SystemDisplayName); + Viewport.RemoveRealtimeOverride(SystemDisplayName, false); } } } diff --git a/Engine/Source/Editor/FoliageEdit/Private/SFoliageEdit.cpp b/Engine/Source/Editor/FoliageEdit/Private/SFoliageEdit.cpp index aa6fd31e74d7..4215e5c1fc12 100644 --- a/Engine/Source/Editor/FoliageEdit/Private/SFoliageEdit.cpp +++ b/Engine/Source/Editor/FoliageEdit/Private/SFoliageEdit.cpp @@ -481,7 +481,7 @@ void SFoliageEdit::CustomizeToolBarPalette(FToolBarBuilder& ToolBarBuilder) FUIAction(FExecuteAction::CreateSP(this, &SFoliageEdit::OnSelectInvalidInstances)), NAME_None, LOCTEXT("FoliageSelectInvalid", "Invalid"), - LOCTEXT("FoliageSelectInvalidTooltip", "Select Invalid Foligae Instances"), + LOCTEXT("FoliageSelectInvalidTooltip", "Select Invalid Foliage Instances"), FSlateIcon(FEditorStyle::GetStyleSetName(), "FoliageEditMode.SelectInvalid") ); diff --git a/Engine/Source/Editor/GameProjectGeneration/Private/GameProjectUtils.cpp b/Engine/Source/Editor/GameProjectGeneration/Private/GameProjectUtils.cpp index 59fd23999f17..f07260a95673 100644 --- a/Engine/Source/Editor/GameProjectGeneration/Private/GameProjectUtils.cpp +++ b/Engine/Source/Editor/GameProjectGeneration/Private/GameProjectUtils.cpp @@ -95,6 +95,8 @@ TWeakPtr GameProjectUtils::WarningProjectNameNotification = N bool GameProjectUtils::bUseAudioMixerForAllPlatforms = false; +constexpr const TCHAR GameProjectUtils::IncludePathFormatString[]; + struct FAudioDefaultPlatformSettings { FString Name; @@ -1919,6 +1921,25 @@ FString GameProjectUtils::GetTemplateDefsFilename() return TEXT("TemplateDefs.ini"); } +FString GameProjectUtils::GetIncludePathForFile(const FString& InFullFilePath, const FString& ModuleRootPath) +{ + FString RelativeHeaderPath = FPaths::ChangeExtension(InFullFilePath, "h"); + + const FString PublicString = ModuleRootPath / "Public" / ""; + if (RelativeHeaderPath.StartsWith(PublicString)) + { + return RelativeHeaderPath.RightChop(PublicString.Len()); + } + + const FString PrivateString = ModuleRootPath / "Private" / ""; + if (RelativeHeaderPath.StartsWith(PrivateString)) + { + return RelativeHeaderPath.RightChop(PrivateString.Len()); + } + + return RelativeHeaderPath.RightChop(ModuleRootPath.Len()); +} + bool GameProjectUtils::NameContainsOnlyLegalCharacters(const FString& TestName, FString& OutIllegalCharacters) { bool bContainsIllegalCharacters = false; @@ -3051,7 +3072,8 @@ FString GameProjectUtils::MakeIncludeList(const TArray& InList) for ( auto ListIt = InList.CreateConstIterator(); ListIt; ++ListIt ) { - ReturnString += FString::Printf( TEXT("#include \"%s\"") LINE_TERMINATOR, **ListIt); + ReturnString += FString::Printf(IncludePathFormatString, **ListIt); + ReturnString += LINE_TERMINATOR; } return ReturnString; @@ -3171,7 +3193,7 @@ bool GameProjectUtils::GenerateClassHeaderFile(const FString& NewHeaderFileName, FString BaseClassIncludePath; if(ParentClassInfo.GetIncludePath(BaseClassIncludePath)) { - BaseClassIncludeDirective = FString::Printf(TEXT("#include \"%s\""), *BaseClassIncludePath); + BaseClassIncludeDirective = FString::Printf(IncludePathFormatString, *BaseClassIncludePath); } FString ModuleAPIMacro; @@ -3320,17 +3342,6 @@ bool GameProjectUtils::GenerateClassCPPFile(const FString& NewCPPFileName, const return false; } - FString AdditionalIncludesStr; - for (int32 IncludeIdx = 0; IncludeIdx < AdditionalIncludes.Num(); ++IncludeIdx) - { - if (IncludeIdx > 0) - { - AdditionalIncludesStr += LINE_TERMINATOR; - } - - AdditionalIncludesStr += FString::Printf(TEXT("#include \"%s\""), *AdditionalIncludes[IncludeIdx]); - } - FString PropertyOverridesStr; for ( int32 OverrideIdx = 0; OverrideIdx < PropertyOverrides.Num(); ++OverrideIdx ) { @@ -3350,7 +3361,7 @@ bool GameProjectUtils::GenerateClassCPPFile(const FString& NewCPPFileName, const const FString ModuleIncludePath = DetermineModuleIncludePath(ModuleInfo, NewCPPFileName); if (ModuleIncludePath.Len() > 0) { - PchIncludeDirective = FString::Printf(TEXT("#include \"%s\""), *ModuleIncludePath); + PchIncludeDirective = FString::Printf(IncludePathFormatString, *ModuleIncludePath); } } @@ -3370,10 +3381,24 @@ bool GameProjectUtils::GenerateClassCPPFile(const FString& NewCPPFileName, const FinalOutput = FinalOutput.Replace(TEXT("%MODULE_NAME%"), *ModuleInfo.ModuleName, ESearchCase::CaseSensitive); FinalOutput = FinalOutput.Replace(TEXT("%PCH_INCLUDE_DIRECTIVE%"), *PchIncludeDirective, ESearchCase::CaseSensitive); + // Fixup the header file include for this cpp file + { + const FString RelativeHeaderIncludePath = GetIncludePathForFile(NewCPPFileName, ModuleInfo.ModuleSourcePath); + + // First make sure we remove any potentially incorrect, legacy paths generated from some version of #include "%UNPREFIXED_CLASS_NAME%.h" + // This didn't take into account potential subfolders for the created class + const FString LegacyHeaderInclude = FString::Printf(IncludePathFormatString, *FPaths::GetCleanFilename(RelativeHeaderIncludePath)); + FinalOutput = FinalOutput.Replace(*LegacyHeaderInclude, TEXT(""), ESearchCase::CaseSensitive); + + // Now add the correct include directive which may include a subfolder. + const FString HeaderIncludeDirective = FString::Printf(IncludePathFormatString, *RelativeHeaderIncludePath); + FinalOutput = FinalOutput.Replace(TEXT("%MY_HEADER_INCLUDE_DIRECTIVE%"), *HeaderIncludeDirective, ESearchCase::CaseSensitive); + } + // Special case where where the wildcard ends with a new line const bool bLeadingTab = false; const bool bTrailingNewLine = true; - FinalOutput = ReplaceWildcard(FinalOutput, TEXT("%ADDITIONAL_INCLUDE_DIRECTIVES%"), *AdditionalIncludesStr, bLeadingTab, bTrailingNewLine); + FinalOutput = ReplaceWildcard(FinalOutput, TEXT("%ADDITIONAL_INCLUDE_DIRECTIVES%"), *MakeIncludeList(AdditionalIncludes), bLeadingTab, bTrailingNewLine); FinalOutput = ReplaceWildcard(FinalOutput, TEXT("%EVENTUAL_CONSTRUCTOR_DEFINITION%"), *EventualConstructorDefinition, bLeadingTab, bTrailingNewLine); FinalOutput = ReplaceWildcard(FinalOutput, TEXT("%ADDITIONAL_MEMBER_DEFINITIONS%"), *AdditionalMemberDefinitions, bLeadingTab, bTrailingNewLine); diff --git a/Engine/Source/Editor/GameProjectGeneration/Public/GameProjectUtils.h b/Engine/Source/Editor/GameProjectGeneration/Public/GameProjectUtils.h index a69b3696b850..c01382b67dfa 100644 --- a/Engine/Source/Editor/GameProjectGeneration/Public/GameProjectUtils.h +++ b/Engine/Source/Editor/GameProjectGeneration/Public/GameProjectUtils.h @@ -318,6 +318,9 @@ private: /** Returns the template defs ini filename */ static FString GetTemplateDefsFilename(); + /** Returns the include header path for a given fully specified, normalized file path */ + static FString GetIncludePathForFile(const FString& InFullFilePath, const FString& ModuleRootPath); + /** Checks the name for an underscore and the existence of XB1 XDK */ static bool NameContainsUnderscoreAndXB1Installed(const FString& TestName); @@ -523,4 +526,6 @@ private: // Whether we should use AudioMixer for all platforms: static bool bUseAudioMixerForAllPlatforms; + + constexpr static const TCHAR IncludePathFormatString[] = TEXT("#include \"%s\""); }; diff --git a/Engine/Source/Editor/GraphEditor/Private/GraphEditorActions.cpp b/Engine/Source/Editor/GraphEditor/Private/GraphEditorActions.cpp index aec75e74a68a..a52e0ee2a14c 100644 --- a/Engine/Source/Editor/GraphEditor/Private/GraphEditorActions.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/GraphEditorActions.cpp @@ -67,7 +67,7 @@ void FGraphEditorCommandsImpl::RegisterCommands() UI_COMMAND( SelectReferenceInLevel, "Find Actor in Level", "Select the actor referenced by this node in the level", EUserInterfaceActionType::Button, FInputChord() ) UI_COMMAND( AssignReferencedActor, "Assign selected Actor", "Assign the selected actor to be this node's referenced object", EUserInterfaceActionType::Button, FInputChord() ) UI_COMMAND( FindReferences, "Find References", "Find references of this item", EUserInterfaceActionType::Button, FInputChord(EKeys::F, EModifierKey::Shift | EModifierKey::Alt) ) - UI_COMMAND( FindAndReplaceReferences, "Find and Replace References", "Brings up a window to help find and replace all instances of this item", EUserInterfaceActionType::Button, FInputChord() ) + UI_COMMAND( FindAndReplaceReferences, "Replace References", "Brings up a window to help find and replace all instances of this item", EUserInterfaceActionType::Button, FInputChord() ) UI_COMMAND( GoToDefinition, "Goto Definition", "Jumps to the defintion of the selected node if available, e.g., C++ code for a native function or the graph for a Blueprint function.", EUserInterfaceActionType::Button, FInputChord(EKeys::G, EModifierKey::Alt) ) diff --git a/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp b/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp index da88aaada6e7..d62c0d15ff7e 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp @@ -112,7 +112,16 @@ void SCommentBubble::Tick( const FGeometry& AllottedGeometry, const double InCur // Toggle the comment on/off, provided it the parent isn't a comment node if( !bInvertLODCulling ) { - OnCommentBubbleToggle( CachedComment.IsEmpty() ? ECheckBoxState::Unchecked : ECheckBoxState::Checked ); + // If the comment visibility isn't correct, toggle it. + if (CachedComment.IsEmpty() == GraphNode->bCommentBubbleVisible) + { + OnCommentBubbleToggle(CachedComment.IsEmpty() ? ECheckBoxState::Unchecked : ECheckBoxState::Checked); + } + else + { + // we just need to refresh the widget's visibility, not the graph node + UpdateBubble(); + } } } } diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.cpp index 59e61016652e..bbbb6fcad743 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.cpp @@ -202,7 +202,7 @@ void SGraphEditorImpl::GetPinContextMenuActionsForSchema(UToolMenu* InMenu) cons ); } - auto GetMenuEntryForPin = [](const UEdGraphPin* TargetPin, const FUIAction& Action, const FText& SingleDescFormat, const FText& MultiDescFormat, TMap< FString, uint32 >& LinkTitleCount) + auto GetMenuEntryForPin = [](const UEdGraphPin* TargetPin, const FToolUIAction& Action, const FText& SingleDescFormat, const FText& MultiDescFormat, TMap< FString, uint32 >& LinkTitleCount) { FText Title = TargetPin->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView); FString TitleString = Title.ToString(); @@ -260,136 +260,125 @@ void SGraphEditorImpl::GetPinContextMenuActionsForSchema(UToolMenu* InMenu) cons } // Break Specific Links - InMenu->AddDynamicSection("BreakLinkTo", FNewToolMenuDelegate::CreateLambda([GetMenuEntryForPin](UToolMenu* InMenu) { - UGraphNodeContextMenuContext* NodeContext = InMenu->FindContext(); - FToolMenuSection& Section = InMenu->FindOrAddSection("EdGraphSchemaPinActions"); + FToolUIAction BreakLinksMenuVisibility; + BreakLinksMenuVisibility.IsActionVisibleDelegate = FToolMenuIsActionButtonVisible::CreateSP(this, &SGraphEditorImpl::IsBreakPinLinksVisible); - const int32 LinkedToCount = NodeContext->Pin->LinkedTo.Num(); - if (LinkedToCount > 1) - { - FText SingleDescFormat = LOCTEXT("BreakDesc", "Break link to {NodeTitle}"); - FText MultiDescFormat = LOCTEXT("BreakDescMulti", "Break link to {NodeTitle} ({NumberOfNodes})"); - Section.AddSubMenu( - "BreakLinkTo", - LOCTEXT("BreakLinkTo", "Break Link To..."), - LOCTEXT("BreakSpecificLinks", "Break a specific link..."), - FNewToolMenuDelegate::CreateLambda([NodeContext, GetMenuEntryForPin, SingleDescFormat, MultiDescFormat](UToolMenu* InToolMenu) + Section.AddSubMenu("BreakLinkTo", + LOCTEXT("BreakLinkTo", "Break Link To..."), + LOCTEXT("BreakSpecificLinks", "Break a specific link..."), + FNewToolMenuDelegate::CreateLambda([this, GetMenuEntryForPin](UToolMenu* InToolMenu) + { + UGraphNodeContextMenuContext* NodeContext = InToolMenu->FindContext(); + if (NodeContext && NodeContext->Pin) { + FText SingleDescFormat = LOCTEXT("BreakDesc", "Break link to {NodeTitle}"); + FText MultiDescFormat = LOCTEXT("BreakDescMulti", "Break link to {NodeTitle} ({NumberOfNodes})"); + TMap< FString, uint32 > LinkTitleCount; for (UEdGraphPin* TargetPin : NodeContext->Pin->LinkedTo) { - FUIAction BreakSpecificLinkAction; - BreakSpecificLinkAction.ExecuteAction = FExecuteAction::CreateLambda([NodeContext, TargetPin]() + FToolUIAction BreakSpecificLinkAction; + BreakSpecificLinkAction.ExecuteAction = FToolMenuExecuteAction::CreateLambda([this, TargetPin](const FToolMenuContext& InMenuContext) { + UGraphNodeContextMenuContext* NodeContext = InMenuContext.FindContext(); + check(NodeContext && NodeContext->Pin); NodeContext->Pin->GetSchema()->BreakSinglePinLink(const_cast(NodeContext->Pin), TargetPin); }); - BreakSpecificLinkAction.IsActionVisibleDelegate = FIsActionButtonVisible::CreateLambda([NodeContext, TargetPin]() - { - return !NodeContext->bIsDebugging; - }); InToolMenu->AddMenuEntry(NAME_None, GetMenuEntryForPin(TargetPin, BreakSpecificLinkAction, SingleDescFormat, MultiDescFormat, LinkTitleCount)); } } - )); - } - })); + }), + BreakLinksMenuVisibility, + EUserInterfaceActionType::Button); + } - InMenu->AddDynamicSection("JumpToConnection", FNewToolMenuDelegate::CreateLambda([this, GetMenuEntryForPin](UToolMenu* InToolMenu) + FToolUIAction PinActionSubMenuVisibiliity; + PinActionSubMenuVisibiliity.IsActionVisibleDelegate = FToolMenuIsActionButtonVisible::CreateSP(this, &SGraphEditorImpl::HasAnyLinkedPins); + + // Jump to specific connections { - UGraphNodeContextMenuContext* NodeContext = InToolMenu->FindContext(); - FToolMenuSection& Section = InToolMenu->FindOrAddSection("EdGraphSchemaPinActions"); - - if (NodeContext && NodeContext->Pin) - { - const int32 LinkedToCount = NodeContext->Pin->LinkedTo.Num(); - if (LinkedToCount > 0) + Section.AddSubMenu("JumpToConnection", + LOCTEXT("JumpToConnection", "Jump To Connection..."), + LOCTEXT("JumpToSpecificConnection", "Jump to specific connection..."), + FNewToolMenuDelegate::CreateLambda([this, GetMenuEntryForPin](UToolMenu* InToolMenu) { - Section.AddSubMenu( - "JumpToConnection", - LOCTEXT("JumpToConnection", "Jump To Connection..."), - LOCTEXT("JumpToSpecificConnection", "Jump to specific connection..."), - FNewToolMenuDelegate::CreateLambda([this, NodeContext, GetMenuEntryForPin](UToolMenu* InToolMenu) - { - FText SingleDescFormat = LOCTEXT("JumpDesc", "Jump to {NodeTitle}"); - FText MultiDescFormat = LOCTEXT("JumpDescMulti", "Jump to {NodeTitle} ({NumberOfNodes})"); - TMap< FString, uint32 > LinkTitleCount; - for (UEdGraphPin* TargetPin : NodeContext->Pin->LinkedTo) - { - FUIAction JumpToConnectionAction; - JumpToConnectionAction.ExecuteAction = FExecuteAction::CreateLambda([this, TargetPin, GetMenuEntryForPin]() - { - const SGraphEditor* ThisAsGraphEditor = this; - const_cast(ThisAsGraphEditor)->JumpToPin(TargetPin); - }); + UGraphNodeContextMenuContext* NodeContext = InToolMenu->FindContext(); + check(NodeContext&& NodeContext->Pin); - InToolMenu->AddMenuEntry(NAME_None, GetMenuEntryForPin(TargetPin, JumpToConnectionAction, SingleDescFormat, MultiDescFormat, LinkTitleCount)); - } - } - )); - } - } - })); + const SGraphEditor* ThisAsGraphEditor = this; + FText SingleDescFormat = LOCTEXT("JumpDesc", "Jump to {NodeTitle}"); + FText MultiDescFormat = LOCTEXT("JumpDescMulti", "Jump to {NodeTitle} ({NumberOfNodes})"); + TMap< FString, uint32 > LinkTitleCount; + for (UEdGraphPin* TargetPin : NodeContext->Pin->LinkedTo) + { + FToolUIAction JumpToConnectionAction; + JumpToConnectionAction.ExecuteAction = FToolMenuExecuteAction::CreateLambda([ThisAsGraphEditor, TargetPin](const FToolMenuContext& InMenuContext) + { + const_cast(ThisAsGraphEditor)->JumpToPin(TargetPin); + }); + + InToolMenu->AddMenuEntry(NAME_None, GetMenuEntryForPin(TargetPin, JumpToConnectionAction, SingleDescFormat, MultiDescFormat, LinkTitleCount)); + } + }), + PinActionSubMenuVisibiliity, + EUserInterfaceActionType::Button); + } // Straighten Connections menu items { - FToolUIAction StraightenConnectionsAction; - StraightenConnectionsAction.ExecuteAction = FToolMenuExecuteAction::CreateLambda([this](const FToolMenuContext& Context) - { - UGraphNodeContextMenuContext* NodeContext = Context.FindContext(); - StraightenConnections(const_cast(NodeContext->Pin), nullptr); - }); - StraightenConnectionsAction.IsActionVisibleDelegate = FToolMenuIsActionButtonVisible::CreateLambda([](const FToolMenuContext& Context) - { - UGraphNodeContextMenuContext* NodeContext = Context.FindContext(); - return (NodeContext->Pin->LinkedTo.Num() > 0); - }); - - Section.AddMenuEntry( - NAME_None, - LOCTEXT("StraightenAllConnections", "Straighten All Pin Connections"), - LOCTEXT("StraightenAllConnectionsTooltip", "Straightens all connected pins"), - FSlateIcon(NAME_None, NAME_None, NAME_None), - StraightenConnectionsAction - ); - } - - InMenu->AddDynamicSection("StraightenPinConnection", FNewToolMenuDelegate::CreateLambda([this, GetMenuEntryForPin](UToolMenu* InToolMenu) - { - UGraphNodeContextMenuContext* NodeContext = InToolMenu->FindContext(); - FToolMenuSection& Section = InToolMenu->FindOrAddSection("EdGraphSchemaPinActions"); - - if (NodeContext && NodeContext->Pin) - { - const int32 LinkedToCount = NodeContext->Pin->LinkedTo.Num(); - if (LinkedToCount > 0) + Section.AddSubMenu("StraightenPinConnection", + LOCTEXT("StraightenConnection", "Straighten Connection..."), + LOCTEXT("StraightenSpecificConnection", "Straighten specific connection..."), + FNewToolMenuDelegate::CreateLambda([this, GetMenuEntryForPin](UToolMenu* InToolMenu) { - Section.AddSubMenu( - "StriaghtenConnection", - LOCTEXT("StraightenConnection", "Straighten Connection..."), - LOCTEXT("StraightenSpecificConnection", "Straighten specific connection..."), - FNewToolMenuDelegate::CreateLambda([this, NodeContext, GetMenuEntryForPin](UToolMenu* InToolMenu) + const UGraphNodeContextMenuContext* NodeContext = InToolMenu->FindContext(); + + // Add straighten all connected pins + { + FToolUIAction StraightenConnectionsAction; + StraightenConnectionsAction.ExecuteAction = FToolMenuExecuteAction::CreateLambda([this](const FToolMenuContext& Context) { - FText SingleDescFormat = LOCTEXT("StraightenDesc", "Straighten Connection to {NodeTitle}"); - FText MultiDescFormat = LOCTEXT("StraightenDescMulti", "Straigten Connection to {NodeTitle} ({NumberOfNodes})"); - TMap< FString, uint32 > LinkTitleCount; - for (UEdGraphPin* TargetPin : NodeContext->Pin->LinkedTo) + UGraphNodeContextMenuContext* NodeContext = Context.FindContext(); + StraightenConnections(const_cast(NodeContext->Pin), nullptr); + }); + StraightenConnectionsAction.IsActionVisibleDelegate = FToolMenuIsActionButtonVisible::CreateSP(this, &SGraphEditorImpl::HasAnyLinkedPins); + + InToolMenu->AddMenuEntry( + NAME_None, + FToolMenuEntry::InitMenuEntry(NAME_None, + LOCTEXT("StraightenAllConnections", "Straighten All Pin Connections"), + LOCTEXT("StraightenAllConnectionsTooltip", "Straightens all connected pins"), + FGraphEditorCommands::Get().StraightenConnections->GetIcon(), + StraightenConnectionsAction) + ); + } + + check(NodeContext && NodeContext->Pin); + + // Add individual pin connections + FText SingleDescFormat = LOCTEXT("StraightenDesc", "Straighten Connection to {NodeTitle}"); + FText MultiDescFormat = LOCTEXT("StraightenDescMulti", "Straigten Connection to {NodeTitle} ({NumberOfNodes})"); + TMap< FString, uint32 > LinkTitleCount; + for (UEdGraphPin* TargetPin : NodeContext->Pin->LinkedTo) + { + FToolUIAction StraightenConnectionAction; + StraightenConnectionAction.ExecuteAction = FToolMenuExecuteAction::CreateLambda([this, TargetPin](const FToolMenuContext& InMenuContext) { - FUIAction StraightenConnectionAction; - - StraightenConnectionAction.ExecuteAction = FExecuteAction::CreateLambda([this, TargetPin, NodeContext]() + UGraphNodeContextMenuContext* NodeContext = InMenuContext.FindContext(); + if (NodeContext && NodeContext->Pin) { - StraightenConnections(const_cast(NodeContext->Pin), const_cast(TargetPin)); - }); + this->StraightenConnections(const_cast(NodeContext->Pin), const_cast(TargetPin)); + } + }); - InToolMenu->AddMenuEntry(NAME_None, GetMenuEntryForPin(TargetPin, StraightenConnectionAction, SingleDescFormat, MultiDescFormat, LinkTitleCount)); - } - } - )); - } - } - })); + InToolMenu->AddMenuEntry(NAME_None, GetMenuEntryForPin(TargetPin, StraightenConnectionAction, SingleDescFormat, MultiDescFormat, LinkTitleCount)); + } + }), + PinActionSubMenuVisibiliity, + EUserInterfaceActionType::Button); + } // Add any additional menu options from the asset toolkit that owns this graph editor UAssetEditorToolkitMenuContext* AssetToolkitContext = InMenu->FindContext(); @@ -420,6 +409,17 @@ bool SGraphEditorImpl::IsBreakPinLinksVisible(const FToolMenuContext& InContext) return false; } +bool SGraphEditorImpl::HasAnyLinkedPins(const FToolMenuContext& InContext) const +{ + UGraphNodeContextMenuContext* NodeContext = InContext.FindContext(); + if (NodeContext && NodeContext->Pin) + { + return (NodeContext->Pin->LinkedTo.Num() > 0); + } + + return false; +} + void SGraphEditorImpl::ExecuteSelectConnectedNodesFromPin(const FToolMenuContext& InContext) const { UGraphNodeContextMenuContext* NodeContext = InContext.FindContext(); @@ -612,6 +612,10 @@ void SGraphEditorImpl::Construct( const FArguments& InArgs ) FCanExecuteAction::CreateSP(this, &SGraphEditorImpl::CanSummonCreateNodeMenu) ); + Commands->MapAction(FGraphEditorCommands::Get().StraightenConnections, + FExecuteAction::CreateSP(this, &SGraphEditorImpl::StraightenConnections) + ); + // Append any additional commands that a consumer of GraphEditor wants us to be aware of. const TSharedPtr& AdditionalCommands = InArgs._AdditionalCommands; if ( AdditionalCommands.IsValid() ) @@ -984,10 +988,12 @@ void SGraphEditorImpl::RegisterContextMenu(const UEdGraphSchema* Schema, FToolMe ToolMenus->RegisterMenu(CommonRootMenuName); } + bool bDidRegisterGraphSchemaMenu = false; const FName EdGraphSchemaContextMenuName = UEdGraphSchema::GetContextMenuName(UEdGraphSchema::StaticClass()); if (!ToolMenus->IsMenuRegistered(EdGraphSchemaContextMenuName)) { ToolMenus->RegisterMenu(EdGraphSchemaContextMenuName); + bDidRegisterGraphSchemaMenu = true; } // Menus for subclasses of EdGraphSchema @@ -1020,6 +1026,7 @@ void SGraphEditorImpl::RegisterContextMenu(const UEdGraphSchema* Schema, FToolMe } // Now register node menus, which will belong to their schemas + bool bDidRegisterNodeMenu = false; if (Context->Node) { for (UClass* CurrentClass = Context->Node->GetClass(); CurrentClass && CurrentClass->IsChildOf(UEdGraphNode::StaticClass()); CurrentClass = CurrentClass->GetSuperClass()) @@ -1037,16 +1044,8 @@ void SGraphEditorImpl::RegisterContextMenu(const UEdGraphSchema* Schema, FToolMe ParentNameToUse = EdGraphSchemaContextMenuName; } - UToolMenu* NodeMenu = ToolMenus->RegisterMenu(CheckMenuName, ParentNameToUse); - - NodeMenu->AddDynamicSection("GetNodeContextMenuActions", FNewToolMenuDelegate::CreateLambda([this](UToolMenu* InMenu) - { - UGraphNodeContextMenuContext* Context = InMenu->FindContext(); - if (Context && Context->Node) - { - Context->Node->GetNodeContextMenuActions(InMenu, Context); - } - })); + ToolMenus->RegisterMenu(CheckMenuName, ParentNameToUse); + bDidRegisterNodeMenu = true; } if (CheckParentName == NAME_None) @@ -1057,29 +1056,46 @@ void SGraphEditorImpl::RegisterContextMenu(const UEdGraphSchema* Schema, FToolMe } // Now that all the possible sections have been registered, we can add the dynamic section for the custom schema node actions to override - UToolMenu* Menu = ToolMenus->FindMenu(EdGraphSchemaContextMenuName); + if (bDidRegisterGraphSchemaMenu) + { + UToolMenu* Menu = ToolMenus->FindMenu(EdGraphSchemaContextMenuName); - Menu->AddDynamicSection("EdGraphSchemaPinActions", FNewToolMenuDelegate::CreateLambda([this](UToolMenu* InMenu) - { - UGraphNodeContextMenuContext* NodeContext = InMenu->FindContext(); - if (NodeContext && NodeContext->Pin) + Menu->AddDynamicSection("EdGraphSchemaPinActions", FNewToolMenuDelegate::CreateLambda([this](UToolMenu* InMenu) { - GetPinContextMenuActionsForSchema(InMenu); + UGraphNodeContextMenuContext* NodeContext = InMenu->FindContext(); + if (NodeContext && NodeContext->Pin) + { + GetPinContextMenuActionsForSchema(InMenu); + } + })); + + Menu->AddDynamicSection("GetContextMenuActions", FNewToolMenuDelegate::CreateLambda([this](UToolMenu* InMenu) + { + if (UGraphNodeContextMenuContext* ContextObject = InMenu->FindContext()) + { + if (const UEdGraphSchema* GraphSchema = ContextObject->Graph->GetSchema()) + { + GraphSchema->GetContextMenuActions(InMenu, ContextObject); + } } })); - Menu->AddDynamicSection("GetContextMenuActions", FNewToolMenuDelegate::CreateLambda([this](UToolMenu* InMenu) - { - if (UGraphNodeContextMenuContext* ContextObject = InMenu->FindContext()) - { - if (const UEdGraphSchema* GraphSchema = ContextObject->Graph->GetSchema()) - { - GraphSchema->GetContextMenuActions(InMenu, ContextObject); - } - } - })); + Menu->AddDynamicSection("EdGraphSchema", FNewToolMenuDelegate::CreateStatic(&SGraphEditorImpl::AddContextMenuCommentSection)); + } - Menu->AddDynamicSection("EdGraphSchema", FNewToolMenuDelegate::CreateStatic(&SGraphEditorImpl::AddContextMenuCommentSection)); + if (bDidRegisterNodeMenu) + { + UToolMenu* Menu = ToolMenus->FindMenu(GetNodeContextMenuName(Context->Node->GetClass())); + + Menu->AddDynamicSection("GetNodeContextMenuActions", FNewToolMenuDelegate::CreateLambda([this](UToolMenu* InMenu) + { + UGraphNodeContextMenuContext* Context = InMenu->FindContext(); + if (Context && Context->Node) + { + Context->Node->GetNodeContextMenuActions(InMenu, Context); + } + })); + } } UToolMenu* SGraphEditorImpl::GenerateContextMenu(const UEdGraphSchema* Schema, FToolMenuContext& MenuContext) const diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.h b/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.h index ed12415c9460..b2474fa555ce 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.h +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.h @@ -349,6 +349,7 @@ private: void GetPinContextMenuActionsForSchema(UToolMenu* InMenu) const; void ExecuteBreakPinLinks(const FToolMenuContext& InContext) const; bool IsBreakPinLinksVisible(const FToolMenuContext& InContext) const; + bool HasAnyLinkedPins(const FToolMenuContext& InContext) const; void ExecuteSelectConnectedNodesFromPin(const FToolMenuContext& InContext) const; void SelectAllNodesInDirection(const UEdGraphPin* InGraphPin) const; diff --git a/Engine/Source/Editor/Kismet/Private/BPFunctionClipboardData.cpp b/Engine/Source/Editor/Kismet/Private/BPFunctionClipboardData.cpp new file mode 100644 index 000000000000..e8aa74d470fe --- /dev/null +++ b/Engine/Source/Editor/Kismet/Private/BPFunctionClipboardData.cpp @@ -0,0 +1,91 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "BPFunctionClipboardData.h" +#include "K2Node_FunctionEntry.h" +#include "EdGraph/EdGraph.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "EdGraphUtilities.h" + +FBPFunctionClipboardData::FBPFunctionClipboardData(const UEdGraph* InFuncGraph) +{ + SetFromGraph(InFuncGraph); +} + +bool FBPFunctionClipboardData::IsValid() const +{ + // the only way to set these is by populating this struct with a graph or using *mostly* valid serialized data + return FuncName != NAME_None && !NodesString.IsEmpty(); +} + +void FBPFunctionClipboardData::SetFromGraph(const UEdGraph* InFuncGraph) +{ + if (InFuncGraph) + { + FuncName = InFuncGraph->GetFName(); + + // TODO: Make this look nicer with an overload of ExportNodesToText that takes a TArray? + // construct a TSet of the nodes in the graph for ExportNodesToText + TSet Nodes; + Nodes.Reserve(InFuncGraph->Nodes.Num()); + for (UEdGraphNode* Node : InFuncGraph->Nodes) + { + Nodes.Add(Node); + } + FEdGraphUtilities::ExportNodesToText(Nodes, NodesString); + } +} + +UEdGraph* FBPFunctionClipboardData::CreateAndPopulateGraph(UBlueprint* InBlueprint, TSubclassOf InSchema) +{ + if (InBlueprint && IsValid()) + { + FName GraphName = FBlueprintEditorUtils::FindUniqueKismetName(InBlueprint, FuncName.ToString()); + UEdGraph* Graph = FBlueprintEditorUtils::CreateNewGraph(InBlueprint, GraphName, UEdGraph::StaticClass(), InSchema); + + if (Graph) + { + InBlueprint->FunctionGraphs.Add(Graph); + PopulateGraph(Graph); + + TArray Entry; + Graph->GetNodesOfClass(Entry); + if (ensure(Entry.Num() == 1)) + { + // Discard category + Entry[0]->MetaData.Category = FText::FromString(TEXT("Default")); + + // Add necessary function flags + int32 AdditionalFunctionFlags = (FUNC_BlueprintEvent | FUNC_BlueprintCallable); + if ((Entry[0]->GetExtraFlags() & FUNC_AccessSpecifiers) == FUNC_None) + { + AdditionalFunctionFlags |= FUNC_Public; + } + Entry[0]->AddExtraFlags(AdditionalFunctionFlags); + + Entry[0]->FunctionReference.SetExternalMember(Graph->GetFName(), nullptr); + + const UEdGraphSchema_K2* K2Schema = Cast(Graph->GetSchema()); + if (K2Schema) + { + // Mark graph as editable in case this came from a UserConstructionScript + K2Schema->MarkFunctionEntryAsEditable(Graph, true); + } + } + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint); + + return Graph; + } + } + + return nullptr; +} + +void FBPFunctionClipboardData::PopulateGraph(UEdGraph* InFuncGraph) +{ + if (FEdGraphUtilities::CanImportNodesFromText(InFuncGraph, NodesString)) + { + TSet ImportedNodes; + FEdGraphUtilities::ImportNodesFromText(InFuncGraph, NodesString, ImportedNodes); + } +} diff --git a/Engine/Source/Editor/Kismet/Private/BPFunctionClipboardData.h b/Engine/Source/Editor/Kismet/Private/BPFunctionClipboardData.h new file mode 100644 index 000000000000..193f9995d9fb --- /dev/null +++ b/Engine/Source/Editor/Kismet/Private/BPFunctionClipboardData.h @@ -0,0 +1,50 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EdGraph/EdGraphPin.h" +#include "BPFunctionClipboardData.generated.h" + +struct FBPVariableDescription; + +/** A helper struct for copying a Blueprint Function to the clipboard */ +USTRUCT() +struct FBPFunctionClipboardData +{ + GENERATED_BODY() + + /** Default constructor */ + FBPFunctionClipboardData() = default; + + /** Constructs a FBPFunctionClipboardData from a graph */ + FBPFunctionClipboardData(const UEdGraph* InFuncGraph); + + /** Checks if the data is valid for configuring a graph */ + bool IsValid() const; + + /** Populates the struct based on a graph */ + void SetFromGraph(const UEdGraph* InFuncGraph); + + /** + * Creates and configures a new graph with data from this struct + * + * @param InBlueprint The Blueprint to add the new graph to + * @param InSchema The schema to use for the new graph + * @return The new Graph, properly configured and populated if data is valid, or nullptr if data is invalid. + */ + UEdGraph* CreateAndPopulateGraph(UBlueprint* InBlueprint, TSubclassOf InSchema); + +private: + /** Configures a graph with the nodes and settings */ + void PopulateGraph(UEdGraph* InFuncGraph); + +private: + /** Name of the Function */ + UPROPERTY() + FName FuncName; + + /** A string containing the exported text for the nodes in this function */ + UPROPERTY() + FString NodesString; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp index 740b85987509..e04a9344e909 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp @@ -2740,7 +2740,6 @@ UClass* FBlueprintCompilationManagerImpl::FastGenerateSkeletonClass(UBlueprint* { CompilerContext.NewClass = Ret; TGuardValue GuardAssignDelegateSignatureFunction( CompilerContext.bAssignDelegateSignatureFunction, true); - TGuardValue GuardGenerateLinkedAnimGraphVariables( CompilerContext.bGenerateLinkedAnimGraphVariables, true); CompilerContext.CreateClassVariablesFromBlueprint(); CompilerContext.NewClass = OriginalNewClass; } diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp index 1b4bcb8c920a..2f0eb6c4070f 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp @@ -77,6 +77,7 @@ #include "UObject/TextProperty.h" #include "Subsystems/AssetEditorSubsystem.h" +#include "SupportedRangeTypes.h" // StructsSupportingRangeVisibility #define LOCTEXT_NAMESPACE "BlueprintDetailsCustomization" @@ -98,7 +99,10 @@ void FBlueprintDetails::AddEventsCategory(IDetailLayoutBuilder& DetailBuilder, F // Check for Ed Graph vars that can generate events if ( PropertyClass && BlueprintObj->AllowsDynamicBinding() ) { - if ( FBlueprintEditorUtils::CanClassGenerateEvents(PropertyClass) ) + // If the object property can't be resolved for the property, than we can't use it's events. + FObjectProperty* VariableProperty = FindFProperty(BlueprintObj->SkeletonGeneratedClass, PropertyName); + + if ( FBlueprintEditorUtils::CanClassGenerateEvents(PropertyClass) && VariableProperty ) { for ( TFieldIterator PropertyIt(PropertyClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt ) { @@ -358,7 +362,7 @@ void FBlueprintVarActionDetails::CustomizeDetails( IDetailLayoutBuilder& DetailL [ SNew(SEditableTextBox) .Text( this, &FBlueprintVarActionDetails::OnGetTooltipText ) - .ToolTip(ToolTipTooltip) + .ToolTipText( this, &FBlueprintVarActionDetails::OnGetTooltipText ) .OnTextCommitted( this, &FBlueprintVarActionDetails::OnTooltipTextCommitted, CachedVariableName ) .IsEnabled(IsVariableInBlueprint()) .Font( DetailFontInfo ) @@ -2111,10 +2115,16 @@ EVisibility FBlueprintVarActionDetails::RangeVisibility() const if (VariableProperty) { const bool bIsInteger = VariableProperty->IsA(FIntProperty::StaticClass()); - const bool bIsNonEnumByte = (VariableProperty->IsA(FByteProperty::StaticClass()) && CastField(VariableProperty)->Enum == NULL); + const bool bIsNonEnumByte = (VariableProperty->IsA(FByteProperty::StaticClass()) && CastField(VariableProperty)->Enum == nullptr); const bool bIsFloat = VariableProperty->IsA(FFloatProperty::StaticClass()); - if (IsABlueprintVariable(VariableProperty) && (bIsInteger || bIsNonEnumByte || bIsFloat)) + // If this is a struct property than we must check the name of the struct it points to, so we can check + // if it supports the editing of the UIMin/UIMax metadata + const FStructProperty* StructProp = CastField(VariableProperty); + const UStruct* InnerStruct = StructProp ? StructProp->Struct : nullptr; + const bool bIsSupportedStruct = InnerStruct ? RangeVisibilityUtils::StructsSupportingRangeVisibility.Contains(InnerStruct->GetFName()) : false; + + if (IsABlueprintVariable(VariableProperty) && (bIsInteger || bIsNonEnumByte || bIsFloat || bIsSupportedStruct)) { return EVisibility::Visible; } diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp index 0f42915ac5e6..639959026089 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp @@ -619,6 +619,13 @@ bool FBlueprintEditor::OnRequestClose() FindResultsTab->RequestCloseTab(); } + // Close the Replace References Tab so it doesn't open the next time we do + TSharedPtr ReplaceRefsTab = TabManager->FindExistingLiveTab(FBlueprintEditorTabs::ReplaceNodeReferencesID); + if (ReplaceRefsTab.IsValid()) + { + ReplaceRefsTab->RequestCloseTab(); + } + bEditorMarkedAsClosed = true; return FWorkflowCentricApplication::OnRequestClose(); } @@ -976,15 +983,17 @@ void FBlueprintEditor::OnSelectionUpdated(const TArrayIsActorNode()) { - AActor* DefaultActor = NodePtr->GetEditableObjectForBlueprint(GetBlueprintObj()); - InspectorObjects.Add(DefaultActor); - - FString Title; - DefaultActor->GetName(Title); - InspectorTitle = FText::FromString(Title); - bShowComponents = false; + if (AActor* DefaultActor = NodePtr->GetEditableObjectForBlueprint(GetBlueprintObj())) + { + InspectorObjects.Add(DefaultActor); - TryInvokingDetailsTab(); + FString Title; + DefaultActor->GetName(Title); + InspectorTitle = FText::FromString(Title); + bShowComponents = false; + + TryInvokingDetailsTab(); + } } else { @@ -1272,8 +1281,8 @@ TSharedRef FBlueprintEditor::CreateGraphEditorWidget(TSharedRefMapAction( FGenericCommands::Get().Paste, - FExecuteAction::CreateSP( this, &FBlueprintEditor::PasteNodes ), - FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanPasteNodes ) + FExecuteAction::CreateSP( this, &FBlueprintEditor::PasteGeneric ), + FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanPasteGeneric ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Duplicate, @@ -2368,11 +2377,26 @@ void FBlueprintEditor::PostLayoutBlueprintEditorInitialization() LogSimpleMessage( FText::Format( LOCTEXT("Blueprint Modified Long", "Blueprint \"{BlueprintName}\" was updated to fix issues detected on load. Please resave."), Args ) ); } - // If we have a warning/error, open output log. - if (!Blueprint->IsUpToDate() || (Blueprint->Status == BS_UpToDateWithWarnings)) + // Determine if the current "mode" supports invoking the Compiler Results tab. + const bool bCanInvokeCompilerResultsTab = TabManager->HasTabSpawner(FBlueprintEditorTabs::CompilerResultsID); + + // If we have a warning/error, open output log if the current mode allows us to invoke it. + const bool bIsBlueprintInWarningOrErrorState = !Blueprint->IsUpToDate() || (Blueprint->Status == BS_UpToDateWithWarnings); + if (bIsBlueprintInWarningOrErrorState && bCanInvokeCompilerResultsTab) { TabManager->TryInvokeTab(FBlueprintEditorTabs::CompilerResultsID); } + else + { + // Toolkit modes that don't include this tab may have been incorrectly saved with layout information for restoring it + // as an "unrecognized" tab, due to having previously invoked it above without checking to see if the layout can open + // it first. To correct this, we check if the tab was restored from a saved layout here, and close it if not supported. + TSharedPtr TabPtr = TabManager->FindExistingLiveTab(FBlueprintEditorTabs::CompilerResultsID); + if (TabPtr.IsValid() && !bCanInvokeCompilerResultsTab) + { + TabPtr->RequestCloseTab(); + } + } } if (!GetDefault()->bHostFindInBlueprintsInGlobalTab) @@ -2558,17 +2582,7 @@ void FBlueprintEditor::CreateSCSEditors() .PreviewActor(this, &FBlueprintEditor::GetPreviewActor) .AllowEditing(this, &FBlueprintEditor::InEditingMode) .OnSelectionUpdated(this, &FBlueprintEditor::OnSelectionUpdated) - .OnItemDoubleClicked(this, &FBlueprintEditor::OnComponentDoubleClicked) - .HideComponentClassCombo_Lambda([]() - { - const UBlueprintEditorProjectSettings* Settings = GetDefault(); - return !!Settings->bDisallowAddingNewComponents; - }) - .ComponentTypeFilter_Lambda([]() - { - const UBlueprintEditorProjectSettings* Settings = GetDefault(); - return Settings->DefaultComponentsTreeViewTypeFilter; - }); + .OnItemDoubleClicked(this, &FBlueprintEditor::OnComponentDoubleClicked); SCSViewport = SAssignNew(SCSViewport, SSCSEditorViewport) .BlueprintEditor(SharedThis(this)); @@ -6851,6 +6865,33 @@ void FBlueprintEditor::PasteNodesHere(class UEdGraph* DestinationGraph, const FV FocusedGraphEd->NotifyGraphChanged(); } +void FBlueprintEditor::PasteGeneric() +{ + if (CanPasteNodes()) + { + PasteNodes(); + } + else if (MyBlueprintWidget.IsValid()) + { + if (MyBlueprintWidget->CanPasteGeneric()) + { + MyBlueprintWidget->OnPasteGeneric(); + } + } +} + +bool FBlueprintEditor::CanPasteGeneric() const +{ + if (CanPasteNodes()) + { + return true; + } + else + { + return MyBlueprintWidget.IsValid() && MyBlueprintWidget->CanPasteGeneric(); + } +} + bool FBlueprintEditor::CanPasteNodes() const { @@ -7040,6 +7081,27 @@ UEdGraphPin* FBlueprintEditor::GetCurrentlySelectedPin() const return NULL; } +void FBlueprintEditor::SetDetailsCustomization(TSharedPtr DetailsObjectFilter, TSharedPtr DetailsRootCustomization) +{ + if (Inspector.IsValid()) + { + if (TSharedPtr DetailsView = Inspector->GetPropertyView()) + { + DetailsView->SetObjectFilter(DetailsObjectFilter); + DetailsView->SetRootObjectCustomizationInstance(DetailsRootCustomization); + DetailsView->ForceRefresh(); + } + } +} + +void FBlueprintEditor::SetSCSEditorUICustomization(TSharedPtr SCSEditorUICustomization) +{ + if (SCSEditor.IsValid()) + { + SCSEditor->SetUICustomization(SCSEditorUICustomization); + } +} + void FBlueprintEditor::RegisterSCSEditorCustomization(const FName& InComponentName, TSharedPtr InCustomization) { SCSEditorCustomizations.Add(InComponentName, InCustomization); diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditorModes.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintEditorModes.cpp index 6f128dbcbf45..741e8b75044b 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditorModes.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditorModes.cpp @@ -95,10 +95,7 @@ FBlueprintEditorApplicationMode::FBlueprintEditorApplicationMode(TSharedPtr()->bEnableFindAndReplaceReferences) - { - BlueprintEditorTabFactories.RegisterFactory(MakeShareable(new FReplaceNodeReferencesSummoner(InBlueprintEditor))); - } + BlueprintEditorTabFactories.RegisterFactory(MakeShareable(new FReplaceNodeReferencesSummoner(InBlueprintEditor))); BlueprintEditorTabFactories.RegisterFactory(MakeShareable(new FCompilerResultsSummoner(InBlueprintEditor))); BlueprintEditorTabFactories.RegisterFactory(MakeShareable(new FFindResultsSummoner(InBlueprintEditor))); BlueprintEditorTabFactories.RegisterFactory(MakeShareable(new FBookmarksSummoner(InBlueprintEditor))); @@ -349,10 +346,7 @@ FBlueprintInterfaceApplicationMode::FBlueprintInterfaceApplicationMode(TSharedPt // Create the tab factories BlueprintInterfaceTabFactories.RegisterFactory(MakeShareable(new FDebugInfoSummoner(InBlueprintEditor))); BlueprintInterfaceTabFactories.RegisterFactory(MakeShareable(new FMyBlueprintSummoner(InBlueprintEditor))); - if (GetDefault()->bEnableFindAndReplaceReferences) - { - BlueprintInterfaceTabFactories.RegisterFactory(MakeShareable(new FReplaceNodeReferencesSummoner(InBlueprintEditor))); - } + BlueprintInterfaceTabFactories.RegisterFactory(MakeShareable(new FReplaceNodeReferencesSummoner(InBlueprintEditor))); BlueprintInterfaceTabFactories.RegisterFactory(MakeShareable(new FCompilerResultsSummoner(InBlueprintEditor))); BlueprintInterfaceTabFactories.RegisterFactory(MakeShareable(new FBookmarksSummoner(InBlueprintEditor))); BlueprintInterfaceTabFactories.RegisterFactory(MakeShareable(new FFindResultsSummoner(InBlueprintEditor))); @@ -458,10 +452,7 @@ FBlueprintMacroApplicationMode::FBlueprintMacroApplicationMode(TSharedPtr()->bEnableFindAndReplaceReferences) - { - BlueprintMacroTabFactories.RegisterFactory(MakeShareable(new FReplaceNodeReferencesSummoner(InBlueprintEditor))); - } + BlueprintMacroTabFactories.RegisterFactory(MakeShareable(new FReplaceNodeReferencesSummoner(InBlueprintEditor))); BlueprintMacroTabFactories.RegisterFactory(MakeShareable(new FPaletteSummoner(InBlueprintEditor))); BlueprintMacroTabFactories.RegisterFactory(MakeShareable(new FBookmarksSummoner(InBlueprintEditor))); BlueprintMacroTabFactories.RegisterFactory(MakeShareable(new FFindResultsSummoner(InBlueprintEditor))); @@ -568,10 +559,7 @@ FBlueprintEditorUnifiedMode::FBlueprintEditorUnifiedMode(TSharedPtr()->bEnableFindAndReplaceReferences) - { - BlueprintEditorTabFactories.RegisterFactory(MakeShareable(new FReplaceNodeReferencesSummoner(InBlueprintEditor))); - } + BlueprintEditorTabFactories.RegisterFactory(MakeShareable(new FReplaceNodeReferencesSummoner(InBlueprintEditor))); BlueprintEditorTabFactories.RegisterFactory(MakeShareable(new FCompilerResultsSummoner(InBlueprintEditor))); BlueprintEditorTabFactories.RegisterFactory(MakeShareable(new FFindResultsSummoner(InBlueprintEditor))); BlueprintEditorTabFactories.RegisterFactory(MakeShareable(new FBookmarksSummoner(InBlueprintEditor))); diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditorModule.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintEditorModule.cpp index 4dba198fae2a..850dd4ae80fa 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditorModule.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditorModule.cpp @@ -258,33 +258,20 @@ void FBlueprintEditorModule::ShutdownModule() UEdGraphPin::ShutdownVerification(); } - TSharedRef FBlueprintEditorModule::CreateBlueprintEditor(const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, UBlueprint* Blueprint, bool bShouldOpenInDefaultsMode) { - TSharedRef< FBlueprintEditor > NewBlueprintEditor( new FBlueprintEditor() ); - - TArray Blueprints; - Blueprints.Add(Blueprint); - NewBlueprintEditor->InitBlueprintEditor(Mode, InitToolkitHost, Blueprints, bShouldOpenInDefaultsMode); - - for(auto It(SCSEditorCustomizations.CreateConstIterator()); It; ++It) - { - NewBlueprintEditor->RegisterSCSEditorCustomization(It->Key, It->Value.Execute(NewBlueprintEditor)); - } - - WatchViewer::UpdateWatchListFromBlueprint(Blueprint); - - EBlueprintType const BPType = Blueprint ? (EBlueprintType)Blueprint->BlueprintType : BPTYPE_Normal; - BlueprintEditorOpened.Broadcast(BPType); - - return NewBlueprintEditor; + TArray BlueprintsToEdit = { Blueprint }; + return CreateBlueprintEditor(Mode, InitToolkitHost, BlueprintsToEdit, bShouldOpenInDefaultsMode); } -TSharedRef FBlueprintEditorModule::CreateBlueprintEditor(const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, const TArray< UBlueprint* >& BlueprintsToEdit ) +TSharedRef FBlueprintEditorModule::CreateBlueprintEditor(const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, const TArray< UBlueprint* >& BlueprintsToEdit, bool bShouldOpenInDefaultsMode) { TSharedRef< FBlueprintEditor > NewBlueprintEditor( new FBlueprintEditor() ); - NewBlueprintEditor->InitBlueprintEditor(Mode, InitToolkitHost, BlueprintsToEdit, true); + NewBlueprintEditor->InitBlueprintEditor(Mode, InitToolkitHost, BlueprintsToEdit, bShouldOpenInDefaultsMode); + + NewBlueprintEditor->SetDetailsCustomization(DetailsObjectFilter, DetailsRootCustomization); + NewBlueprintEditor->SetSCSEditorUICustomization(SCSEditorUICustomization); for(auto It(SCSEditorCustomizations.CreateConstIterator()); It; ++It) { @@ -302,9 +289,37 @@ TSharedRef FBlueprintEditorModule::CreateBlueprintEditor(const BlueprintEditorOpened.Broadcast(BPType); + BlueprintEditors.Add(NewBlueprintEditor); + return NewBlueprintEditor; } +TArray> FBlueprintEditorModule::GetBlueprintEditors() const +{ + TArray> ValidBlueprintEditors; + ValidBlueprintEditors.Reserve(BlueprintEditors.Num()); + + for (TWeakPtr BlueprintEditor : BlueprintEditors) + { + if (TSharedPtr BlueprintEditorPinned = BlueprintEditor.Pin()) + { + ValidBlueprintEditors.Add(BlueprintEditorPinned.ToSharedRef()); + } + } + + if (BlueprintEditors.Num() > ValidBlueprintEditors.Num()) + { + TArray>& BlueprintEditorsNonConst = const_cast>&>(BlueprintEditors); + BlueprintEditorsNonConst.Reset(ValidBlueprintEditors.Num()); + for (const TSharedRef& ValidBlueprintEditor : ValidBlueprintEditors) + { + BlueprintEditorsNonConst.Add(StaticCastSharedRef(ValidBlueprintEditor)); + } + } + + return ValidBlueprintEditors; +} + TSharedRef FBlueprintEditorModule::CreateUserDefinedEnumEditor(const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, UUserDefinedEnum* UDEnum) { TSharedRef UserDefinedEnumEditor(new FUserDefinedEnumEditor()); @@ -319,6 +334,27 @@ TSharedRef FBlueprintEditorModule::CreateUserDefine return UserDefinedStructureEditor; } +void FBlueprintEditorModule::SetDetailsCustomization(TSharedPtr InDetailsObjectFilter, TSharedPtr InDetailsRootCustomization) +{ + DetailsObjectFilter = InDetailsObjectFilter; + DetailsRootCustomization = InDetailsRootCustomization; + + for (const TSharedRef& BlueprintEditor : GetBlueprintEditors()) + { + StaticCastSharedRef(BlueprintEditor)->SetDetailsCustomization(DetailsObjectFilter, DetailsRootCustomization); + } +} + +void FBlueprintEditorModule::SetSCSEditorUICustomization(TSharedPtr InSCSEditorUICustomization) +{ + SCSEditorUICustomization = InSCSEditorUICustomization; + + for (const TSharedRef& BlueprintEditor : GetBlueprintEditors()) + { + StaticCastSharedRef(BlueprintEditor)->SetSCSEditorUICustomization(SCSEditorUICustomization); + } +} + void FBlueprintEditorModule::RegisterSCSEditorCustomization(const FName& InComponentName, FSCSEditorCustomizationBuilder InCustomizationBuilder) { SCSEditorCustomizations.Add(InComponentName, InCustomizationBuilder); diff --git a/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp b/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp index ebd2f6549a06..cb3424a14eee 100644 --- a/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp +++ b/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp @@ -2098,11 +2098,16 @@ void FFindInBlueprintSearchManager::OnAssetAdded(const FAssetData& InAssetData) if (InAssetData.IsAssetLoaded()) { - UObject* AssetObject = InAssetData.GetAsset(); - UBlueprint* Blueprint = Handler->RetrieveBlueprint(AssetObject); - if (Blueprint) + if (UObject* AssetObject = InAssetData.GetAsset()) { - AddOrUpdateBlueprintSearchMetadata(Blueprint); + if (ensureMsgf(AssetObject->IsA(AssetClass), TEXT("AssetClass (%s) matched handler, but does not match actual object type (%s) for asset: %s."), *AssetClass->GetName(), *AssetObject->GetClass()->GetName(), *AssetObject->GetPathName())) + { + UBlueprint* Blueprint = Handler->RetrieveBlueprint(AssetObject); + if (Blueprint) + { + AddOrUpdateBlueprintSearchMetadata(Blueprint); + } + } } } else if (Handler->AssetContainsBlueprint(InAssetData)) @@ -3589,10 +3594,11 @@ void FFindInBlueprintSearchManager::Tick(float DeltaTime) bool FFindInBlueprintSearchManager::IsTickable() const { - const bool bHasPendingAssets = PendingAssets.Num() > 0 || (bHasFirstSearchOccurred && AssetsToIndexOnFirstSearch.Num() > 0); + const bool bHasPendingAssets = PendingAssets.Num() > 0; + const bool bNeedsFirstIndex = bHasFirstSearchOccurred && AssetsToIndexOnFirstSearch.Num() > 0; - // Tick only if we have an active caching operation or if we have pending assets and an open FiB context - return IsCacheInProgress() || (bHasPendingAssets && GlobalFindResults.Num() > 0); + // Tick only if we have an active caching operation or if a search has occured before we're ready or if we have pending assets and an open FiB context + return IsCacheInProgress() || bNeedsFirstIndex || (bHasPendingAssets && IsGlobalFindResultsOpen()); } TStatId FFindInBlueprintSearchManager::GetStatId() const diff --git a/Engine/Source/Editor/Kismet/Private/FindInBlueprints.cpp b/Engine/Source/Editor/Kismet/Private/FindInBlueprints.cpp index c30f6eed6ed1..fbeeb4eac011 100644 --- a/Engine/Source/Editor/Kismet/Private/FindInBlueprints.cpp +++ b/Engine/Source/Editor/Kismet/Private/FindInBlueprints.cpp @@ -629,7 +629,7 @@ void SFindInBlueprints::Construct( const FArguments& InArgs, TSharedPtrChildren; } + + // call SearchCompleted callback if bound (the only steps left are to update the TreeView, the search operation is complete) + if (InOnSearchComplete.IsBound()) + { + TArray FilteredImaginaryResults; + SearchInstance->CreateFilteredResultsListFromTree(InSearchOptions.ImaginaryDataFilter, FilteredImaginaryResults); + InOnSearchComplete.Execute(FilteredImaginaryResults); + } } if(ItemsFound.Num() == 0) @@ -1684,6 +1692,11 @@ void SFindInBlueprints::CloseHostTab() } } +bool SFindInBlueprints::IsSearchInProgress() const +{ + return StreamSearch.IsValid() && !StreamSearch->IsComplete(); +} + FReply SFindInBlueprints::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { // BlueprintEditor's IToolkit code will handle shortcuts itself - but we can just use @@ -1698,4 +1711,14 @@ FReply SFindInBlueprints::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent); } +void SFindInBlueprints::ClearResults() +{ + ItemsFound.Empty(); + + if (TreeView.IsValid()) + { + TreeView->RequestTreeRefresh(); + } +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/Kismet/Private/ISCSEditorUICustomization.cpp b/Engine/Source/Editor/Kismet/Private/ISCSEditorUICustomization.cpp new file mode 100644 index 000000000000..338789291194 --- /dev/null +++ b/Engine/Source/Editor/Kismet/Private/ISCSEditorUICustomization.cpp @@ -0,0 +1,3 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ISCSEditorUICustomization.h" diff --git a/Engine/Source/Editor/Kismet/Private/ReplaceNodeReferencesHelper.cpp b/Engine/Source/Editor/Kismet/Private/ReplaceNodeReferencesHelper.cpp new file mode 100644 index 000000000000..c9289cfda0d8 --- /dev/null +++ b/Engine/Source/Editor/Kismet/Private/ReplaceNodeReferencesHelper.cpp @@ -0,0 +1,139 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ReplaceNodeReferencesHelper.h" +#include "ImaginaryBlueprintData.h" +#include "K2Node_Variable.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "Misc/ScopedSlowTask.h" + +#define LOCTEXT_NAMESPACE "FReplaceNodeReferencesHelper" + +FReplaceNodeReferencesHelper::FReplaceNodeReferencesHelper(const FMemberReference& Source, const FMemberReference& Replacement, UBlueprint* InBlueprint) + : SourceReference(Source) + , ReplacementReference(Replacement) + , Blueprint(InBlueprint) +{ +} + +FReplaceNodeReferencesHelper::FReplaceNodeReferencesHelper(FMemberReference&& Source, FMemberReference&& Replacement, UBlueprint* InBlueprint) + : SourceReference(MoveTemp(Source)) + , ReplacementReference(MoveTemp(Replacement)) + , Blueprint(InBlueprint) +{ +} + +FReplaceNodeReferencesHelper::~FReplaceNodeReferencesHelper() +{ +} + +void FReplaceNodeReferencesHelper::BeginFindAndReplace(const FSimpleDelegate& InOnCompleted /*=FSimpleDelegate()*/) +{ + bCompleted = false; + OnCompleted = InOnCompleted; + FFindInBlueprintCachingOptions CachingOptions; + CachingOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; + CachingOptions.OnFinished.BindRaw(this, &FReplaceNodeReferencesHelper::OnSubmitSearchQuery); + FFindInBlueprintSearchManager::Get().CacheAllAssets(nullptr, CachingOptions); + SlowTask = MakeUnique(3.f, LOCTEXT("Caching", "Caching Blueprints...")); + SlowTask->MakeDialog(); +} + +void FReplaceNodeReferencesHelper::ReplaceReferences(TArray& InRawDataList) +{ + ReplaceReferences(ReplacementReference, Blueprint, InRawDataList); +} + +void FReplaceNodeReferencesHelper::ReplaceReferences(FMemberReference& InReplacement, UBlueprint* InBlueprint, TArray& InRawDataList) +{ + const FScopedTransaction Transaction(FText::Format(LOCTEXT("ReplaceRefs", "Replace References with {0}"), FText::FromName(InReplacement.GetMemberName()))); + TArray< UBlueprint* > BlueprintsModified; + for (FImaginaryFiBDataSharedPtr ImaginaryData : InRawDataList) + { + UBlueprint* Blueprint = ImaginaryData->GetBlueprint(); + BlueprintsModified.AddUnique(Blueprint); + UObject* Node = ImaginaryData->GetObject(Blueprint); + UK2Node_Variable* VariableNode = Cast(Node); + if (ensure(VariableNode)) + { + VariableNode->Modify(); + if (VariableNode->VariableReference.IsLocalScope() || VariableNode->VariableReference.IsSelfContext()) + { + VariableNode->VariableReference = InReplacement; + } + else + { + VariableNode->VariableReference.SetFromField(InReplacement.ResolveMember(InBlueprint), Blueprint->GeneratedClass); + } + VariableNode->ReconstructNode(); + } + } + + for (UBlueprint* ModifiedBlueprint : BlueprintsModified) + { + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(ModifiedBlueprint); + FFindInBlueprintSearchManager::Get().AddOrUpdateBlueprintSearchMetadata(ModifiedBlueprint); + } +} + +const void FReplaceNodeReferencesHelper::SetTransaction(TSharedPtr InTransaction) +{ + Transaction = InTransaction; +} + +bool FReplaceNodeReferencesHelper::IsTickable() const +{ + return SlowTask.IsValid(); +} + +void FReplaceNodeReferencesHelper::Tick(float DeltaSeconds) +{ + if (StreamSearch.IsValid()) + { + UpdateSearchQuery(); + } + else + { + SlowTask->CompletedWork = FFindInBlueprintSearchManager::Get().GetCacheProgress(); + } +} + +TStatId FReplaceNodeReferencesHelper::GetStatId() const +{ + return TStatId(); +} + +void FReplaceNodeReferencesHelper::OnSubmitSearchQuery() +{ + SlowTask->FrameMessage = LOCTEXT("Searching", "Searching Blueprints..."); + FString SearchTerm = SourceReference.GetReferenceSearchString(SourceReference.GetMemberParentClass()); + + FStreamSearchOptions SearchOptions; + SearchOptions.ImaginaryDataFilter = ESearchQueryFilter::NodesFilter; + SearchOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; + + StreamSearch = MakeShared(SearchTerm, SearchOptions); +} + +void FReplaceNodeReferencesHelper::UpdateSearchQuery() +{ + if (!StreamSearch->IsComplete()) + { + SlowTask->CompletedWork = 1.f + FFindInBlueprintSearchManager::Get().GetPercentComplete(StreamSearch.Get()); + } + else + { + TArray ImaginaryData; + StreamSearch->GetFilteredImaginaryResults(ImaginaryData); + ReplaceReferences(ImaginaryData); + + StreamSearch->EnsureCompletion(); + + // End the SlowTask + SlowTask.Reset(); + + OnCompleted.ExecuteIfBound(); + bCompleted = true; + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/Kismet/Private/SCSEditorExtensionContext.cpp b/Engine/Source/Editor/Kismet/Private/SCSEditorExtensionContext.cpp new file mode 100644 index 000000000000..3ce0dc773247 --- /dev/null +++ b/Engine/Source/Editor/Kismet/Private/SCSEditorExtensionContext.cpp @@ -0,0 +1,3 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SCSEditorExtensionContext.h" diff --git a/Engine/Source/Editor/Kismet/Private/SMyBlueprint.cpp b/Engine/Source/Editor/Kismet/Private/SMyBlueprint.cpp index b4a7b9391512..46f520e39a43 100644 --- a/Engine/Source/Editor/Kismet/Private/SMyBlueprint.cpp +++ b/Engine/Source/Editor/Kismet/Private/SMyBlueprint.cpp @@ -26,6 +26,7 @@ #include "K2Node_FunctionEntry.h" #include "K2Node_EventNodeInterface.h" #include "ScopedTransaction.h" +#include "HAL/PlatformApplicationMisc.h" #include "DetailLayoutBuilder.h" @@ -54,21 +55,34 @@ #include "BlueprintEditorSettings.h" #include "SReplaceNodeReferences.h" +#include "ReplaceNodeReferencesHelper.h" #include "Animation/AnimClassInterface.h" +#include "BPFunctionClipboardData.h" + #define LOCTEXT_NAMESPACE "MyBlueprint" ////////////////////////////////////////////////////////////////////////// +// Magic values to differentiate Variables and Functions on the clipboard +static const TCHAR* VAR_PREFIX = TEXT("BPVar"); +static const TCHAR* FUNC_PREFIX = TEXT("BPFunc"); + +////////////////////////////////////////////////////////////////////////// + void FMyBlueprintCommands::RegisterCommands() { UI_COMMAND( OpenGraph, "Open Graph", "Opens up this function, macro, or event graph's graph panel up.", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( OpenGraphInNewTab, "Open in New Tab", "Opens up this function, macro, or event graph's graph panel up in a new tab. Hold down Ctrl and double click for shortcut.", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( FocusNode, "Focus", "Focuses on the associated node", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( FocusNodeInNewTab, "Focus in New Tab", "Focuses on the associated node in a new tab", EUserInterfaceActionType::Button, FInputChord() ); - UI_COMMAND( ImplementFunction, "Implement Function", "Implements this overridable function as a new function.", EUserInterfaceActionType::Button, FInputChord() ); - UI_COMMAND(DeleteEntry, "Delete", "Deletes this function or variable from this blueprint.", EUserInterfaceActionType::Button, FInputChord(EKeys::Platform_Delete)); + UI_COMMAND( ImplementFunction, "Implement event", "Implements this overridable function as a new event.", EUserInterfaceActionType::Button, FInputChord() ); + UI_COMMAND( DeleteEntry, "Delete", "Deletes this function or variable from this blueprint.", EUserInterfaceActionType::Button, FInputChord(EKeys::Platform_Delete)); + UI_COMMAND( PasteVariable, "Paste Variable", "Pastes the variable to this blueprint.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND( PasteLocalVariable, "Paste Local Variable", "Pastes the variable to this scope.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND( PasteFunction, "Paste Function", "Pastes the function to this blueprint.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND( GotoNativeVarDefinition, "Goto Code Definition", "Goto the native code definition of this variable", EUserInterfaceActionType::Button, FInputChord() ); + UI_COMMAND( MoveToParent, "Move to Parent Class", "Moves the variable to its parent class", EUserInterfaceActionType::Button, FInputChord() ); } ////////////////////////////////////////////////////////////////////////// @@ -246,65 +260,101 @@ void SMyBlueprint::Construct(const FArguments& InArgs, TWeakPtrGetBlueprintObj(); - TSharedPtr ToolKitCommandList = InBlueprintEditor.Pin()->GetToolkitCommands(); + CommandList = MakeShareable(new FUICommandList); + + CommandList->Append(InBlueprintEditor.Pin()->GetToolkitCommands()); - ToolKitCommandList->MapAction( FMyBlueprintCommands::Get().OpenGraph, + CommandList->MapAction( FMyBlueprintCommands::Get().OpenGraph, FExecuteAction::CreateSP(this, &SMyBlueprint::OnOpenGraph), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanOpenGraph) ); - ToolKitCommandList->MapAction( FMyBlueprintCommands::Get().OpenGraphInNewTab, + CommandList->MapAction( FMyBlueprintCommands::Get().OpenGraphInNewTab, FExecuteAction::CreateSP(this, &SMyBlueprint::OnOpenGraphInNewTab), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanOpenGraph) ); - ToolKitCommandList->MapAction( FMyBlueprintCommands::Get().FocusNode, + CommandList->MapAction( FMyBlueprintCommands::Get().FocusNode, FExecuteAction::CreateSP(this, &SMyBlueprint::OnFocusNode), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanFocusOnNode) ); - ToolKitCommandList->MapAction( FMyBlueprintCommands::Get().FocusNodeInNewTab, + CommandList->MapAction( FMyBlueprintCommands::Get().FocusNodeInNewTab, FExecuteAction::CreateSP(this, &SMyBlueprint::OnFocusNodeInNewTab), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanFocusOnNode) ); - ToolKitCommandList->MapAction( FMyBlueprintCommands::Get().ImplementFunction, + CommandList->MapAction( FMyBlueprintCommands::Get().ImplementFunction, FExecuteAction::CreateSP(this, &SMyBlueprint::OnImplementFunction), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanImplementFunction) ); - ToolKitCommandList->MapAction( FGraphEditorCommands::Get().FindReferences, + CommandList->MapAction( FGraphEditorCommands::Get().FindReferences, FExecuteAction::CreateSP(this, &SMyBlueprint::OnFindReference), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanFindReference) ); - ToolKitCommandList->MapAction( FGraphEditorCommands::Get().FindAndReplaceReferences, + CommandList->MapAction( FGraphEditorCommands::Get().FindAndReplaceReferences, FExecuteAction::CreateSP(this, &SMyBlueprint::OnFindAndReplaceReference), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanFindAndReplaceReference) ); - ToolKitCommandList->MapAction( FMyBlueprintCommands::Get().DeleteEntry, + CommandList->MapAction( FMyBlueprintCommands::Get().DeleteEntry, FExecuteAction::CreateSP(this, &SMyBlueprint::OnDeleteEntry), FCanExecuteAction::CreateSP(this, &SMyBlueprint::CanDeleteEntry) ); - ToolKitCommandList->MapAction( FGenericCommands::Get().Duplicate, + CommandList->MapAction( FGenericCommands::Get().Duplicate, FExecuteAction::CreateSP(this, &SMyBlueprint::OnDuplicateAction), FCanExecuteAction::CreateSP(this, &SMyBlueprint::CanDuplicateAction), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::IsDuplicateActionVisible) ); - ToolKitCommandList->MapAction( FMyBlueprintCommands::Get().GotoNativeVarDefinition, + CommandList->MapAction( FMyBlueprintCommands::Get().MoveToParent, + FExecuteAction::CreateSP(this, &SMyBlueprint::OnMoveToParent), + FCanExecuteAction(), + FIsActionChecked(), + FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanMoveToParent) ); + + CommandList->MapAction( FMyBlueprintCommands::Get().GotoNativeVarDefinition, FExecuteAction::CreateSP(this, &SMyBlueprint::GotoNativeCodeVarDefinition), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::IsNativeVariable) ); ToolbarBuilderWidget = SNullWidget::NullWidget; - ToolKitCommandList->MapAction(FGenericCommands::Get().Rename, + CommandList->MapAction(FGenericCommands::Get().Rename, FExecuteAction::CreateSP(this, &SMyBlueprint::OnRequestRenameOnActionNode), FCanExecuteAction::CreateSP(this, &SMyBlueprint::CanRequestRenameOnActionNode)); + + CommandList->MapAction(FGenericCommands::Get().Copy, + FExecuteAction::CreateSP(this, &SMyBlueprint::OnCopy), + FCanExecuteAction::CreateSP(this, &SMyBlueprint::CanCopy)); + + CommandList->MapAction(FGenericCommands::Get().Cut, + FExecuteAction::CreateSP(this, &SMyBlueprint::OnCut), + FCanExecuteAction::CreateSP(this, &SMyBlueprint::CanCut)); + + CommandList->MapAction(FGenericCommands::Get().Paste, + FExecuteAction::CreateSP(this, &SMyBlueprint::OnPasteGeneric), + FCanExecuteAction(), FIsActionChecked(), + FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanPasteGeneric)); + + CommandList->MapAction(FMyBlueprintCommands::Get().PasteVariable, + FExecuteAction::CreateSP(this, &SMyBlueprint::OnPasteVariable), + FCanExecuteAction(), FIsActionChecked(), + FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanPasteVariable)); + + CommandList->MapAction(FMyBlueprintCommands::Get().PasteLocalVariable, + FExecuteAction::CreateSP(this, &SMyBlueprint::OnPasteLocalVariable), + FCanExecuteAction(), FIsActionChecked(), + FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanPasteLocalVariable)); + + CommandList->MapAction(FMyBlueprintCommands::Get().PasteFunction, + FExecuteAction::CreateSP(this, &SMyBlueprint::OnPasteFunction), + FCanExecuteAction(), FIsActionChecked(), + FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanPasteFunction)); } else { @@ -550,6 +600,15 @@ void SMyBlueprint::Tick(const FGeometry& AllottedGeometry, const double InCurren } } +FReply SMyBlueprint::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + if (CommandList->ProcessCommandBindings(InKeyEvent)) + { + return FReply::Handled(); + } + return FReply::Unhandled(); +} + void SMyBlueprint::OnCategoryNameCommitted(const FText& InNewText, ETextCommit::Type InTextCommit, TWeakPtr< FGraphActionNode > InAction ) { // Remove excess whitespace and prevent categories with just spaces @@ -801,22 +860,22 @@ FReply SMyBlueprint::OnAddButtonClickedOnSection(int32 InSectionID) switch ( InSectionID ) { case NodeSectionID::VARIABLE: - BlueprintEditor->GetToolkitCommands()->ExecuteAction(FBlueprintEditorCommands::Get().AddNewVariable.ToSharedRef()); + CommandList->ExecuteAction(FBlueprintEditorCommands::Get().AddNewVariable.ToSharedRef()); break; case NodeSectionID::FUNCTION: - BlueprintEditor->GetToolkitCommands()->ExecuteAction(FBlueprintEditorCommands::Get().AddNewFunction.ToSharedRef()); + CommandList->ExecuteAction(FBlueprintEditorCommands::Get().AddNewFunction.ToSharedRef()); break; case NodeSectionID::MACRO: - BlueprintEditor->GetToolkitCommands()->ExecuteAction(FBlueprintEditorCommands::Get().AddNewMacroDeclaration.ToSharedRef()); + CommandList->ExecuteAction(FBlueprintEditorCommands::Get().AddNewMacroDeclaration.ToSharedRef()); break; case NodeSectionID::DELEGATE: - BlueprintEditor->GetToolkitCommands()->ExecuteAction(FBlueprintEditorCommands::Get().AddNewDelegate.ToSharedRef()); + CommandList->ExecuteAction(FBlueprintEditorCommands::Get().AddNewDelegate.ToSharedRef()); break; case NodeSectionID::GRAPH: - BlueprintEditor->GetToolkitCommands()->ExecuteAction(FBlueprintEditorCommands::Get().AddNewEventGraph.ToSharedRef()); + CommandList->ExecuteAction(FBlueprintEditorCommands::Get().AddNewEventGraph.ToSharedRef()); break; case NodeSectionID::ANIMLAYER: - BlueprintEditor->GetToolkitCommands()->ExecuteAction(FBlueprintEditorCommands::Get().AddNewAnimationLayer.ToSharedRef()); + CommandList->ExecuteAction(FBlueprintEditorCommands::Get().AddNewAnimationLayer.ToSharedRef()); break; case NodeSectionID::LOCAL_VARIABLE: OnAddNewLocalVariable(); @@ -881,7 +940,7 @@ EVisibility SMyBlueprint::OnGetSectionTextVisibility(TWeakPtr RowWidget TSharedRef SMyBlueprint::OnGetFunctionListMenu() { const bool bShouldCloseWindowAfterMenuSelection = true; - FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, BlueprintEditorPtr.Pin()->GetToolkitCommands()); + FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, CommandList); BuildOverridableFunctionsMenu(MenuBuilder); @@ -980,6 +1039,12 @@ void SMyBlueprint::Refresh() { bNeedsRefresh = false; + // If there's a valid replace helper and it needs to be deleted, get rid of it + if (ReplaceHelper.IsValid() && ReplaceHelper->IsCompleted()) + { + ReplaceHelper.Reset(); + } + // Conform to our interfaces here to ensure we catch any newly added functions FBlueprintEditorUtils::ConformImplementedInterfaces(GetBlueprintObj()); @@ -1824,13 +1889,7 @@ void SMyBlueprint::OnActionSelectedHelper(TSharedPtr InAct SKismetInspector::FShowDetailsOptions Options(FText::FromName(VarAction->GetVariableName())); Options.bForceRefresh = true; - FProperty* Prop = VarAction->GetProperty(); - UPropertyWrapper* PropWrap = (Prop ? Prop->GetUPropertyWrapper() : nullptr); - Inspector->ShowDetailsForSingleObject(PropWrap, Options); - if (InBlueprintEditor.IsValid()) - { - InBlueprintEditor.Pin()->GetReplaceReferencesWidget()->SetSourceVariable(VarAction->GetProperty()); - } + Inspector->ShowDetailsForSingleObject(VarAction->GetProperty()->GetUPropertyWrapper(), Options); } else if (InAction->GetTypeId() == FEdGraphSchemaAction_K2LocalVar::StaticGetTypeId()) { @@ -2075,7 +2134,7 @@ TSharedPtr SMyBlueprint::OnContextMenuOpening() } const bool bShouldCloseWindowAfterMenuSelection = true; - FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection,BlueprintEditorPtr.Pin()->GetToolkitCommands()); + FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, CommandList); // Check if the selected action is valid for a context menu if (SelectionHasContextMenu()) @@ -2091,7 +2150,10 @@ TSharedPtr SMyBlueprint::OnContextMenuOpening() MenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().FindReferences); MenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().FindAndReplaceReferences); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().GotoNativeVarDefinition); + MenuBuilder.AddMenuEntry(FGenericCommands::Get().Cut); + MenuBuilder.AddMenuEntry(FGenericCommands::Get().Copy); MenuBuilder.AddMenuEntry(FGenericCommands::Get().Duplicate); + MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().MoveToParent); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().DeleteEntry); } MenuBuilder.EndSection(); @@ -2123,16 +2185,20 @@ TSharedPtr SMyBlueprint::OnContextMenuOpening() } } // If this is a function graph than we should add the option to convert it to an event if possible - else if( Graph ) + else if( Graph && Graph->EdGraph ) { // The first function entry node will have all the information that the conversion needs + // (the interface method entry in the tree might not have a real graph though, if it comes from a parent unchanged or is an event that hasn't been implemented yet) UK2Node_FunctionEntry* EntryNode = nullptr; - for(UEdGraphNode* Node : Graph->EdGraph->Nodes) + if (Graph->EdGraph != nullptr) { - if (UK2Node_FunctionEntry* TypedNode = Cast(Node)) + for( UEdGraphNode* Node : Graph->EdGraph->Nodes) { - EntryNode = TypedNode; - break; + if (UK2Node_FunctionEntry* TypedNode = Cast(Node)) + { + EntryNode = TypedNode; + break; + } } } @@ -2180,7 +2246,7 @@ TSharedPtr SMyBlueprint::OnContextMenuOpening() TSharedRef SMyBlueprint::CreateAddNewMenuWidget() { const bool bShouldCloseWindowAfterMenuSelection = true; - FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, BlueprintEditorPtr.Pin()->GetToolkitCommands()); + FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, CommandList); BuildAddNewMenu(MenuBuilder); @@ -2196,14 +2262,17 @@ void SMyBlueprint::BuildAddNewMenu(FMenuBuilder& MenuBuilder) if (CurrentBlueprint->SupportsGlobalVariables()) { MenuBuilder.AddMenuEntry(FBlueprintEditorCommands::Get().AddNewVariable); + MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().PasteVariable); } if (CurrentBlueprint->SupportsLocalVariables()) { MenuBuilder.AddMenuEntry(FBlueprintEditorCommands::Get().AddNewLocalVariable); + MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().PasteLocalVariable); } if (CurrentBlueprint->SupportsFunctions()) { MenuBuilder.AddMenuEntry(FBlueprintEditorCommands::Get().AddNewFunction); + MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().PasteFunction); // If we cannot handle Function Graphs, we cannot handle function overrides if (OverridableFunctionActions.Num() > 0 && BlueprintEditorPtr.Pin()->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewFunctionGraph)) @@ -2528,16 +2597,34 @@ bool SMyBlueprint::CanFindReference() const void SMyBlueprint::OnFindAndReplaceReference() { - BlueprintEditorPtr.Pin()->SummonFindAndReplaceUI(); + TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); + if (PinnedEditor.IsValid()) + { + if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) + { + PinnedEditor->SummonFindAndReplaceUI(); + if (PinnedEditor->GetReplaceReferencesWidget().IsValid()) + { + PinnedEditor->GetReplaceReferencesWidget()->SetSourceVariable(VarAction->GetProperty()); + } + } + } } bool SMyBlueprint::CanFindAndReplaceReference() const { - if (SelectionAsVar() && GetDefault()->bEnableFindAndReplaceReferences) + if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) { - return true; + // If this variable was introduced in this class + // note: this also disallows SCS component variables because they won't be found in the NewVariables list + UBlueprint* SourceBlueprint; + int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndexAndBlueprint(Blueprint, VarAction->GetVariableName(), SourceBlueprint); + if (VarIndex != INDEX_NONE) + { + return SourceBlueprint == Blueprint; + } } - + return false; } @@ -2975,6 +3062,7 @@ void SMyBlueprint::OnDuplicateAction() if(DuplicateActionName != NAME_None) { SelectItemByName(DuplicateActionName); + Refresh(); OnRequestRenameOnActionNode(); } } @@ -3004,6 +3092,375 @@ bool SMyBlueprint::IsNativeVariable() const return false; } +void SMyBlueprint::OnMoveToParent() +{ + if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) + { + if (UBlueprint* ParentBlueprint = UBlueprint::GetBlueprintFromClass(Blueprint->ParentClass)) + { + TSharedPtr Transaction = MakeShared(LOCTEXT("MoveToParent", "Move To Parent")); + + FName VarCopyName = FBlueprintEditorUtils::DuplicateMemberVariable(Blueprint, ParentBlueprint, VarAction->GetVariableName()); + + if (VarCopyName != NAME_None) + { + // If properties are not found, these will be nullptr + const FProperty* SourceProperty = FindFProperty(Blueprint->SkeletonGeneratedClass, VarAction->GetVariableName()); + const FProperty* ReplacementProperty = FindFProperty(ParentBlueprint->SkeletonGeneratedClass, VarCopyName); + if (SourceProperty && ReplacementProperty) + { + // ReplaceAllReferences + FMemberReference OldVar; + FMemberReference NewVar; + OldVar.SetFromField(SourceProperty, true, SourceProperty->GetOwnerClass()); + NewVar.SetFromField(ReplacementProperty, true, ReplacementProperty->GetOwnerClass()); + ReplaceHelper = MakeShared(MoveTemp(OldVar), MoveTemp(NewVar), Blueprint); + ReplaceHelper->SetTransaction(Transaction); + + FSimpleDelegate OnCompleted = FSimpleDelegate::CreateSP(this, &SMyBlueprint::OnMoveToParentCompleted); + + // This starts an FSlowTask, so we don't need to worry about anything breaking while the task is completed + ReplaceHelper->BeginFindAndReplace(OnCompleted); + } + } + } + } +} + +void SMyBlueprint::OnMoveToParentCompleted() +{ + if (UBlueprint* ParentBlueprint = UBlueprint::GetBlueprintFromClass(Blueprint->ParentClass)) + { + // Remove old var + FName OldName = ReplaceHelper->GetSource().GetMemberName(); + Blueprint->Modify(); + FBlueprintEditorUtils::RemoveMemberVariable(Blueprint, ReplaceHelper->GetSource().GetMemberName()); + + // Rename new var + FBlueprintEditorUtils::RenameMemberVariable(ParentBlueprint, ReplaceHelper->GetReplacement().GetMemberName(), OldName); + } + + // We need to defer destroying the helper until the next refresh because helper is currently ticking + bNeedsRefresh = true; +} + +bool SMyBlueprint::CanMoveToParent() const +{ + bool bCanMove = false; + + TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); + if (PinnedEditor.IsValid() && PinnedEditor->IsParentClassABlueprint()) + { + if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) + { + // If this variable is new to this class + UBlueprint* SourceBlueprint; + int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndexAndBlueprint(Blueprint, VarAction->GetVariableName(), SourceBlueprint); + bCanMove = (VarIndex != INDEX_NONE) && (SourceBlueprint == Blueprint); + } + else if (SelectionAsGraph()) + { + // TODO : add support for functions + } + } + + return bCanMove; +} + +void SMyBlueprint::OnCopy() +{ + FString OutputString; + + if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) + { + UBlueprint* SourceBlueprint; + int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndexAndBlueprint(Blueprint, VarAction->GetVariableName(), SourceBlueprint); + if (VarIndex != INDEX_NONE) + { + // make a copy of the Variable description so we can set the default value + FBPVariableDescription Description = SourceBlueprint->NewVariables[VarIndex]; + + //Grab property of blueprint's current CDO + UClass* GeneratedClass = SourceBlueprint->GeneratedClass; + UObject* GeneratedCDO = GeneratedClass->GetDefaultObject(); + FProperty* TargetProperty = FindFProperty(GeneratedClass, Description.VarName); + + if (TargetProperty) + { + // Grab the address of where the property is actually stored (UObject* base, plus the offset defined in the property) + void* OldPropertyAddr = TargetProperty->ContainerPtrToValuePtr(GeneratedCDO); + if (OldPropertyAddr) + { + TargetProperty->ExportTextItem(Description.DefaultValue, OldPropertyAddr, OldPropertyAddr, nullptr, PPF_SerializedAsImportText); + } + } + + FBPVariableDescription::StaticStruct()->ExportText(OutputString, &Description, nullptr, nullptr, 0, nullptr, false); + OutputString = VAR_PREFIX + OutputString; + } + } + else if (FEdGraphSchemaAction_K2LocalVar* LocalVarAction = SelectionAsLocalVar()) + { + FBPVariableDescription* Description = FBlueprintEditorUtils::FindLocalVariable(Blueprint, LocalVarAction->GetVariableScope(), LocalVarAction->GetVariableName()); + + if (Description) + { + FBPVariableDescription::StaticStruct()->ExportText(OutputString, Description, nullptr, nullptr, 0, nullptr, false); + OutputString = VAR_PREFIX + OutputString; + } + } + else if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) + { + if (GraphAction->GraphType == EEdGraphSchemaAction_K2Graph::Function) + { + FBPFunctionClipboardData FuncData(GraphAction->EdGraph); + FBPFunctionClipboardData::StaticStruct()->ExportText(OutputString, &FuncData, nullptr, nullptr, 0, nullptr, false); + OutputString = FUNC_PREFIX + OutputString; + } + } + + if (!OutputString.IsEmpty()) + { + FPlatformApplicationMisc::ClipboardCopy(OutputString.GetCharArray().GetData()); + } +} + +bool SMyBlueprint::CanCopy() const +{ + if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) + { + return FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarAction->GetVariableName()) != INDEX_NONE; + } + if (FEdGraphSchemaAction_K2LocalVar* LocalVarAction = SelectionAsLocalVar()) + { + return FBlueprintEditorUtils::FindLocalVariable(Blueprint, LocalVarAction->GetVariableScope(), LocalVarAction->GetVariableName()) != nullptr; + } + if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) + { + return GraphAction->GraphType == EEdGraphSchemaAction_K2Graph::Function; + } + + return false; +} + +void SMyBlueprint::OnCut() +{ + OnCopy(); + OnDeleteEntry(); +} + +bool SMyBlueprint::CanCut() const +{ + return CanCopy() && CanDeleteEntry(); +} + +void SMyBlueprint::OnPasteGeneric() +{ + // prioritize pasting as a member variable if possible + if (CanPasteVariable()) + { + OnPasteVariable(); + } + else if (CanPasteLocalVariable()) + { + OnPasteLocalVariable(); + } + else if (CanPasteFunction()) + { + OnPasteFunction(); + } +} + +bool SMyBlueprint::CanPasteGeneric() +{ + return CanPasteVariable() || CanPasteLocalVariable() || CanPasteFunction(); +} + +void SMyBlueprint::OnPasteVariable() +{ + FString ClipboardText; + FPlatformApplicationMisc::ClipboardPaste(ClipboardText); + if (!ensure(ClipboardText.StartsWith(VAR_PREFIX, ESearchCase::CaseSensitive))) + { + return; + } + + FBPVariableDescription Description; + FStringOutputDevice Errors; + const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(VAR_PREFIX); + FBPVariableDescription::StaticStruct()->ImportText(Import, &Description, nullptr, 0, &Errors, FBPVariableDescription::StaticStruct()->GetName()); + if (Errors.IsEmpty()) + { + FBPVariableDescription NewVar = FBlueprintEditorUtils::DuplicateVariableDescription(Blueprint, Description); + if (NewVar.VarGuid.IsValid()) + { + FScopedTransaction Transaction(FText::Format(LOCTEXT("PasteVariable", "Paste Variable: {0}"), FText::FromName(NewVar.VarName))); + Blueprint->Modify(); + + Blueprint->NewVariables.Add(NewVar); + + // Potentially adjust variable names for any child blueprints + FBlueprintEditorUtils::ValidateBlueprintChildVariables(Blueprint, NewVar.VarName); + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); + + SelectItemByName(NewVar.VarName); + } + } +} + +void SMyBlueprint::OnPasteLocalVariable() +{ + TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); + if (PinnedEditor.IsValid()) + { + UEdGraph* FocusedGraph = PinnedEditor->GetFocusedGraph(); + if (FocusedGraph) + { + TArray FunctionEntry; + FocusedGraph->GetNodesOfClass(FunctionEntry); + + if (FunctionEntry.Num() == 1) + { + FString ClipboardText; + FPlatformApplicationMisc::ClipboardPaste(ClipboardText); + if (!ensure(ClipboardText.StartsWith(VAR_PREFIX, ESearchCase::CaseSensitive))) + { + return; + } + + FBPVariableDescription Description; + FStringOutputDevice Errors; + const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(VAR_PREFIX); + FBPVariableDescription::StaticStruct()->ImportText(Import, &Description, nullptr, 0, &Errors, FBPVariableDescription::StaticStruct()->GetName()); + if (Errors.IsEmpty()) + { + FBPVariableDescription NewVar = FBlueprintEditorUtils::DuplicateVariableDescription(Blueprint, Description); + if (NewVar.VarGuid.IsValid()) + { + FScopedTransaction Transaction(FText::Format(LOCTEXT("PasteLocalVariable", "Paste Local Variable: {0}"), FText::FromName(NewVar.VarName))); + + FunctionEntry[0]->Modify(); + FunctionEntry[0]->LocalVariables.Add(NewVar); + + // Potentially adjust variable names for any child blueprints + FBlueprintEditorUtils::ValidateBlueprintChildVariables(Blueprint, NewVar.VarName); + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); + + SelectItemByName(NewVar.VarName); + } + } + } + } + } +} + +bool SMyBlueprint::CanPasteVariable() const +{ + TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); + if (PinnedEditor.IsValid() && !PinnedEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewVariable)) + { + return false; + } + + FString ClipboardText; + FPlatformApplicationMisc::ClipboardPaste(ClipboardText); + if (ClipboardText.StartsWith(VAR_PREFIX, ESearchCase::CaseSensitive)) + { + FBPVariableDescription Description; + FStringOutputDevice Errors; + const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(VAR_PREFIX); + FBPVariableDescription::StaticStruct()->ImportText(Import, &Description, nullptr, 0, &Errors, FBPVariableDescription::StaticStruct()->GetName()); + + return Errors.IsEmpty(); + } + + return false; +} + +bool SMyBlueprint::CanPasteLocalVariable() const +{ + TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); + if (PinnedEditor.IsValid() && !PinnedEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewLocalVariable)) + { + return false; + } + + FString ClipboardText; + FPlatformApplicationMisc::ClipboardPaste(ClipboardText); + if (ClipboardText.StartsWith(VAR_PREFIX, ESearchCase::CaseSensitive)) + { + FBPVariableDescription Description; + FStringOutputDevice Errors; + const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(VAR_PREFIX); + FBPVariableDescription::StaticStruct()->ImportText(Import, &Description, nullptr, 0, &Errors, FBPVariableDescription::StaticStruct()->GetName()); + + return Errors.IsEmpty(); + } + + return false; +} + +void SMyBlueprint::OnPasteFunction() +{ + FString ClipboardText; + FPlatformApplicationMisc::ClipboardPaste(ClipboardText); + if (!ensure(ClipboardText.StartsWith(FUNC_PREFIX, ESearchCase::CaseSensitive))) + { + return; + } + + FBPFunctionClipboardData FuncData; + FStringOutputDevice Errors; + const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(FUNC_PREFIX); + FBPFunctionClipboardData::StaticStruct()->ImportText(Import, &FuncData, nullptr, 0, &Errors, FBPFunctionClipboardData::StaticStruct()->GetName()); + if (Errors.IsEmpty() && FuncData.IsValid()) + { + FScopedTransaction Transaction(LOCTEXT("PasteFunction", "Paste Function")); + + TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); + if (PinnedEditor.IsValid()) + { + Blueprint->Modify(); + UEdGraph* Graph = FuncData.CreateAndPopulateGraph(Blueprint, PinnedEditor->GetDefaultSchema()); + + if (Graph) + { + PinnedEditor->OpenDocument(Graph, FDocumentTracker::OpenNewDocument); + SelectItemByName(Graph->GetFName()); + Refresh(); + OnRequestRenameOnActionNode(); + } + else + { + Transaction.Cancel(); + } + } + } +} + +bool SMyBlueprint::CanPasteFunction() const +{ + TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); + if (PinnedEditor.IsValid() && !PinnedEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewFunctionGraph)) + { + return false; + } + + FString ClipboardText; + FPlatformApplicationMisc::ClipboardPaste(ClipboardText); + if (ClipboardText.StartsWith(FUNC_PREFIX, ESearchCase::CaseSensitive)) + { + FBPFunctionClipboardData FuncData; + FStringOutputDevice Errors; + const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(FUNC_PREFIX); + FBPFunctionClipboardData::StaticStruct()->ImportText(Import, &FuncData, nullptr, 0, &Errors, FBPFunctionClipboardData::StaticStruct()->GetName()); + + return Errors.IsEmpty(); + } + + return false; +} + void SMyBlueprint::OnResetItemFilter() { FilterBox->SetText(FText::GetEmpty()); diff --git a/Engine/Source/Editor/Kismet/Private/SMyBlueprint.h b/Engine/Source/Editor/Kismet/Private/SMyBlueprint.h index a96be2755da9..f205e86cdbb1 100644 --- a/Engine/Source/Editor/Kismet/Private/SMyBlueprint.h +++ b/Engine/Source/Editor/Kismet/Private/SMyBlueprint.h @@ -25,6 +25,7 @@ class UUserDefinedStruct; struct FEdGraphSchemaAction_K2Struct; struct FGraphActionNode; struct FGraphActionSort; +struct FReplaceNodeReferencesHelper; class FMyBlueprintCommands : public TCommands { @@ -42,7 +43,11 @@ public: TSharedPtr FocusNodeInNewTab; TSharedPtr ImplementFunction; TSharedPtr DeleteEntry; + TSharedPtr PasteVariable; + TSharedPtr PasteLocalVariable; + TSharedPtr PasteFunction; TSharedPtr GotoNativeVarDefinition; + TSharedPtr MoveToParent; // Add New Item /** Initialize commands */ virtual void RegisterCommands() override; @@ -61,6 +66,7 @@ public: /* SWidget interface */ virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime); + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent); /* Reset the last pin type settings to default. */ void ResetLastPinType(); @@ -134,6 +140,10 @@ public: /** Move the category before the target category */ bool MoveCategoryBeforeCategory( const FText& CategoryToMove, const FText& TargetCategory ); + + /** Callbacks for Paste Commands */ + void OnPasteGeneric(); + bool CanPasteGeneric(); private: /** Creates widgets for the graph schema actions */ TSharedRef OnCreateWidgetForAction(struct FCreateWidgetForActionData* const InCreateData); @@ -233,6 +243,19 @@ private: void OnDuplicateAction(); void GotoNativeCodeVarDefinition(); bool IsNativeVariable() const; + void OnMoveToParent(); + void OnMoveToParentCompleted(); + bool CanMoveToParent() const; + void OnCopy(); + bool CanCopy() const; + void OnCut(); + bool CanCut() const; + void OnPasteVariable(); + void OnPasteLocalVariable(); + bool CanPasteVariable() const; + bool CanPasteLocalVariable() const; + void OnPasteFunction(); + bool CanPasteFunction() const; /** Callback when the filter is changed, forces the action tree(s) to filter */ void OnFilterTextChanged( const FText& InFilterText ); @@ -263,6 +286,9 @@ private: /** Helper function indicating whehter we're in editing mode, and can modify the target blueprint */ bool IsEditingMode() const; private: + /** List of UI Commands for this scope */ + TSharedPtr CommandList; + /** Pointer back to the blueprint editor that owns us */ TWeakPtr BlueprintEditorPtr; @@ -300,6 +326,9 @@ private: /** The Kismet Inspector used to display properties: */ TWeakPtr Inspector; + /** A transient Replace helper used when moving variables to the parent class */ + TSharedPtr ReplaceHelper; + /** Flag to indicate whether or not we need to refresh the panel */ bool bNeedsRefresh; diff --git a/Engine/Source/Editor/Kismet/Private/SReplaceNodeReferences.cpp b/Engine/Source/Editor/Kismet/Private/SReplaceNodeReferences.cpp index b8f6f5af8bd9..9222f10e71b1 100644 --- a/Engine/Source/Editor/Kismet/Private/SReplaceNodeReferences.cpp +++ b/Engine/Source/Editor/Kismet/Private/SReplaceNodeReferences.cpp @@ -3,8 +3,10 @@ #include "SReplaceNodeReferences.h" #include "UObject/UObjectHash.h" #include "Widgets/Images/SImage.h" +#include "Widgets/Images/SLayeredImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" +#include "Widgets/Input/SCheckBox.h" #include "Engine/MemberReference.h" #include "EdGraphSchema_K2.h" #include "K2Node_Variable.h" @@ -13,6 +15,8 @@ #include "ObjectEditorUtils.h" #include "EditorCategoryUtils.h" #include "ScopedTransaction.h" +#include "ReplaceNodeReferencesHelper.h" +#include "Algo/RemoveIf.h" #define LOCTEXT_NAMESPACE "SNodeVariableReferences" @@ -62,7 +66,7 @@ public: .VAlign(VAlign_Center) .Padding(2.0f, 0.0f) [ - SNew(SImage) + SNew(SLayeredImage, GetSecondaryIcon(), GetSecondaryIconColor()) .Image(GetIcon()) .ColorAndOpacity(GetIconColor()) ] @@ -96,6 +100,17 @@ public: UEdGraphSchema_K2 const* K2Schema = GetDefault(); return K2Schema->GetPinTypeColor(PinType); } + + virtual const struct FSlateBrush* GetSecondaryIcon() const override + { + return FBlueprintEditorUtils::GetSecondaryIconFromPin(PinType); + } + + virtual FSlateColor GetSecondaryIconColor() const override + { + UEdGraphSchema_K2 const* K2Schema = GetDefault(); + return K2Schema->GetSecondaryPinTypeColor(PinType); + } // End of FTargetReplaceReferences interface public: @@ -110,6 +125,7 @@ void SReplaceNodeReferences::Construct(const FArguments& InArgs, TSharedPtr(this, &SReplaceNodeReferences::GetSecondarySourceReferenceIcon), + TAttribute(this, &SReplaceNodeReferences::GetSecondarySourceReferenceIconColor) + ) + .Image(this, &SReplaceNodeReferences::GetSourceReferenceIcon) + .ColorAndOpacity(this, &SReplaceNodeReferences::GetSourceReferenceIconColor) ] +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(this, &SReplaceNodeReferences::GetSourceDisplayText) + ] + ] + ] + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(3.0f, 5.0f) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("ReplaceWith", "Replace with:")) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Left) + [ + SNew(SBox) + .MinDesiredWidth(150.0f) + [ + SAssignNew( TargetReferencesComboBox, SComboButton ) + .OnGetMenuContent(this, &SReplaceNodeReferences::GetTargetMenuContent) + .ContentPadding(0.0f) + .ToolTipText(this, &SReplaceNodeReferences::GetTargetDisplayText) + .HasDownArrow(true) + .ButtonContent() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2.0f, 0.0f) + [ + SNew( + SLayeredImage, + TAttribute(this, &SReplaceNodeReferences::GetSecondaryTargetIcon), + TAttribute(this, &SReplaceNodeReferences::GetSecondaryTargetIconColor) + ) + .Image(this, &SReplaceNodeReferences::GetTargetIcon) + .ColorAndOpacity(this, &SReplaceNodeReferences::GetTargetIconColor) + ] + + +SHorizontalBox::Slot() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(this, &SReplaceNodeReferences::GetTargetDisplayText) ] + ] ] ] ] @@ -205,10 +243,32 @@ void SReplaceNodeReferences::Construct(const FArguments& InArgs, TSharedPtrGetBlueprintObj()->SkeletonGeneratedClass; - GatherAllAvailableBlueprintVariables(TargetClass); + GatherAllAvailableBlueprintVariables(TargetClass, true); + GatherAllAvailableBlueprintVariables(TargetClass, false); } void SReplaceNodeReferences::SetSourceVariable(FProperty* InProperty) { if (InProperty) { + FEdGraphPinType OldSourcePinType = SourcePinType; UEdGraphSchema_K2 const* K2Schema = GetDefault(); K2Schema->ConvertPropertyToPinType(InProperty, SourcePinType); SourceProperty = InProperty; - BlueprintVariableList.Empty(); - GatherAllAvailableBlueprintVariables(TargetClass); + // If the type has changed, reset the target + if (SourcePinType != OldSourcePinType) + { + SelectedTargetReferenceItem.Reset(); + } + + PossibleTargetVariableList.Empty(); + GatherAllAvailableBlueprintVariables(TargetClass, true); if (AvailableTargetReferencesTreeView.IsValid()) { @@ -267,6 +371,12 @@ void SReplaceNodeReferences::SetSourceVariable(FProperty* InProperty) { SourceProperty = nullptr; } + + // Reset the FindInBlueprints results + if (FindInBlueprints.IsValid()) + { + FindInBlueprints->ClearResults(); + } } SReplaceNodeReferences::~SReplaceNodeReferences() @@ -274,27 +384,41 @@ SReplaceNodeReferences::~SReplaceNodeReferences() } -TSharedRef SReplaceNodeReferences::GetMenuContent() +TSharedRef SReplaceNodeReferences::GetTargetMenuContent() { return SAssignNew(AvailableTargetReferencesTreeView, SReplaceReferencesTreeViewType) .ItemHeight(24) - .TreeItemsSource( &BlueprintVariableList ) - .OnSelectionChanged(this, &SReplaceNodeReferences::OnSelectionChanged) + .TreeItemsSource( &PossibleTargetVariableList ) + .OnSelectionChanged(this, &SReplaceNodeReferences::OnTargetSelectionChanged) .OnGenerateRow( this, &SReplaceNodeReferences::OnGenerateRow ) .OnGetChildren( this, &SReplaceNodeReferences::OnGetChildren ); } -void SReplaceNodeReferences::GatherAllAvailableBlueprintVariables(UClass* InTargetClass) +TSharedRef SReplaceNodeReferences::GetSourceMenuContent() +{ + return SAssignNew(AvailableSourceReferencesTreeView, SReplaceReferencesTreeViewType) + .ItemHeight(24) + .TreeItemsSource(&PossibleSourceVariableList) + .OnSelectionChanged(this, &SReplaceNodeReferences::OnSourceSelectionChanged) + .OnGenerateRow(this, &SReplaceNodeReferences::OnGenerateRow) + .OnGetChildren(this, &SReplaceNodeReferences::OnGetChildren); +} + +void SReplaceNodeReferences::GatherAllAvailableBlueprintVariables(UClass* InTargetClass, bool bForTarget) { if (InTargetClass == nullptr) { return; } - GatherAllAvailableBlueprintVariables(InTargetClass->GetSuperClass()); + + if (bForTarget) + { + GatherAllAvailableBlueprintVariables(InTargetClass->GetSuperClass(), bForTarget); + } TMap > CategoryMap; - UObject* PathObject = InTargetClass->ClassGeneratedBy? InTargetClass->ClassGeneratedBy : InTargetClass; + UObject* PathObject = InTargetClass->ClassGeneratedBy ? InTargetClass->ClassGeneratedBy : InTargetClass; TSharedPtr< FTargetCategoryReplaceReferences > BlueprintCategory = MakeShareable(new FTargetCategoryReplaceReferences(FText::FromString(PathObject->GetPathName()))); for (TFieldIterator PropertyIt(InTargetClass, EFieldIteratorFlags::ExcludeSuper); PropertyIt; ++PropertyIt) { @@ -364,7 +488,7 @@ void SReplaceNodeReferences::GatherAllAvailableBlueprintVariables(UClass* InTarg FEdGraphPinType Type; K2Schema->ConvertPropertyToPinType(Property, VariableItem->PinType); - if (VariableItem->PinType == SourcePinType) + if (!bForTarget || VariableItem->PinType == SourcePinType) { CategoryReference->Children.Add(VariableItem); @@ -377,9 +501,11 @@ void SReplaceNodeReferences::GatherAllAvailableBlueprintVariables(UClass* InTarg } } + TArray& CurrentList = bForTarget ? PossibleTargetVariableList : PossibleSourceVariableList; + if (BlueprintCategory->Children.Num()) { - BlueprintVariableList.Add(BlueprintCategory); + CurrentList.Add(BlueprintCategory); // Sort markers struct FCompareCategoryTitles { @@ -398,6 +524,21 @@ void SReplaceNodeReferences::GatherAllAvailableBlueprintVariables(UClass* InTarg }; BlueprintCategory->Children.Sort(FCompareCategoryTitles()); } + + // Conditionally add "No variables found" + if (TargetClass == InTargetClass && CurrentList.Num() == 0) + { + if (bForTarget) + { + TSharedPtr< FTargetCategoryReplaceReferences > NoneFound = MakeShared(LOCTEXT("NoReplacements", "No viable replacements found!")); + CurrentList.Add(NoneFound); + } + else + { + TSharedPtr< FTargetCategoryReplaceReferences > NoneFound = MakeShared(LOCTEXT("NoSources", "No replaceable variables found!")); + CurrentList.Add(NoneFound); + } + } } TSharedRef SReplaceNodeReferences::OnGenerateRow(FTreeViewItem InItem, const TSharedRef& OwnerTable) @@ -415,7 +556,17 @@ void SReplaceNodeReferences::OnGetChildren( FTreeViewItem InItem, TArray< FTreeV FReply SReplaceNodeReferences::OnFindAll() { - OnSubmitSearchQuery(false); + if (bFindWithinBlueprint) + { + OnSubmitSearchQuery(false); + } + else + { + FFindInBlueprintCachingOptions CachingOptions; + CachingOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; + CachingOptions.OnFinished = FSimpleDelegate::CreateSP(this, &SReplaceNodeReferences::OnSubmitSearchQuery, false); + FindInBlueprints->CacheAllBlueprints(CachingOptions); + } return FReply::Handled(); } @@ -423,82 +574,95 @@ FReply SReplaceNodeReferences::OnFindAndReplaceAll() { if (SelectedTargetReferenceItem.IsValid()) { - FFindInBlueprintCachingOptions CachingOptions; - CachingOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; - CachingOptions.OnFinished = FSimpleDelegate::CreateSP(this, &SReplaceNodeReferences::OnSubmitSearchQuery, true); - FindInBlueprints->CacheAllBlueprints(CachingOptions); + if (bFindWithinBlueprint) + { + OnSubmitSearchQuery(true); + } + else + { + FFindInBlueprintCachingOptions CachingOptions; + CachingOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; + CachingOptions.OnFinished = FSimpleDelegate::CreateSP(this, &SReplaceNodeReferences::OnSubmitSearchQuery, true); + FindInBlueprints->CacheAllBlueprints(CachingOptions); + } } return FReply::Handled(); } void SReplaceNodeReferences::OnSubmitSearchQuery(bool bFindAndReplace) { - FString SearchTerm; - - FMemberReference SourceVariableReference; - SourceVariableReference.SetFromField(SourceProperty, true, SourceProperty->GetOwnerClass()); - SearchTerm = SourceVariableReference.GetReferenceSearchString(SourceProperty->GetOwnerClass()); - - FOnSearchComplete OnSearchComplete; - if (bFindAndReplace) + if (HasValidSource()) { - OnSearchComplete = FOnSearchComplete::CreateSP(this, &SReplaceNodeReferences::FindAllReplacementsComplete); - } + FString SearchTerm; - FStreamSearchOptions SearchOptions; - SearchOptions.ImaginaryDataFilter = ESearchQueryFilter::NodesFilter; - SearchOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; - FindInBlueprints->MakeSearchQuery(SearchTerm, false, SearchOptions, OnSearchComplete); + FMemberReference SourceVariableReference; + SourceVariableReference.SetFromField(SourceProperty, true, SourceProperty->GetOwnerClass()); + SearchTerm = SourceVariableReference.GetReferenceSearchString(SourceProperty->GetOwnerClass()); + + FOnSearchComplete OnSearchComplete; + if (bFindAndReplace) + { + OnSearchComplete = FOnSearchComplete::CreateSP(this, &SReplaceNodeReferences::FindAllReplacementsComplete); + } + + FStreamSearchOptions SearchOptions; + SearchOptions.ImaginaryDataFilter = ESearchQueryFilter::NodesFilter; + SearchOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; + FindInBlueprints->MakeSearchQuery(SearchTerm, bFindWithinBlueprint, SearchOptions, OnSearchComplete); + } } void SReplaceNodeReferences::FindAllReplacementsComplete(TArray& InRawDataList) { + if (InRawDataList.Num() == 0) + { + return; + } + + if (!bFindWithinBlueprint) + { + SReplaceReferencesConfirmation::EDialogResponse Response = SReplaceReferencesConfirmation::CreateModal(&InRawDataList); + + if (Response == SReplaceReferencesConfirmation::EDialogResponse::Cancel) + { + return; + } + } + if (SelectedTargetReferenceItem.IsValid()) { FMemberReference VariableReference; if (SelectedTargetReferenceItem->GetMemberReference(VariableReference)) { - FText TransactionTitle = FText::Format(LOCTEXT("FindReplaceAllTransaction", "{0} replaced with {1}"), FText::FromString(SourceProperty->GetName()), FText::FromName(VariableReference.GetMemberName())); - const FScopedTransaction Transaction( TransactionTitle ); - BlueprintEditor.Pin()->GetBlueprintObj()->Modify(); - - TArray< UBlueprint* > BlueprintsModified; - for (FImaginaryFiBDataSharedPtr ImaginaryData : InRawDataList) + TSharedPtr PinnedEditor = BlueprintEditor.Pin(); + if (PinnedEditor.IsValid()) { - BlueprintsModified.AddUnique(ImaginaryData->GetBlueprint()); - UObject* Node = ImaginaryData->GetObject(ImaginaryData->GetBlueprint()); - UK2Node_Variable* VariableNode = Cast(Node); - if (ensure(VariableNode)) + UBlueprint* Blueprint = PinnedEditor->GetBlueprintObj(); + if (Blueprint) { - VariableNode->Modify(); - if (VariableNode->VariableReference.IsLocalScope() || VariableNode->VariableReference.IsSelfContext()) + const FScopedTransaction Transaction(GetTransactionTitle(VariableReference)); + Blueprint->Modify(); + + FReplaceNodeReferencesHelper::ReplaceReferences(VariableReference, Blueprint, InRawDataList); + + if (SourceProperty && bShowReplacementsWhenFinished) { - VariableNode->VariableReference = VariableReference; + TSharedPtr GlobalResults = FFindInBlueprintSearchManager::Get().GetGlobalFindResults(); + if (GlobalResults) + { + FStreamSearchOptions SearchOptions; + SearchOptions.ImaginaryDataFilter = ESearchQueryFilter::NodesFilter; + SearchOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; + GlobalResults->MakeSearchQuery(VariableReference.GetReferenceSearchString(SourceProperty->GetOwnerClass()), bFindWithinBlueprint, SearchOptions); + } } - else - { - UBlueprint* Blueprint = BlueprintEditor.Pin()->GetBlueprintObj(); - VariableNode->VariableReference.SetFromField(VariableReference.ResolveMember(Blueprint), Blueprint->GeneratedClass); - } - VariableNode->ReconstructNode(); } } - - for (UBlueprint* Blueprint : BlueprintsModified) - { - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); - FFindInBlueprintSearchManager::Get().AddOrUpdateBlueprintSearchMetadata(Blueprint); - } - - FStreamSearchOptions SearchOptions; - SearchOptions.ImaginaryDataFilter = ESearchQueryFilter::NodesFilter; - SearchOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; - FindInBlueprints->MakeSearchQuery(VariableReference.GetReferenceSearchString(SourceProperty->GetOwnerClass()), false, SearchOptions); } } } -void SReplaceNodeReferences::OnSelectionChanged(FTreeViewItem Selection, ESelectInfo::Type SelectInfo) +void SReplaceNodeReferences::OnTargetSelectionChanged(FTreeViewItem Selection, ESelectInfo::Type SelectInfo) { // When the user is navigating, do not act upon the selection change if(SelectInfo == ESelectInfo::OnNavigation || (Selection.IsValid() && Selection->IsCategory())) @@ -510,18 +674,40 @@ void SReplaceNodeReferences::OnSelectionChanged(FTreeViewItem Selection, ESelect TargetReferencesComboBox->SetIsOpen(false); } +void SReplaceNodeReferences::OnSourceSelectionChanged(FTreeViewItem Selection, ESelectInfo::Type SelectInfo) +{ + // When the user is navigating, do not act upon the selection change + if (SelectInfo == ESelectInfo::OnNavigation || (Selection.IsValid() && Selection->IsCategory())) + { + return; + } + + FMemberReference NewSource; + if (Selection->GetMemberReference(NewSource)) + { + SetSourceVariable(NewSource.ResolveMember(TargetClass)); + } + + SourceReferencesComboBox->SetIsOpen(false); +} + FText SReplaceNodeReferences::GetSourceDisplayText() const { - if (SourceProperty == nullptr) + if (!HasValidSource()) { - return FText::FromString(TEXT("Hello World!")); + return LOCTEXT("UnselectedSourceReference", "Please select a source reference!"); } return FText::FromString(SourceProperty->GetName()); } const FSlateBrush* SReplaceNodeReferences::GetSourceReferenceIcon() const { - return FBlueprintEditorUtils::GetIconFromPin(SourcePinType); + if (HasValidSource()) + { + return FBlueprintEditorUtils::GetIconFromPin(SourcePinType); + } + + return nullptr; } FSlateColor SReplaceNodeReferences::GetSourceReferenceIconColor() const @@ -530,6 +716,177 @@ FSlateColor SReplaceNodeReferences::GetSourceReferenceIconColor() const return K2Schema->GetPinTypeColor(SourcePinType); } +const FSlateBrush* SReplaceNodeReferences::GetSecondarySourceReferenceIcon() const +{ + if (HasValidSource()) + { + return FBlueprintEditorUtils::GetSecondaryIconFromPin(SourcePinType); + } + + return nullptr; +} + +FSlateColor SReplaceNodeReferences::GetSecondarySourceReferenceIconColor() const +{ + UEdGraphSchema_K2 const* K2Schema = GetDefault(); + return K2Schema->GetSecondaryPinTypeColor(SourcePinType); +} + +bool SReplaceNodeReferences::HasValidSource() const +{ + return (SourceProperty != nullptr); +} + +FText SReplaceNodeReferences::GetFindAllButtonText() const +{ + if (bFindWithinBlueprint) + { + const UBlueprint* BlueprintObj = BlueprintEditor.Pin()->GetBlueprintObj(); + FFormatNamedArguments Args; + Args.Add(TEXT("BP"), BlueprintObj ? FText::FromString(BlueprintObj->GetName()) : FText::FromString(TEXT(""))); + return FText::Format(LOCTEXT("FindLocal", "Find References in {BP}"), Args); + } + else + { + return LOCTEXT("FindAll", "Find All References"); + } +} + +FText SReplaceNodeReferences::GetFindAndReplaceAllButtonText() const +{ + if (bFindWithinBlueprint) + { + const UBlueprint* BlueprintObj = BlueprintEditor.Pin()->GetBlueprintObj(); + FFormatNamedArguments Args; + Args.Add(TEXT("BP"), BlueprintObj ? FText::FromString(BlueprintObj->GetName()) : FText::FromString(TEXT(""))); + return FText::Format(LOCTEXT("ReplaceLocal", "Find and Replace References in {BP}"), Args); + } + else + { + return LOCTEXT("ReplaceAll", "Find and Replace All References"); + } +} + +FText SReplaceNodeReferences::GetFindAndReplaceToolTipText(bool bFindAndReplace) const +{ + if (CanBeginSearch(bFindAndReplace)) + { + return FText::GetEmpty(); + } + else + { + if (!HasValidSource()) + { + return LOCTEXT("PickSourceVariable", "Pick a source variable from the menu!"); + } + else if (bFindAndReplace && !SelectedTargetReferenceItem.IsValid()) + { + return LOCTEXT("PickTarget", "Pick a target variable to replace with from the menu!"); + } + else + { + return LOCTEXT("SearchInProgress", "A search is already in progress!"); + } + } +} + +bool SReplaceNodeReferences::CanBeginSearch(bool bFindAndReplace) const +{ + bool bCanSearch = HasValidSource() && !IsSearchInProgress(); + + if (bFindAndReplace) + { + bCanSearch = bCanSearch && SelectedTargetReferenceItem.IsValid(); + } + + return bCanSearch; +} + +void SReplaceNodeReferences::OnLocalCheckBoxChanged(ECheckBoxState Checked) +{ + bFindWithinBlueprint = (Checked == ECheckBoxState::Checked); +} + +ECheckBoxState SReplaceNodeReferences::GetLocalCheckBoxState() const +{ + if (FindInBlueprints.IsValid()) + { + return bFindWithinBlueprint ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + } + + return ECheckBoxState::Unchecked; +} + +FText SReplaceNodeReferences::GetShowReplacementsCheckBoxLabelText() const +{ + return LOCTEXT("ShowReplacements", "Show Replacements when complete?"); +} + +void SReplaceNodeReferences::OnShowReplacementsCheckBoxChanged(ECheckBoxState Checked) +{ + bShowReplacementsWhenFinished = (Checked == ECheckBoxState::Checked); +} + +ECheckBoxState SReplaceNodeReferences::GetShowReplacementsCheckBoxState() const +{ + return bShowReplacementsWhenFinished ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +FText SReplaceNodeReferences::GetLocalCheckBoxLabelText() const +{ + if (BlueprintEditor.IsValid()) + { + const UBlueprint* BlueprintObj = BlueprintEditor.Pin()->GetBlueprintObj(); + + FFormatNamedArguments Args; + Args.Add(TEXT("BlueprintClass"), BlueprintObj ? FText::FromString(BlueprintObj->GetName()) : FText::GetEmpty()); + return FText::Format(LOCTEXT("OnlyLocal", "Only show and replace results from {BlueprintClass} class?"), Args); + } + + return FText::GetEmpty(); +} + +FText SReplaceNodeReferences::GetStatusText() const +{ + if (IsSearchInProgress()) + { + if (FFindInBlueprintSearchManager::Get().IsCacheInProgress()) + { + return LOCTEXT("Caching", "Caching..."); + } + else + { + return LOCTEXT("Searching", "Searching..."); + } + } + + return FText::GetEmpty(); +} + +bool SReplaceNodeReferences::IsSearchInProgress() const +{ + return FindInBlueprints.IsValid() && + (FindInBlueprints->IsSearchInProgress() || FFindInBlueprintSearchManager::Get().IsCacheInProgress()); +} + +FText SReplaceNodeReferences::GetTransactionTitle(const FMemberReference& TargetReference) const +{ + FText BlueprintName; + + if (bFindWithinBlueprint && BlueprintEditor.IsValid()) + { + const UBlueprint* BlueprintObj = BlueprintEditor.Pin()->GetBlueprintObj(); + + BlueprintName = BlueprintObj ? FText::FromString(BlueprintObj->GetName()) : FText::GetEmpty(); + } + + FFormatNamedArguments Args; + Args.Add(TEXT("Source"), FText::FromString(SourceProperty->GetName())); + Args.Add(TEXT("Target"), FText::FromName(TargetReference.GetMemberName())); + Args.Add(TEXT("Scope"), bFindWithinBlueprint ? BlueprintName : LOCTEXT("AllBlueprints", "All Blueprints")); + return FText::Format(LOCTEXT("FindReplaceAllTransaction", "{Source} replaced with {Target} in {Scope}"), Args); +} + FText SReplaceNodeReferences::GetTargetDisplayText() const { FText ReturnText = LOCTEXT("UnselectedTargetReference", "Please select a target reference!"); @@ -563,4 +920,202 @@ FSlateColor SReplaceNodeReferences::GetTargetIconColor() const return ReturnColor; } +const FSlateBrush* SReplaceNodeReferences::GetSecondaryTargetIcon() const +{ + const FSlateBrush* ReturnBrush = nullptr; + + if (SelectedTargetReferenceItem.IsValid()) + { + ReturnBrush = SelectedTargetReferenceItem->GetSecondaryIcon(); + } + return ReturnBrush; +} + +FSlateColor SReplaceNodeReferences::GetSecondaryTargetIconColor() const +{ + FSlateColor ReturnColor = FLinearColor::White; + + if (SelectedTargetReferenceItem.IsValid()) + { + ReturnColor = SelectedTargetReferenceItem->GetSecondaryIconColor(); + } + return ReturnColor; +} + +//////////////////////////////////////////////// +// Replace References Confirmation + +TSharedRef FReplaceConfirmationListItem::CreateWidget() +{ + return SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SCheckBox) + .IsChecked_Raw(this, &FReplaceConfirmationListItem::IsChecked) + .OnCheckStateChanged_Raw(this, &FReplaceConfirmationListItem::OnCheckStateChanged) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(5.0f, 3.0f) + [ + SNew(STextBlock) + .Text(Blueprint ? FText::FromString(Blueprint->GetPathName()) : FText::FromString(TEXT(""))) + ]; +} + +void FReplaceConfirmationListItem::OnCheckStateChanged(ECheckBoxState State) +{ + bReplace = State == ECheckBoxState::Checked; +} + +ECheckBoxState FReplaceConfirmationListItem::IsChecked() const +{ + return bReplace ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void SReplaceReferencesConfirmation::Construct(const FArguments& InArgs) +{ + RawFindData = InArgs._FindResults; + Response = EDialogResponse::Cancel; + + if (RawFindData) + { + for (FImaginaryFiBDataSharedPtr Data : *RawFindData) + { + const UBlueprint* DataBlueprint = Data->GetBlueprint(); + + const FListViewItem* FoundItem = AffectedBlueprints.FindByPredicate([DataBlueprint](FListViewItem Item) + { + return Item->GetBlueprint() == DataBlueprint; + }); + + if (!FoundItem) + { + AffectedBlueprints.Add(MakeShared(Data->GetBlueprint())); + } + } + } + + ChildSlot + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("ReplaceIn", "Replace references in the following Blueprints:")) + ] + + +SVerticalBox::Slot() + .Padding(5.0f, 5.0f) + [ + SNew(SBox) + .MinDesiredHeight(100.0f) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SNew(SListView) + .ItemHeight(24.0f) + .ListItemsSource(&AffectedBlueprints) + .SelectionMode(ESelectionMode::None) + .OnGenerateRow(this, &SReplaceReferencesConfirmation::OnGenerateRow) + ] + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1.0f) + .HAlign(HAlign_Right) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + .VAlign(VAlign_Bottom) + .Padding(5.0f, 3.0f) + [ + SNew(SButton) + .Text(LOCTEXT("Confirm", "Confirm")) + .OnClicked(this, &SReplaceReferencesConfirmation::CloseWindow, EDialogResponse::Confirm) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + .VAlign(VAlign_Bottom) + .Padding(5.0f, 3.0f) + [ + SNew(SButton) + .Text(LOCTEXT("Cancel", "Cancel")) + .OnClicked(this, &SReplaceReferencesConfirmation::CloseWindow, EDialogResponse::Cancel) + ] + ] + ] + ]; +} + +SReplaceReferencesConfirmation::EDialogResponse SReplaceReferencesConfirmation::CreateModal(TArray* InFindResults) +{ + TSharedPtr Window; + TSharedPtr Widget; + + Window = SNew(SWindow) + .Title(LOCTEXT("ConfirmReplace", "Confirm Replacements")) + .SizingRule(ESizingRule::UserSized) + .MinWidth(400.f) + .MinHeight(300.f) + .SupportsMaximize(true) + .SupportsMinimize(false) + [ + SNew(SBorder) + .Padding(4.f) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SAssignNew(Widget, SReplaceReferencesConfirmation) + .FindResults(InFindResults) + ] + ]; + + Widget->MyWindow = Window; + + GEditor->EditorAddModalWindow(Window.ToSharedRef()); + + return Widget->Response; +} + +TSharedRef SReplaceReferencesConfirmation::OnGenerateRow(FListViewItem Item, const TSharedRef& OwnerTable) const +{ + return SNew(STableRow, OwnerTable) + [ + Item->CreateWidget() + ]; +} + +FReply SReplaceReferencesConfirmation::CloseWindow(EDialogResponse InResponse) +{ + if (InResponse == EDialogResponse::Confirm && RawFindData) + { + // Filter the Results if necessary + for (FListViewItem Item : AffectedBlueprints) + { + if (!Item->ShouldReplace()) + { + const UBlueprint* BP = Item->GetBlueprint(); + RawFindData->SetNum(Algo::RemoveIf(*RawFindData, [BP](FImaginaryFiBDataSharedPtr Data) { return Data->GetBlueprint() == BP; })); + } + } + } + + Response = InResponse; + MyWindow->RequestDestroyWindow(); + return FReply::Handled(); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/Kismet/Private/SReplaceNodeReferences.h b/Engine/Source/Editor/Kismet/Private/SReplaceNodeReferences.h index 8d9778404fc9..6e35076caba7 100644 --- a/Engine/Source/Editor/Kismet/Private/SReplaceNodeReferences.h +++ b/Engine/Source/Editor/Kismet/Private/SReplaceNodeReferences.h @@ -50,6 +50,12 @@ public: /** Returns the Icon Color of this reference */ virtual FSlateColor GetIconColor() const { return FLinearColor::White; } + /** Returns the Secondary Icon representing this reference */ + virtual const struct FSlateBrush* GetSecondaryIcon() const { return nullptr; } + + /** Returns the Secondary Icon Color of this reference */ + virtual FSlateColor GetSecondaryIconColor() const { return FLinearColor::White; } + public: /** Children members to sub-list in the tree */ TArray< TSharedPtr > Children; @@ -81,18 +87,6 @@ public: protected: - /** Callback for determining if the SourceReference is visible */ - EVisibility GetSourceReferenceVisibility() const - { - return SourceProperty == nullptr? EVisibility::Collapsed : EVisibility::Visible; - } - - /** Callback for determining if the message to inform the user a SourceReference is needed is visible */ - EVisibility GetPickSourceReferenceVisibility() const - { - return SourceProperty == nullptr? EVisibility::Visible : EVisibility::Collapsed; - } - /* Called when a new row is being generated */ TSharedRef OnGenerateRow(FTreeViewItem InItem, const TSharedRef& OwnerTable); @@ -100,10 +94,16 @@ protected: void OnGetChildren( FTreeViewItem InItem, TArray< FTreeViewItem >& OutChildren ); /* Returns the menu content for the Target Reference drop down section of the combo button */ - TSharedRef GetMenuContent(); + TSharedRef GetTargetMenuContent(); + + /* Returns the menu content for the Source Reference drop down section of the combo button */ + TSharedRef GetSourceMenuContent(); - /** Callback when selection in the combo button has changed */ - void OnSelectionChanged(FTreeViewItem Selection, ESelectInfo::Type SelectInfo); + /** Callback when selection in the target combo button has changed */ + void OnTargetSelectionChanged(FTreeViewItem Selection, ESelectInfo::Type SelectInfo); + + /** Callback when selection in the target combo button has changed */ + void OnSourceSelectionChanged(FTreeViewItem Selection, ESelectInfo::Type SelectInfo); /** * Submits a search query and potentially does a mass replace on results @@ -121,8 +121,13 @@ protected: /** Callback when the search for "Find and Replace All" is complete so that the replacements can begin */ void FindAllReplacementsComplete(TArray& InRawDataList); - /** Recursively gathers all available Blueprint Variable references to replace with */ - void GatherAllAvailableBlueprintVariables(UClass* InTargetClass); + /** + * Gathers all Blueprint Variable references from the Target Class + * + * @param InTargetClass Class to gather variables from + * @param bForTarget TRUE if we need to check that the type is the same as the source and recurse parent classes, FALSE if picking a new source + */ + void GatherAllAvailableBlueprintVariables(UClass* InTargetClass, bool bForTarget); /** Returns the display text for the target reference */ FText GetTargetDisplayText() const; @@ -132,6 +137,12 @@ protected: /** Returns the icon color for the target reference */ FSlateColor GetTargetIconColor() const; + + /** Returns the icon for the target reference */ + const struct FSlateBrush* GetSecondaryTargetIcon() const; + + /** Returns the icon color for the target reference */ + FSlateColor GetSecondaryTargetIconColor() const; /** Returns the display text for the source reference */ FText GetSourceDisplayText() const; @@ -141,22 +152,83 @@ protected: /** Returns the icon color for the source reference */ FSlateColor GetSourceReferenceIconColor() const; + + /** Returns the secondary icon for the source reference */ + const FSlateBrush* GetSecondarySourceReferenceIcon() const; + + /** Returns the secondary icon color for the source reference */ + FSlateColor GetSecondarySourceReferenceIconColor() const; + + /** Returns whether the source property is a valid property to search for and replace */ + bool HasValidSource() const; + + /** Returns the text for the Find All button */ + FText GetFindAllButtonText() const; + + /** Returns the text for the Find and Replace All button */ + FText GetFindAndReplaceAllButtonText() const; + + /** Returns tool tip text for the "Find All" and "Find And Replace All" buttons */ + FText GetFindAndReplaceToolTipText(bool bFindAndReplace) const; + + /** Returns whether or not a search can be initiated right now */ + bool CanBeginSearch(bool bFindAndReplace) const; + + /** Callback for when the "Only Local Results" CheckBox is changed */ + void OnLocalCheckBoxChanged(ECheckBoxState Checked); + + /** Returns the current state of the "Only Local Results" CheckBox */ + ECheckBoxState GetLocalCheckBoxState() const; + + /** Returns the label text for the "OnlyLocalResults" CheckBox */ + FText GetLocalCheckBoxLabelText() const; + + /** Callback for when the "Show When Finished" CheckBox is changed */ + void OnShowReplacementsCheckBoxChanged(ECheckBoxState Checked); + + /** Returns the current state of the "Show When Finished" CheckBox */ + ECheckBoxState GetShowReplacementsCheckBoxState() const; + + /** Returns the label text for the "Show When Finished" CheckBox */ + FText GetShowReplacementsCheckBoxLabelText() const; + + /** Returns the text to display in the bottom right corner of the window */ + FText GetStatusText() const; + + /** Determines whether a search is actively in progress */ + bool IsSearchInProgress() const; + + /** + * Builds a title for the transaction of Replacing references + * + * @param TargetReference The Target variable that will be replacing the source + */ + FText GetTransactionTitle(const FMemberReference& TargetReference) const; protected: /** Combo box for selecting the target reference */ TSharedPtr< SComboButton > TargetReferencesComboBox; - /** Tree view for display available target references */ + /** Tree view for displaying available target references */ TSharedPtr< SReplaceReferencesTreeViewType > AvailableTargetReferencesTreeView; - /** List of items used for the root of the tree */ - TArray BlueprintVariableList; + /** List of items used for the root of the target picker tree */ + TArray< FTreeViewItem > PossibleTargetVariableList; + + /** Combo box for selecting the source reference */ + TSharedPtr< SComboButton > SourceReferencesComboBox; + + /** Tree view for displaying available source references */ + TSharedPtr< SReplaceReferencesTreeViewType > AvailableSourceReferencesTreeView; + + /** List of items used for the root of the source picker tree */ + TArray< FTreeViewItem > PossibleSourceVariableList; /** Target SKEL_ class that is being referenced by this window */ UClass* TargetClass; /** Blueprint editor that owns this window */ - TWeakPtr BlueprintEditor; + TWeakPtr< FBlueprintEditor > BlueprintEditor; /** Cached SourcePinType for the property the user wants to replace */ FEdGraphPinType SourcePinType; @@ -169,4 +241,87 @@ protected: /** Currently selected target reference */ FTreeViewItem SelectedTargetReferenceItem; + + /** Whether to search only within the currently open blueprint */ + bool bFindWithinBlueprint; + + /** Whether to show a FindResults tab for the replacements after completing action */ + bool bShowReplacementsWhenFinished; }; + +/** List item for the Confirmation Dialog */ +class FReplaceConfirmationListItem +{ +public: + FReplaceConfirmationListItem(const UBlueprint* InBlueprint) : Blueprint(InBlueprint), bReplace(true) {} + + /** Gets the blueprint this Item refers to */ + const UBlueprint* GetBlueprint() const { return Blueprint; } + + /** Gets whether the references in this blueprint will be replace when the modal is closed */ + bool ShouldReplace() const { return bReplace; } + + /** Sets whether the references in this blueprint will be replace when the modal is closed */ + void SetShouldReplace(bool bInReplace) { bReplace = bInReplace; } + + /** Sets whether the references in this blueprint will be replace when the modal is closed */ + TSharedRef CreateWidget(); +private: + /** Callback for Checkbox status changed */ + void OnCheckStateChanged(ECheckBoxState State); + + /** Callback for getting Checkbox State */ + ECheckBoxState IsChecked() const; + +private: + /** The Blueprint this item represents*/ + const UBlueprint* Blueprint; + + /** Whether or not to replace references in this blueprint */ + bool bReplace; +}; + +/** Widget for the ReplaceNodeReferences Confirmation Dialog */ +class SReplaceReferencesConfirmation : public SCompoundWidget +{ +private: + typedef TSharedPtr FListViewItem; +public: + enum class EDialogResponse { Confirm, Cancel }; + + SLATE_BEGIN_ARGS(SReplaceReferencesConfirmation) + : _FindResults(nullptr) + {} + + SLATE_ARGUMENT(TArray< FImaginaryFiBDataSharedPtr >*, FindResults) + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs); + + /** + * Creates a Confirmation Modal, this function will not return until the Dialog is closed + * + * @param InFindResults The Results to filter with this dialog + * @return A constructed Confirmation Widget + */ + static EDialogResponse CreateModal(TArray< FImaginaryFiBDataSharedPtr >* InFindResults); + +private: + /** Generates a row for a List Item */ + TSharedRef OnGenerateRow(FListViewItem Item, const TSharedRef& OwnerTable) const; + + /** Closes the window with the given response */ + FReply CloseWindow(EDialogResponse InResponse); +private: + /** The list of unique blueprints that are affected */ + TArray< FListViewItem > AffectedBlueprints; + + /** The find results to modify */ + TArray< FImaginaryFiBDataSharedPtr >* RawFindData; + + /** The User's response */ + EDialogResponse Response; + + /** Window to close when dialog completed */ + TSharedPtr MyWindow; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp b/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp index 7eb7da40bf09..69a866d69578 100644 --- a/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp +++ b/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp @@ -79,6 +79,9 @@ #include "Kismet2/CompilerResultsLog.h" #include "Dialogs/Dialogs.h" #include "Subsystems/AssetEditorSubsystem.h" +#include "Subsystems/PanelExtensionSubsystem.h" +#include "SCSEditorExtensionContext.h" +#include "ISCSEditorUICustomization.h" #include "Logging/MessageLog.h" @@ -830,7 +833,7 @@ FSCSEditorTreeNodePtrType FSCSEditorTreeNode::FactoryNodeFromComponent(UActorCom bool bComponentIsInAnInstance = false; AActor* Owner = InComponent->GetOwner(); - if ((Owner != nullptr) && !Owner->HasAllFlags(RF_ClassDefaultObject)) + if ((Owner != nullptr) && !Owner->HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject)) { bComponentIsInAnInstance = true; } @@ -1237,10 +1240,11 @@ FText FSCSEditorTreeNodeInstanceAddedComponent::GetDisplayName() const void FSCSEditorTreeNodeInstanceAddedComponent::RemoveMeAsChild() { USceneComponent* ChildInstance = Cast(GetComponentTemplate()); - check(ChildInstance != nullptr); - - // Handle detachment at the instance level - ChildInstance->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + if (ensure(ChildInstance)) + { + // Handle detachment at the instance level + ChildInstance->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + } } void FSCSEditorTreeNodeInstanceAddedComponent::OnCompleteRename(const FText& InNewName) @@ -1298,7 +1302,7 @@ FSCSEditorTreeNodeComponent::FSCSEditorTreeNodeComponent(UActorComponent* InComp AActor* Owner = InComponentTemplate->GetOwner(); if (Owner != nullptr) { - ensureMsgf(Owner->HasAllFlags(RF_ClassDefaultObject), TEXT("Use a different node class for instanced components")); + ensureMsgf(Owner->HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject), TEXT("Use a different node class for instanced components")); } } @@ -3910,10 +3914,10 @@ void SSCSEditor::Construct( const FArguments& InArgs ) ); TSharedPtr HeaderBox; - TSharedPtr SearchBar = - SAssignNew(FilterBox, SSearchBox) - .HintText(EditorMode == EComponentEditorMode::ActorInstance ? LOCTEXT("SearchComponentsHint", "Search Components") : LOCTEXT("SearchHint", "Search")) - .OnTextChanged(this, &SSCSEditor::OnFilterTextChanged); + TSharedPtr SearchBar = SAssignNew(FilterBox, SSearchBox) + .HintText(EditorMode == EComponentEditorMode::ActorInstance ? LOCTEXT("SearchComponentsHint", "Search Components") : LOCTEXT("SearchHint", "Search")) + .OnTextChanged(this, &SSCSEditor::OnFilterTextChanged) + .Visibility(this, &SSCSEditor::GetComponentsFilterBoxVisibility); const bool bInlineSearchBarWithButtons = (EditorMode == EComponentEditorMode::BlueprintSCS); @@ -3978,12 +3982,25 @@ void SSCSEditor::Construct( const FArguments& InArgs ) ]; + USCSEditorExtensionContext* ExtensionContext = NewObject(); + ExtensionContext->SCSEditor = SharedThis(this); + ExtensionContext->AddToRoot(); Contents = SNew(SVerticalBox) + SVerticalBox::Slot() .Padding(0.0f) [ SNew(SVerticalBox) + + + SVerticalBox::Slot() + .Padding(0) + .AutoHeight() + [ + SAssignNew(ExtensionPanel, SExtensionPanel) + .ExtensionPanelID("SCSEditor") + .ExtensionContext(ExtensionContext) + ] + + SVerticalBox::Slot() .AutoHeight() .VAlign(VAlign_Top) @@ -4006,6 +4023,7 @@ void SSCSEditor::Construct( const FArguments& InArgs ) .Padding(2.0f) .BorderImage(FEditorStyle::GetBrush("SCSEditor.TreePanel")) .AddMetaData(FTagMetaData(TEXT("ComponentsPanel"))) + .Visibility(this, &SSCSEditor::GetComponentsTreeVisibility) [ SCSTreeWidget.ToSharedRef() ] @@ -4046,6 +4064,14 @@ void SSCSEditor::Construct( const FArguments& InArgs ) } END_SLATE_FUNCTION_BUILD_OPTIMIZATION +SSCSEditor::~SSCSEditor() +{ + if (UObject* ExtensionContext = ExtensionPanel->GetExtensionContext()) + { + ExtensionContext->RemoveFromRoot(); + } +} + TSharedPtr SSCSEditor::GetToolButtonsBox() { return ButtonBox; @@ -4156,6 +4182,25 @@ void SSCSEditor::GetSelectedItemsForContextMenu(TArray SSCSEditor::GetSelectedEditableObjects() const +{ + TArray SelectedObjects; + if (UBlueprint* BP = GetBlueprint()) + { + TArray SelectedTreeItems = SCSTreeWidget->GetSelectedItems(); + SelectedObjects.Reserve(SelectedTreeItems.Num()); + for (const FSCSEditorTreeNodePtrType& TreeNode : SelectedTreeItems) + { + UObject* Obj = TreeNode->GetEditableObjectForBlueprint(BP); + if (Obj) + { + SelectedObjects.Add(Obj); + } + } + } + return SelectedObjects; +} + void SSCSEditor::PopulateContextMenu(UToolMenu* Menu) { TArray SelectedItems = SCSTreeWidget->GetSelectedItems(); @@ -4556,7 +4601,7 @@ void SSCSEditor::OnGetChildrenForTree( FSCSEditorTreeNodePtrType InNodePtr, TArr const TArray& Children = InNodePtr->GetChildren(); OutChildren.Reserve(Children.Num()); - if (ComponentTypeFilter.IsSet() || !GetFilterText().IsEmpty()) + if (GetComponentTypeFilterToApply() || !GetFilterText().IsEmpty()) { for (FSCSEditorTreeNodePtrType Child : Children) { @@ -6534,8 +6579,10 @@ FSCSEditorTreeNodePtrType SSCSEditor::AddTreeNodeFromChildActor(FSCSEditorTreeNo return nullptr; } + const EChildActorComponentTreeViewVisualizationMode DefaultVisOverride = UICustomization.IsValid() ? UICustomization->GetChildActorVisualizationMode() : EChildActorComponentTreeViewVisualizationMode::UseDefault; + // Skip any expansion logic if the option is disabled - if (!FChildActorComponentEditorUtils::IsChildActorTreeViewExpansionEnabled()) + if (DefaultVisOverride == EChildActorComponentTreeViewVisualizationMode::UseDefault && !FChildActorComponentEditorUtils::IsChildActorTreeViewExpansionEnabled()) { return nullptr; } @@ -6548,14 +6595,14 @@ FSCSEditorTreeNodePtrType SSCSEditor::AddTreeNodeFromChildActor(FSCSEditorTreeNo check(ChildActorComponent != nullptr); // Check to see if we should expand the child actor node within the tree view - const bool bExpandChildActorInTreeView = FChildActorComponentEditorUtils::ShouldExpandChildActorInTreeView(ChildActorComponent); + const bool bExpandChildActorInTreeView = FChildActorComponentEditorUtils::ShouldExpandChildActorInTreeView(ChildActorComponent, DefaultVisOverride); if (bExpandChildActorInTreeView) { // Do the expansion as a normal actor subtree BuildSubTreeForActorNode(ChildActorNodePtr); // Check to see if we should include the child actor node within the tree view - const bool bShowChildActorNodeInTreeView = FChildActorComponentEditorUtils::ShouldShowChildActorNodeInTreeView(ChildActorComponent); + const bool bShowChildActorNodeInTreeView = FChildActorComponentEditorUtils::ShouldShowChildActorNodeInTreeView(ChildActorComponent, DefaultVisOverride); if (bShowChildActorNodeInTreeView) { // Add the child actor node into the tree view @@ -6802,35 +6849,37 @@ void SSCSEditor::GetCollapsedNodes(const FSCSEditorTreeNodePtrType& InNodePtr, T EVisibility SSCSEditor::GetPromoteToBlueprintButtonVisibility() const { - EVisibility ButtonVisibility = EVisibility::Collapsed; - if (EditorMode == EComponentEditorMode::ActorInstance) - { - if (GetBlueprint() == nullptr) - { - ButtonVisibility = EVisibility::Visible; - } - } - - return ButtonVisibility; + return (UICustomization.IsValid() && UICustomization->HideBlueprintButtons()) + || (EditorMode != EComponentEditorMode::ActorInstance) + || (GetBlueprint() != nullptr) + ? EVisibility::Collapsed : EVisibility::Visible; } EVisibility SSCSEditor::GetEditBlueprintButtonVisibility() const { - EVisibility ButtonVisibility = EVisibility::Collapsed; - if (EditorMode == EComponentEditorMode::ActorInstance) - { - if (GetBlueprint() != nullptr) - { - ButtonVisibility = EVisibility::Visible; - } - } - - return ButtonVisibility; + return (UICustomization.IsValid() && UICustomization->HideBlueprintButtons()) + || (EditorMode != EComponentEditorMode::ActorInstance) + || (GetBlueprint() == nullptr) + ? EVisibility::Collapsed : EVisibility::Visible; } EVisibility SSCSEditor::GetComponentClassComboButtonVisibility() const { - return HideComponentClassCombo.Get() ? EVisibility::Collapsed : EVisibility::Visible; + return (HideComponentClassCombo.Get() + || (UICustomization.IsValid() && UICustomization->HideAddComponentButton())) + ? EVisibility::Collapsed : EVisibility::Visible; +} + +EVisibility SSCSEditor::GetComponentsTreeVisibility() const +{ + return (UICustomization.IsValid() && UICustomization->HideComponentsTree()) + ? EVisibility::Collapsed : EVisibility::Visible; +} + +EVisibility SSCSEditor::GetComponentsFilterBoxVisibility() const +{ + return (UICustomization.IsValid() && UICustomization->HideComponentsFilterBox()) + ? EVisibility::Collapsed : EVisibility::Visible; } FText SSCSEditor::OnGetApplyChangesToBlueprintTooltip() const @@ -7037,7 +7086,7 @@ void SSCSEditor::OnApplyChangesToBlueprint() const } } -void SSCSEditor::OnResetToBlueprintDefaults() const +void SSCSEditor::OnResetToBlueprintDefaults() { int32 NumChangedProperties = 0; @@ -7052,7 +7101,7 @@ void SSCSEditor::OnResetToBlueprintDefaults() const AActor* BlueprintCDO = Actor->GetClass()->GetDefaultObject(); if (BlueprintCDO != NULL) { - const EditorUtilities::ECopyOptions::Type CopyOptions = (EditorUtilities::ECopyOptions::Type)(EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | EditorUtilities::ECopyOptions::CallPostEditChangeProperty); + const EditorUtilities::ECopyOptions::Type CopyOptions = (EditorUtilities::ECopyOptions::Type)(EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties); NumChangedProperties = EditorUtilities::CopyActorProperties(BlueprintCDO, Actor, CopyOptions); } NumChangedProperties += Actor->GetInstanceComponents().Num(); @@ -7090,6 +7139,8 @@ void SSCSEditor::OnResetToBlueprintDefaults() const CompletionState = SNotificationItem::CS_Fail; } + UpdateTree(); + // Add the notification to the queue const TSharedPtr Notification = FSlateNotificationManager::Get().AddNotification(NotificationInfo); Notification->SetCompletionState(CompletionState); @@ -7130,7 +7181,7 @@ FText SSCSEditor::GetFilterText() const return FilterBox->GetText(); } -void SSCSEditor::OnFilterTextChanged(const FText& InFilterText) +void SSCSEditor::OnFilterTextChanged(const FText& /*InFilterText*/) { struct OnFilterTextChanged_Inner { @@ -7191,7 +7242,7 @@ void SSCSEditor::OnFilterTextChanged(const FText& InFilterText) bool SSCSEditor::RefreshFilteredState(FSCSEditorTreeNodePtrType TreeNode, bool bRecursive) { - const UClass* FilterType = ComponentTypeFilter.Get(); + const UClass* FilterType = GetComponentTypeFilterToApply(); FString FilterText = FText::TrimPrecedingAndTrailing( GetFilterText() ).ToString(); TArray FilterTerms; @@ -7201,4 +7252,21 @@ bool SSCSEditor::RefreshFilteredState(FSCSEditorTreeNodePtrType TreeNode, bool b return TreeNode->IsFlaggedForFiltration(); } +void SSCSEditor::SetUICustomization(TSharedPtr InUICustomization) +{ + UICustomization = InUICustomization; + + UpdateTree(true /*bRegenerateTreeNodes*/); +} + +TSubclassOf SSCSEditor::GetComponentTypeFilterToApply() const +{ + TSubclassOf ComponentType = UICustomization.IsValid() ? UICustomization->GetComponentTypeFilter() : nullptr; + if (!ComponentType) + { + ComponentType = ComponentTypeFilter.Get(); + } + return ComponentType; +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/Kismet/Private/WatchPointViewer.cpp b/Engine/Source/Editor/Kismet/Private/WatchPointViewer.cpp index 83879a7dba0b..09f3b8e922a2 100644 --- a/Engine/Source/Editor/Kismet/Private/WatchPointViewer.cpp +++ b/Engine/Source/Editor/Kismet/Private/WatchPointViewer.cpp @@ -766,7 +766,7 @@ void WatchViewer::UpdateInstancedWatchDisplay() #if DO_BLUEPRINT_GUARD { Private_InstanceWatchSource.Reset(); - const TArray& ScriptStack = FBlueprintExceptionTracker::Get().ScriptStack; + const TArray& ScriptStack = FBlueprintContextTracker::Get().GetScriptStack(); TSet SeenBlueprints; diff --git a/Engine/Source/Editor/Kismet/Public/BlueprintEditor.h b/Engine/Source/Editor/Kismet/Public/BlueprintEditor.h index 28f60a4a46eb..0d84062d11a3 100644 --- a/Engine/Source/Editor/Kismet/Public/BlueprintEditor.h +++ b/Engine/Source/Editor/Kismet/Public/BlueprintEditor.h @@ -562,6 +562,12 @@ public: /** Update Node Creation mechanisms for analytics */ void UpdateNodeCreationStats(const ENodeCreateAction::Type CreateAction); + /** Sets customizations for the BP editor details panel. */ + void SetDetailsCustomization(TSharedPtr DetailsObjectFilter, TSharedPtr DetailsRootCustomization); + + /** Sets SCS editor UI customization */ + void SetSCSEditorUICustomization(TSharedPtr SCSEditorUICustomization); + /** * Register a customization for interacting with the SCS editor * @param InComponentName The name of the component to customize behavior for @@ -642,6 +648,9 @@ public: /** Removes the bookmark node with the given ID. */ void RemoveBookmark(const FGuid& BookmarkNodeId, bool bRefreshUI = true); + /** Gets the default schema for this editor */ + TSubclassOf GetDefaultSchema() const { return GetDefaultSchemaClass(); } + protected: UE_DEPRECATED(4.26, "Please do any validation inside the UBlueprint class during compilation, extra errors during compiling only supplied by the designer can lead to design time only errors being reported and being missed during cooks/content validation.") virtual void AppendExtraCompilerResults(TSharedPtr ResultsListing) {} @@ -925,6 +934,10 @@ protected: /** Paste on graph at specific location */ virtual void PasteNodesHere(class UEdGraph* DestinationGraph, const FVector2D& GraphLocation) override; + /** Paste Variable Definition or Nodes */ + virtual void PasteGeneric(); + virtual bool CanPasteGeneric() const; + virtual void PasteNodes(); virtual bool CanPasteNodes() const override; diff --git a/Engine/Source/Editor/Kismet/Public/BlueprintEditorModule.h b/Engine/Source/Editor/Kismet/Public/BlueprintEditorModule.h index b3aa486ddf5c..701b3c490955 100644 --- a/Engine/Source/Editor/Kismet/Public/BlueprintEditorModule.h +++ b/Engine/Source/Editor/Kismet/Public/BlueprintEditorModule.h @@ -127,7 +127,10 @@ public: * @return Interface to the new Blueprint editor */ virtual TSharedRef CreateBlueprintEditor(const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, UBlueprint* Blueprint, bool bShouldOpenInDefaultsMode = false); - virtual TSharedRef CreateBlueprintEditor( const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, const TArray< UBlueprint* >& BlueprintsToEdit ); + virtual TSharedRef CreateBlueprintEditor( const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, const TArray< UBlueprint* >& BlueprintsToEdit, bool bShouldOpenInDefaultsMode = true); + + /** Get all blueprint editor instances */ + virtual TArray> GetBlueprintEditors() const; /** * Creates an instance of a Enum editor object. @@ -164,7 +167,13 @@ public: DECLARE_EVENT_OneParam(IBlueprintEditor, FOnRegisterLayoutExtensions, FLayoutExtender&); FOnRegisterLayoutExtensions& OnRegisterLayoutExtensions() { return RegisterLayoutExtensions; } - /** + /** Sets customizations for the BP editor details panel. */ + virtual void SetDetailsCustomization(TSharedPtr InDetailsObjectFilter, TSharedPtr InDetailsRootCustomization); + + /** Sets SCS editor UI customization */ + virtual void SetSCSEditorUICustomization(TSharedPtr InSCSEditorUICustomization); + + /** * Register a customization for interacting with the SCS editor * @param InComponentName The name of the component to customize behavior for * @param InCustomizationBuilder The delegate used to create customization instances @@ -234,6 +243,9 @@ private: void PrepareAutoGeneratedDefaultEvents(); private: + /** List of all blueprint editors that were created. */ + TArray> BlueprintEditors; + TSharedPtr MenuExtensibilityManager; // @@ -255,6 +267,15 @@ private: /** Customizations for Blueprint graphs */ TMap GraphCustomizations; + /** Root customization for the BP editor details panel. */ + TSharedPtr DetailsRootCustomization; + + /** Filter used to determine the set of objects shown in the BP editor details panel. */ + TSharedPtr DetailsObjectFilter; + + /** UI customizations for the SCS editor inside the blueprint editor */ + TSharedPtr SCSEditorUICustomization; + /** * A command list that can be passed around and isn't bound to an instance * of the blueprint editor. diff --git a/Engine/Source/Editor/Kismet/Public/FindInBlueprints.h b/Engine/Source/Editor/Kismet/Public/FindInBlueprints.h index da5bc8ab3bec..dcef3d142a4c 100644 --- a/Engine/Source/Editor/Kismet/Public/FindInBlueprints.h +++ b/Engine/Source/Editor/Kismet/Public/FindInBlueprints.h @@ -233,10 +233,12 @@ public: SLATE_BEGIN_ARGS( SFindInBlueprints ) : _bIsSearchWindow(true) , _bHideSearchBar(false) + , _bHideFindGlobalButton(false) , _ContainingTab() {} SLATE_ARGUMENT(bool, bIsSearchWindow) SLATE_ARGUMENT(bool, bHideSearchBar) + SLATE_ARGUMENT(bool, bHideFindGlobalButton) SLATE_ARGUMENT(TSharedPtr, ContainingTab) SLATE_END_ARGS() @@ -281,9 +283,15 @@ public: return bIsLocked; } + /** Determines whether a search query is actively in progress */ + bool IsSearchInProgress() const; + /** SWidget overrides */ virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; + /** Clears the currently visible results */ + void ClearResults(); + private: /** Processes results of the ongoing async stream search */ EActiveTimerReturnType UpdateSearchResults( double InCurrentTime, float InDeltaTime ); diff --git a/Engine/Source/Editor/Kismet/Public/ISCSEditorUICustomization.h b/Engine/Source/Editor/Kismet/Public/ISCSEditorUICustomization.h new file mode 100644 index 000000000000..4b5dafe7920e --- /dev/null +++ b/Engine/Source/Editor/Kismet/Public/ISCSEditorUICustomization.h @@ -0,0 +1,34 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ChildActorComponent.h" + +/** SCSEditor UI customization */ +class ISCSEditorUICustomization +{ +public: + virtual ~ISCSEditorUICustomization() {} + + /** @return Whether to hide the components tree */ + virtual bool HideComponentsTree() const { return false; } + + /** @return Whether to hide the components filter box */ + virtual bool HideComponentsFilterBox() const { return false; } + + /** @return Whether to hide the "Add Component" combo button */ + virtual bool HideAddComponentButton() const { return false; } + + /** @return Whether to hide the "Edit Blueprint" and "Blueprint/Add Script" buttons */ + virtual bool HideBlueprintButtons() const { return false; } + + /** + * @return An override for the default ChildActorComponentTreeViewVisualizationMode from the project settings, if different from UseDefault. + * @note Setting an override also forces child actor tree view expansion to be enabled. + */ + virtual EChildActorComponentTreeViewVisualizationMode GetChildActorVisualizationMode() const { return EChildActorComponentTreeViewVisualizationMode::UseDefault; } + + /** @return A component type that limits visible nodes when filtering the tree view */ + virtual TSubclassOf GetComponentTypeFilter() const { return nullptr; } +}; diff --git a/Engine/Source/Editor/Kismet/Public/ReplaceNodeReferencesHelper.h b/Engine/Source/Editor/Kismet/Public/ReplaceNodeReferencesHelper.h new file mode 100644 index 000000000000..b9b0579b58a2 --- /dev/null +++ b/Engine/Source/Editor/Kismet/Public/ReplaceNodeReferencesHelper.h @@ -0,0 +1,89 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "FindInBlueprintManager.h" +#include "Engine/MemberReference.h" +#include "TickableEditorObject.h" +#include "Misc/ScopedSlowTask.h" +#include "ScopedTransaction.h" + +/** A helper class for Replacing Variable references in blueprints */ +struct FReplaceNodeReferencesHelper : FTickableEditorObject +{ + /** Constructs a ReplaceNodeReferencesHelper with the specified Source and Replacement variables */ + FReplaceNodeReferencesHelper(const FMemberReference& Source, const FMemberReference& Replacement, UBlueprint* InBlueprint); + + /** Constructs a ReplaceNodeReference Helper with the specified Source and Replacement variables */ + FReplaceNodeReferencesHelper(FMemberReference&& Source, FMemberReference&& Replacement, UBlueprint* InBlueprint); + + virtual ~FReplaceNodeReferencesHelper(); + + /** Triggers a FindInBlueprints cache of all blueprints, and submits a search query when it is done, this could take a while */ + void BeginFindAndReplace(const FSimpleDelegate& InOnCompleted = FSimpleDelegate()); + + /** Callback to replace references when Search is completed */ + void ReplaceReferences(TArray& InRawDataList); + + /** Returns true when the Find/Replace operation is finished */ + bool IsCompleted() const { return bCompleted; } + + /** Returns the MemberReference for the Source */ + const FMemberReference& GetSource() const { return SourceReference; } + + /** Returns the MemberReference for the Replacement */ + const FMemberReference& GetReplacement() const { return ReplacementReference; } + + /** Keeps a scoped transaction alive while it does it's job (call with nullptr to reset if needed) */ + const void SetTransaction(TSharedPtr InTransaction); + + //~ Begin FTickableEditorObject Interface + virtual bool IsTickable() const override; + + virtual void Tick(float DeltaSeconds) override; + + virtual TStatId GetStatId() const override; + //~ End FTickableEditorObject Interface + + /** + * Helper function to replace references + * + * @param InReplacement Variable reference to replace with + * @param InBlueprint Blueprint that InReplacement belongs to + * @param InRawDataList Raw find in blueprints search results + */ + static void ReplaceReferences(FMemberReference& InReplacement, UBlueprint* InBlueprint, TArray& InRawDataList); + +private: + + /** Submits a search query and calls ReplaceReferences when complete */ + void OnSubmitSearchQuery(); + + /** Updates the active search and ends it when complete */ + void UpdateSearchQuery(); + + /** The source variable to be replaced */ + FMemberReference SourceReference; + + /** The variable to replace references to the source with */ + FMemberReference ReplacementReference; + + /** The Class that owns the variables */ + UBlueprint* Blueprint; + + /** Callback for when the FindAndReplace is completed */ + FSimpleDelegate OnCompleted; + + /** Used when starting a full find and replace task */ + TUniquePtr SlowTask; + + /** In Progress Search Object */ + TSharedPtr StreamSearch; + + /** Transaction if the user wants us to keep one alive */ + TSharedPtr Transaction; + + /** Whether a search has been started and finished */ + bool bCompleted; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/Kismet/Public/SCSEditorExtensionContext.h b/Engine/Source/Editor/Kismet/Public/SCSEditorExtensionContext.h new file mode 100644 index 000000000000..4c130419d3ba --- /dev/null +++ b/Engine/Source/Editor/Kismet/Public/SCSEditorExtensionContext.h @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "SCSEditorExtensionContext.generated.h" + +class SSCSEditor; + +UCLASS() +class KISMET_API USCSEditorExtensionContext : public UObject +{ + GENERATED_BODY() + +public: + const TWeakPtr& GetSCSEditor() const { return SCSEditor; } + +private: + friend SSCSEditor; + + TWeakPtr SCSEditor; +}; diff --git a/Engine/Source/Editor/Kismet/Public/SSCSEditor.h b/Engine/Source/Editor/Kismet/Public/SSCSEditor.h index 963d0bdf2946..e45489971ce8 100644 --- a/Engine/Source/Editor/Kismet/Public/SSCSEditor.h +++ b/Engine/Source/Editor/Kismet/Public/SSCSEditor.h @@ -30,6 +30,7 @@ class FSCSEditorTreeNode; class SSCSEditor; class UPrimitiveComponent; struct EventData; +class ISCSEditorUICustomization; // SCS editor tree node pointer types using FSCSEditorTreeNodePtrType = TSharedPtr; @@ -991,6 +992,8 @@ public: void Construct(const FArguments& InArgs); + ~SSCSEditor(); + /** SWidget interface */ virtual FReply OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ); @@ -1166,6 +1169,9 @@ public: */ void GetSelectedItemsForContextMenu(TArray& OutSelectedItems) const; + /** @return Array of the editable objects selected in the tree */ + TArray GetSelectedEditableObjects() const; + /** Provides access to the Blueprint context that's being edited */ class UBlueprint* GetBlueprint() const; @@ -1187,6 +1193,9 @@ public: /** Return the button widgets that can add components or create/edit blueprints */ TSharedPtr GetToolButtonsBox(); + /** Sets UI customizations of this SCSEditor. */ + void SetUICustomization(TSharedPtr InUICustomization); + protected: FSCSEditorTreeNodePtrType FindOrCreateParentForExistingComponent(UActorComponent* InActorComponent, FSCSEditorActorNodePtrType ActorRootNode); FSCSEditorTreeNodePtrType FindParentForNewComponent(UActorComponent* NewComponent) const; @@ -1295,6 +1304,12 @@ protected: /** @return The visibility of the Add Component combo button */ EVisibility GetComponentClassComboButtonVisibility() const; + /** @return The visibility of the components tree */ + EVisibility GetComponentsTreeVisibility() const; + + /** @return The visibility of the components filter box */ + EVisibility GetComponentsFilterBoxVisibility() const; + /** @return the tooltip describing how many properties will be applied to the blueprint */ FText OnGetApplyChangesToBlueprintTooltip() const; @@ -1308,7 +1323,7 @@ protected: void OnApplyChangesToBlueprint() const; /** Resets instance changes to the blueprint default */ - void OnResetToBlueprintDefaults() const; + void OnResetToBlueprintDefaults(); /** Converts the current actor instance to a blueprint */ void PromoteToBlueprint() const; @@ -1348,6 +1363,9 @@ protected: /** Helper method to construct the subtree for the given actor (root) node. */ void BuildSubTreeForActorNode(FSCSEditorActorNodePtrType InActorNode); + /** @return Type of component to filter the tree view with or nullptr if there's no filter. */ + TSubclassOf GetComponentTypeFilterToApply() const; + public: /** Tree widget */ TSharedPtr SCSTreeWidget; @@ -1390,6 +1408,7 @@ public: /** Returns the Actor context for which we are viewing/editing the SCS. Can return null. Should not be cached as it may change from frame to frame. */ class AActor* GetActorContext() const; + private: /** Indicates which editor mode we're in. */ EComponentEditorMode::Type EditorMode; @@ -1417,4 +1436,9 @@ private: /** The tools buttons box **/ TSharedPtr ButtonBox; -}; + + /** SCSEditor UI customizations */ + TSharedPtr UICustomization; + + /** SCSEditor UI extension */ + TSharedPtr ExtensionPanel; }; diff --git a/Engine/Source/Editor/KismetCompiler/Private/AnimBlueprintCompiler.cpp b/Engine/Source/Editor/KismetCompiler/Private/AnimBlueprintCompiler.cpp deleted file mode 100644 index 4468f533e6af..000000000000 --- a/Engine/Source/Editor/KismetCompiler/Private/AnimBlueprintCompiler.cpp +++ /dev/null @@ -1,3494 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "AnimBlueprintCompiler.h" -#include "UObject/UObjectHash.h" -#include "Animation/AnimInstance.h" -#include "EdGraphUtilities.h" -#include "K2Node_CallFunction.h" -#include "K2Node_StructMemberGet.h" -#include "K2Node_BreakStruct.h" -#include "K2Node_CallArrayFunction.h" -#include "K2Node_CustomEvent.h" -#include "K2Node_Knot.h" -#include "K2Node_StructMemberSet.h" -#include "K2Node_VariableGet.h" -#include "K2Node_VariableSet.h" - -#include "AnimationGraphSchema.h" -#include "K2Node_TransitionRuleGetter.h" -#include "Kismet/BlueprintFunctionLibrary.h" -#include "Kismet/KismetArrayLibrary.h" -#include "Kismet/KismetMathLibrary.h" -#include "Kismet2/BlueprintEditorUtils.h" -#include "Kismet2/KismetReinstanceUtilities.h" -#include "AnimStateNodeBase.h" -#include "AnimStateNode.h" -#include "AnimStateConduitNode.h" -#include "AnimStateEntryNode.h" -#include "AnimStateTransitionNode.h" -#include "AnimationCustomTransitionGraph.h" -#include "AnimationStateGraph.h" -#include "AnimationStateMachineGraph.h" -#include "AnimationStateMachineSchema.h" -#include "AnimationTransitionGraph.h" -#include "AnimGraphNode_Root.h" -#include "AnimGraphNode_CustomTransitionResult.h" -#include "Animation/AnimNode_UseCachedPose.h" -#include "AnimGraphNode_SaveCachedPose.h" -#include "AnimGraphNode_UseCachedPose.h" -#include "AnimGraphNode_StateMachineBase.h" -#include "AnimGraphNode_StateResult.h" -#include "AnimGraphNode_SequencePlayer.h" -#include "AnimGraphNode_TransitionPoseEvaluator.h" -#include "AnimGraphNode_TransitionResult.h" -#include "K2Node_AnimGetter.h" -#include "AnimGraphNode_StateMachine.h" -#include "Animation/AnimNode_CustomProperty.h" -#include "Animation/AnimNode_LinkedAnimGraph.h" -#include "AnimGraphNode_LinkedAnimGraph.h" -#include "AnimGraphNode_Slot.h" -#include "AnimationEditorUtils.h" -#include "AnimationGraph.h" - -#include "AnimBlueprintPostCompileValidation.h" -#include "AnimGraphNode_LinkedInputPose.h" -#include "K2Node_FunctionEntry.h" -#include "K2Node_FunctionResult.h" -#include "AnimGraphNode_LinkedAnimLayer.h" - -#define LOCTEXT_NAMESPACE "AnimBlueprintCompiler" - -#define ANIM_FUNC_DECORATOR TEXT("__AnimFunc") - -// -// Forward declarations. -// -class UAnimStateNodeBase; - -////////////////////////////////////////////////////////////////////////// -// FAnimBlueprintCompilerContext::FEffectiveConstantRecord - -bool FAnimBlueprintCompilerContext::FEffectiveConstantRecord::Apply(UObject* Object) -{ - uint8* StructPtr = nullptr; - uint8* PropertyPtr = nullptr; - - if(NodeVariableProperty->Struct->IsChildOf(FAnimNode_LinkedAnimGraph::StaticStruct())) - { - PropertyPtr = ConstantProperty->ContainerPtrToValuePtr(Object); - } - else - { - StructPtr = NodeVariableProperty->ContainerPtrToValuePtr(Object); - PropertyPtr = ConstantProperty->ContainerPtrToValuePtr(StructPtr); - } - - if (ArrayIndex != INDEX_NONE) - { - FArrayProperty* ArrayProperty = CastFieldChecked(ConstantProperty); - - // Peer inside the array - FScriptArrayHelper ArrayHelper(ArrayProperty, PropertyPtr); - - if (ArrayHelper.IsValidIndex(ArrayIndex)) - { - FBlueprintEditorUtils::PropertyValueFromString_Direct(ArrayProperty->Inner, LiteralSourcePin->GetDefaultAsString(), ArrayHelper.GetRawPtr(ArrayIndex)); - } - else - { - return false; - } - } - else - { - FBlueprintEditorUtils::PropertyValueFromString_Direct(ConstantProperty, LiteralSourcePin->GetDefaultAsString(), PropertyPtr); - } - - return true; -} - -////////////////////////////////////////////////////////////////////////// -// FAnimBlueprintCompiler - -FAnimBlueprintCompilerContext::FAnimBlueprintCompilerContext(UAnimBlueprint* SourceSketch, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompileOptions) - : FKismetCompilerContext(SourceSketch, InMessageLog, InCompileOptions) - , AnimBlueprint(SourceSketch) - , bIsDerivedAnimBlueprint(false) -{ - // Make sure the skeleton has finished preloading - if (AnimBlueprint->TargetSkeleton != nullptr) - { - if (FLinkerLoad* Linker = AnimBlueprint->TargetSkeleton->GetLinker()) - { - Linker->Preload(AnimBlueprint->TargetSkeleton); - } - } - - if (AnimBlueprint->HasAnyFlags(RF_NeedPostLoad)) - { - //Compilation during loading .. need to verify node guids as some anim blueprints have duplicated guids - - TArray ChildGraphs; - ChildGraphs.Reserve(20); - - TSet NodeGuids; - NodeGuids.Reserve(200); - - // Tracking to see if we need to warn for deterministic cooking - bool bNodeGuidsRegenerated = false; - - auto CheckGraph = [&bNodeGuidsRegenerated, &NodeGuids, &ChildGraphs](UEdGraph* InGraph) - { - if (AnimationEditorUtils::IsAnimGraph(InGraph)) - { - ChildGraphs.Reset(); - AnimationEditorUtils::FindChildGraphsFromNodes(InGraph, ChildGraphs); - - for (int32 Index = 0; Index < ChildGraphs.Num(); ++Index) // Not ranged for as we modify array within the loop - { - UEdGraph* ChildGraph = ChildGraphs[Index]; - - // Get subgraphs before continuing - AnimationEditorUtils::FindChildGraphsFromNodes(ChildGraph, ChildGraphs); - - for (UEdGraphNode* Node : ChildGraph->Nodes) - { - if (Node) - { - if (NodeGuids.Contains(Node->NodeGuid)) - { - bNodeGuidsRegenerated = true; - - Node->CreateNewGuid(); // GUID is already being used, create a new one. - } - else - { - NodeGuids.Add(Node->NodeGuid); - } - } - } - } - } - }; - - for (UEdGraph* Graph : AnimBlueprint->FunctionGraphs) - { - CheckGraph(Graph); - } - - for(FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) - { - for(UEdGraph* Graph : InterfaceDesc.Graphs) - { - CheckGraph(Graph); - } - } - - if(bNodeGuidsRegenerated) - { - UE_LOG(LogAnimation, Warning, TEXT("Animation Blueprint %s has nodes with invalid node guids that have been regenerated. This blueprint will not cook deterministically until it is resaved."), *AnimBlueprint->GetPathName()); - } - } - - // Determine if there is an anim blueprint in the ancestry of this class - bIsDerivedAnimBlueprint = UAnimBlueprint::FindRootAnimBlueprint(AnimBlueprint) != NULL; - - // Regenerate temporary stub functions - // We do this here to catch the standard and 'fast' (compilation manager) compilation paths - CreateAnimGraphStubFunctions(); -} - -FAnimBlueprintCompilerContext::~FAnimBlueprintCompilerContext() -{ - DestroyAnimGraphStubFunctions(); -} - -void FAnimBlueprintCompilerContext::CreateClassVariablesFromBlueprint() -{ - FKismetCompilerContext::CreateClassVariablesFromBlueprint(); - - if(bGenerateLinkedAnimGraphVariables) - { - if(!bIsDerivedAnimBlueprint) - { - auto ProcessAllSubGraphs = [this](UEdGraph* InGraph) - { - auto ProcessGraph = [this](UEdGraph* InGraph) - { - TArray CustomPropertyNodes; - InGraph->GetNodesOfClass(CustomPropertyNodes); - for(UAnimGraphNode_CustomProperty* CustomPropNode : CustomPropertyNodes) - { - ProcessCustomPropertyNode(CustomPropNode); - } - - TArray LinkedAnimGraphNodes; - InGraph->GetNodesOfClass(LinkedAnimGraphNodes); - for(UAnimGraphNode_LinkedAnimGraphBase* LinkedAnimGraphNode : LinkedAnimGraphNodes) - { - ProcessLinkedAnimGraph(LinkedAnimGraphNode, false); - } - - TArray LinkedInputPoseNodes; - InGraph->GetNodesOfClass(LinkedInputPoseNodes); - for(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode : LinkedInputPoseNodes) - { - ProcessLinkedInputPose(LinkedInputPoseNode); - } - }; - - // Need to extract subgraphs to catch state machine states - TArray AllGraphs; - AllGraphs.Add(InGraph); - InGraph->GetAllChildrenGraphs(AllGraphs); - - for(UEdGraph* CurrGraph : AllGraphs) - { - ProcessGraph(CurrGraph); - } - }; - - for (UEdGraph* Graph : Blueprint->FunctionGraphs) - { - ProcessAllSubGraphs(Graph); - } - - for(FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) - { - for(UEdGraph* Graph : InterfaceDesc.Graphs) - { - ProcessAllSubGraphs(Graph); - } - } - } - } -} - - -UEdGraphSchema_K2* FAnimBlueprintCompilerContext::CreateSchema() -{ - AnimSchema = NewObject(); - return AnimSchema; -} - -UK2Node_CallFunction* FAnimBlueprintCompilerContext::SpawnCallAnimInstanceFunction(UEdGraphNode* SourceNode, FName FunctionName) -{ - //@TODO: SKELETON: This is a call on a parent function (UAnimInstance::StaticClass() specifically), should we treat it as self or not? - UK2Node_CallFunction* FunctionCall = SpawnIntermediateNode(SourceNode); - FunctionCall->FunctionReference.SetSelfMember(FunctionName); - FunctionCall->AllocateDefaultPins(); - - return FunctionCall; -} - -void FAnimBlueprintCompilerContext::CreateEvaluationHandler(UAnimGraphNode_Base* VisualAnimNode, FEvaluationHandlerRecord& Record) -{ - // Shouldn't create a handler if there is nothing to work with - check(Record.ServicedProperties.Num() > 0); - check(Record.NodeVariableProperty != NULL); - const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault(); - - if (Record.IsFastPath()) - { - return; - } - - // Use the node GUID for a stable name across compiles - FString FunctionName = FString::Printf(TEXT("%s_%s_%s_%s"), *AnimGraphDefaultSchema->DefaultEvaluationHandlerName.ToString(), *VisualAnimNode->GetOuter()->GetName(), *VisualAnimNode->GetClass()->GetName(), *VisualAnimNode->NodeGuid.ToString()); - Record.HandlerFunctionName = FName(*FunctionName); - - // check function name isnt already used (data exists that can contain duplicate GUIDs) and apply a numeric extension until it is unique - int32 ExtensionIndex = 0; - FName* ExistingName = HandlerFunctionNames.Find(Record.HandlerFunctionName); - while (ExistingName != nullptr) - { - FunctionName = FString::Printf(TEXT("%s_%s_%s_%s_%d"), *AnimGraphDefaultSchema->DefaultEvaluationHandlerName.ToString(), *VisualAnimNode->GetOuter()->GetName(), *VisualAnimNode->GetClass()->GetName(), *VisualAnimNode->NodeGuid.ToString(), ExtensionIndex); - Record.HandlerFunctionName = FName(*FunctionName); - ExistingName = HandlerFunctionNames.Find(Record.HandlerFunctionName); - ExtensionIndex++; - } - - HandlerFunctionNames.Add(Record.HandlerFunctionName); - - // Add a custom event in the graph - UK2Node_CustomEvent* EntryNode = SpawnIntermediateEventNode(VisualAnimNode, nullptr, ConsolidatedEventGraph); - EntryNode->bInternalEvent = true; - EntryNode->CustomFunctionName = Record.HandlerFunctionName; - EntryNode->AllocateDefaultPins(); - - // The ExecChain is the current exec output pin in the linear chain - UEdGraphPin* ExecChain = Schema->FindExecutionPin(*EntryNode, EGPD_Output); - if (Record.bServicesInstanceProperties) - { - // Need to create a variable set call for each serviced property in the handler - for (TPair& PropHandlerPair : Record.ServicedProperties) - { - FAnimNodeSinglePropertyHandler& PropHandler = PropHandlerPair.Value; - FName PropertyName = PropHandlerPair.Key; - - // Should be true, we only want to deal with instance targets in here - if (PropHandler.bInstanceIsTarget) - { - for (FPropertyCopyRecord& CopyRecord : PropHandler.CopyRecords) - { - // New set node for the property - UK2Node_VariableSet* VarAssignNode = SpawnIntermediateNode(VisualAnimNode, ConsolidatedEventGraph); - VarAssignNode->VariableReference.SetSelfMember(CopyRecord.DestProperty->GetFName()); - VarAssignNode->AllocateDefaultPins(); - - // Wire up the exec line, and update the end of the chain - UEdGraphPin* ExecVariablesIn = Schema->FindExecutionPin(*VarAssignNode, EGPD_Input); - ExecChain->MakeLinkTo(ExecVariablesIn); - ExecChain = Schema->FindExecutionPin(*VarAssignNode, EGPD_Output); - - // Find the property pin on the set node and configure - for (UEdGraphPin* TargetPin : VarAssignNode->Pins) - { - if (TargetPin->PinType.IsContainer()) - { - // Currently unsupported - continue; - } - - FName PinPropertyName(TargetPin->PinName); - - if (PinPropertyName == PropertyName) - { - // This is us, wire up the variable - UEdGraphPin* DestPin = CopyRecord.DestPin; - - // Copy the data (link up to the source nodes) - TargetPin->CopyPersistentDataFromOldPin(*DestPin); - MessageLog.NotifyIntermediatePinCreation(TargetPin, DestPin); - - // Old pin needs to not be connected now - break all its links - DestPin->BreakAllPinLinks(); - - break; - } - } - - //VarAssignNode->ReconstructNode(); - } - } - } - } - - if (Record.bServicesNodeProperties) - { - // Create a struct member write node to store the parameters into the animation node - UK2Node_StructMemberSet* AssignmentNode = SpawnIntermediateNode(VisualAnimNode, ConsolidatedEventGraph); - AssignmentNode->VariableReference.SetSelfMember(Record.NodeVariableProperty->GetFName()); - AssignmentNode->StructType = Record.NodeVariableProperty->Struct; - AssignmentNode->AllocateDefaultPins(); - - // Wire up the variable node execution wires - UEdGraphPin* ExecVariablesIn = Schema->FindExecutionPin(*AssignmentNode, EGPD_Input); - ExecChain->MakeLinkTo(ExecVariablesIn); - ExecChain = Schema->FindExecutionPin(*AssignmentNode, EGPD_Output); - - // Run thru each property - TSet PropertiesBeingSet; - - for (auto TargetPinIt = AssignmentNode->Pins.CreateIterator(); TargetPinIt; ++TargetPinIt) - { - UEdGraphPin* TargetPin = *TargetPinIt; - FName PropertyName(TargetPin->PinName); - - // Does it get serviced by this handler? - if (FAnimNodeSinglePropertyHandler* SourceInfo = Record.ServicedProperties.Find(PropertyName)) - { - if (TargetPin->PinType.IsArray()) - { - // Grab the array that we need to set members for - UK2Node_StructMemberGet* FetchArrayNode = SpawnIntermediateNode(VisualAnimNode, ConsolidatedEventGraph); - FetchArrayNode->VariableReference.SetSelfMember(Record.NodeVariableProperty->GetFName()); - FetchArrayNode->StructType = Record.NodeVariableProperty->Struct; - FetchArrayNode->AllocatePinsForSingleMemberGet(PropertyName); - - UEdGraphPin* ArrayVariableNode = FetchArrayNode->FindPin(PropertyName); - - if (SourceInfo->CopyRecords.Num() > 0) - { - // Set each element in the array - for (FPropertyCopyRecord& CopyRecord : SourceInfo->CopyRecords) - { - int32 ArrayIndex = CopyRecord.DestArrayIndex; - UEdGraphPin* DestPin = CopyRecord.DestPin; - - // Create an array element set node - UK2Node_CallArrayFunction* ArrayNode = SpawnIntermediateNode(VisualAnimNode, ConsolidatedEventGraph); - ArrayNode->FunctionReference.SetExternalMember(GET_FUNCTION_NAME_CHECKED(UKismetArrayLibrary, Array_Set), UKismetArrayLibrary::StaticClass()); - ArrayNode->AllocateDefaultPins(); - - // Connect the execution chain - ExecChain->MakeLinkTo(ArrayNode->GetExecPin()); - ExecChain = ArrayNode->GetThenPin(); - - // Connect the input array - UEdGraphPin* TargetArrayPin = ArrayNode->FindPinChecked(TEXT("TargetArray")); - TargetArrayPin->MakeLinkTo(ArrayVariableNode); - ArrayNode->PinConnectionListChanged(TargetArrayPin); - - // Set the array index - UEdGraphPin* TargetIndexPin = ArrayNode->FindPinChecked(TEXT("Index")); - TargetIndexPin->DefaultValue = FString::FromInt(ArrayIndex); - - // Wire up the data input - UEdGraphPin* TargetItemPin = ArrayNode->FindPinChecked(TEXT("Item")); - TargetItemPin->CopyPersistentDataFromOldPin(*DestPin); - MessageLog.NotifyIntermediatePinCreation(TargetItemPin, DestPin); - DestPin->BreakAllPinLinks(); - } - } - } - else - { - check(!TargetPin->PinType.IsContainer()); - // Single property - if (SourceInfo->CopyRecords.Num() > 0 && SourceInfo->CopyRecords[0].DestPin != nullptr) - { - UEdGraphPin* DestPin = SourceInfo->CopyRecords[0].DestPin; - - PropertiesBeingSet.Add(DestPin->PinName); - TargetPin->CopyPersistentDataFromOldPin(*DestPin); - MessageLog.NotifyIntermediatePinCreation(TargetPin, DestPin); - DestPin->BreakAllPinLinks(); - } - } - } - } - - // Remove any unused pins from the assignment node to avoid smashing constant values - for (int32 PinIndex = 0; PinIndex < AssignmentNode->ShowPinForProperties.Num(); ++PinIndex) - { - FOptionalPinFromProperty& TestProperty = AssignmentNode->ShowPinForProperties[PinIndex]; - TestProperty.bShowPin = PropertiesBeingSet.Contains(TestProperty.PropertyName); - } - - AssignmentNode->ReconstructNode(); - } -} - - -void FAnimBlueprintCompilerContext::ProcessAnimationNode(UAnimGraphNode_Base* VisualAnimNode) -{ - // Early out if this node has already been processed - if (AllocatedAnimNodes.Contains(VisualAnimNode)) - { - return; - } - - // Make sure the visual node has a runtime node template - const UScriptStruct* NodeType = VisualAnimNode->GetFNodeType(); - if (NodeType == NULL) - { - MessageLog.Error(TEXT("@@ has no animation node member"), VisualAnimNode); - return; - } - - // Give the visual node a chance to do validation - { - const int32 PreValidationErrorCount = MessageLog.NumErrors; - VisualAnimNode->ValidateAnimNodeDuringCompilation(AnimBlueprint->TargetSkeleton, MessageLog); - VisualAnimNode->BakeDataDuringCompilation(MessageLog); - if (MessageLog.NumErrors != PreValidationErrorCount) - { - return; - } - } - - // Create a property for the node - const FString NodeVariableName = ClassScopeNetNameMap.MakeValidName(VisualAnimNode); - - const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault(); - - FEdGraphPinType NodeVariableType; - NodeVariableType.PinCategory = UAnimationGraphSchema::PC_Struct; - NodeVariableType.PinSubCategoryObject = MakeWeakObjectPtr(const_cast(NodeType)); - - FStructProperty* NewProperty = CastField(CreateVariable(FName(*NodeVariableName), NodeVariableType)); - - if (NewProperty == NULL) - { - MessageLog.Error(TEXT("Failed to create node property for @@"), VisualAnimNode); - } - - // Register this node with the compile-time data structures - const int32 AllocatedIndex = AllocateNodeIndexCounter++; - AllocatedAnimNodes.Add(VisualAnimNode, NewProperty); - AllocatedNodePropertiesToNodes.Add(NewProperty, VisualAnimNode); - AllocatedAnimNodeIndices.Add(VisualAnimNode, AllocatedIndex); - AllocatedPropertiesByIndex.Add(AllocatedIndex, NewProperty); - - UAnimGraphNode_Base* TrueSourceObject = MessageLog.FindSourceObjectTypeChecked(VisualAnimNode); - SourceNodeToProcessedNodeMap.Add(TrueSourceObject, VisualAnimNode); - - // Register the slightly more permanent debug information - FAnimBlueprintDebugData& NewAnimBlueprintDebugData = NewAnimBlueprintClass->GetAnimBlueprintDebugData(); - NewAnimBlueprintDebugData.NodePropertyToIndexMap.Add(TrueSourceObject, AllocatedIndex); - NewAnimBlueprintDebugData.NodeGuidToIndexMap.Add(TrueSourceObject->NodeGuid, AllocatedIndex); - NewAnimBlueprintDebugData.NodePropertyIndexToNodeMap.Add(AllocatedIndex, TrueSourceObject); - NewAnimBlueprintClass->GetDebugData().RegisterClassPropertyAssociation(TrueSourceObject, NewProperty); - - // Node-specific compilation that requires compiler state info - if (UAnimGraphNode_StateMachineBase* StateMachineInstance = Cast(VisualAnimNode)) - { - // Compile the state machine - ProcessStateMachine(StateMachineInstance); - } - else if (UAnimGraphNode_UseCachedPose* UseCachedPose = Cast(VisualAnimNode)) - { - // Handle a save/use cached pose linkage - ProcessUseCachedPose(UseCachedPose); - } - else if(UAnimGraphNode_LinkedAnimGraphBase* LinkedAnimGraphNode = Cast(VisualAnimNode)) - { - ProcessLinkedAnimGraph(LinkedAnimGraphNode, true); - } - else if (UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode = Cast(VisualAnimNode)) - { - // Process linked input pose nodes (input) - ProcessLinkedInputPose(LinkedInputPoseNode); - } - else if(UAnimGraphNode_Root* RootNode = Cast(VisualAnimNode)) - { - // Process root nodes - ProcessRoot(RootNode); - } - else if(UAnimGraphNode_StateResult* StateResultNode = Cast(VisualAnimNode)) - { - // Process state result nodes - ProcessStateResult(StateResultNode); - } - else if (UAnimGraphNode_AssetPlayerBase* AssetPlayerBase = Cast(VisualAnimNode)) - { - // Process Asset Player nodes to, if necessary cache off their node index for retrieval at runtime (used for evaluating Automatic Rule Transitions when using Layer nodes) - auto ProcessGraph = [this, GUID = AssetPlayerBase->NodeGuid](UEdGraph* Graph) - { - // Make sure we do not process the default AnimGraph - static const FName DefaultAnimGraphName("AnimGraph"); - if (Graph->GetFName() != DefaultAnimGraphName) - { - FString GraphName = Graph->GetName(); - // Also make sure we do not process any empty stub graphs - if (!GraphName.Contains(ANIM_FUNC_DECORATOR)) - { - if (Graph->Nodes.ContainsByPredicate([GUID](UEdGraphNode* Node) { return Node->NodeGuid == GUID; })) - { - if (int32* IndexPtr = NewAnimBlueprintClass->AnimBlueprintDebugData.NodeGuidToIndexMap.Find(GUID)) - { - FGraphAssetPlayerInformation& Info = NewAnimBlueprintClass->GraphAssetPlayerInformation.FindOrAdd(FName(*GraphName)); - Info.PlayerNodeIndices.AddUnique(*IndexPtr); - } - } - } - } - }; - - // Check for any definition of a layer graph - for (UEdGraph* Graph : Blueprint->FunctionGraphs) - { - ProcessGraph(Graph); - } - - // Check for any implemented AnimLayer interface graphs - for (FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) - { - // Only process Anim Layer interfaces - if (InterfaceDesc.Interface->IsChildOf()) - { - for (UEdGraph* Graph : InterfaceDesc.Graphs) - { - ProcessGraph(Graph); - } - } - } - - } - - // should we do this earlier? @Tom should we split this to create anim instance vars vs linking to sub anim instance node? - if (UAnimGraphNode_CustomProperty* CustomPropNode = Cast(VisualAnimNode)) - { - ProcessCustomPropertyNode(CustomPropNode); - } - - // Record pose pins for later patchup and gather pins that have an associated evaluation handler - TMap StructEvalHandlers; - - for (auto SourcePinIt = VisualAnimNode->Pins.CreateIterator(); SourcePinIt; ++SourcePinIt) - { - UEdGraphPin* SourcePin = *SourcePinIt; - bool bConsumed = false; - - // Register pose links for future use - if ((SourcePin->Direction == EGPD_Input) && (AnimGraphDefaultSchema->IsPosePin(SourcePin->PinType))) - { - // Input pose pin, going to need to be linked up - FPoseLinkMappingRecord LinkRecord = VisualAnimNode->GetLinkIDLocation(NodeType, SourcePin); - if (LinkRecord.IsValid()) - { - ValidPoseLinkList.Add(LinkRecord); - bConsumed = true; - } - } - else - { - // The property source for our data, either the struct property for an anim node, or the - // owning anim instance if using a linked instance node. - FProperty* SourcePinProperty = nullptr; - int32 SourceArrayIndex = INDEX_NONE; - bool bInstancePropertyExists = false; - - // We have special handling below if we're targeting a linked instance instead of our own instance properties - UAnimGraphNode_CustomProperty* CustomPropertyNode = Cast(VisualAnimNode); - - VisualAnimNode->GetPinAssociatedProperty(NodeType, SourcePin, /*out*/ SourcePinProperty, /*out*/ SourceArrayIndex); - - // Does this pin have an associated evaluation handler? - if(!SourcePinProperty && CustomPropertyNode) - { - // Custom property nodes use instance properties not node properties as they aren't UObjects - // and we can't store non-native properties there - CustomPropertyNode->GetInstancePinProperty(NewAnimBlueprintClass, SourcePin, SourcePinProperty); - bInstancePropertyExists = true; - } - - if (SourcePinProperty != NULL) - { - if (SourcePin->LinkedTo.Num() == 0) - { - // Literal that can be pushed into the CDO instead of re-evaluated every frame - new (ValidAnimNodePinConstants) FEffectiveConstantRecord(NewProperty, SourcePin, SourcePinProperty, SourceArrayIndex); - bConsumed = true; - } - else - { - // Dynamic value that needs to be wired up and evaluated each frame - const FString& EvaluationHandlerStr = SourcePinProperty->GetMetaData(AnimGraphDefaultSchema->NAME_OnEvaluate); - FName EvaluationHandlerName(*EvaluationHandlerStr); - if (EvaluationHandlerName != NAME_None) - { - // warn that NAME_OnEvaluate is deprecated: - MessageLog.Warning(*LOCTEXT("OnEvaluateDeprecated", "OnEvaluate meta data is deprecated, found on @@").ToString(), SourcePinProperty); - } - - EvaluationHandlerName = AnimGraphDefaultSchema->DefaultEvaluationHandlerName; - - FEvaluationHandlerRecord& EvalHandler = StructEvalHandlers.FindOrAdd(EvaluationHandlerName); - - ensure(EvalHandler.NodeVariableProperty == nullptr || EvalHandler.NodeVariableProperty == NewProperty); - EvalHandler.NodeVariableProperty = NewProperty; - EvalHandler.RegisterPin(SourcePin, SourcePinProperty, SourceArrayIndex); - // if it's not instance property, ensure we mark it - EvalHandler.bServicesNodeProperties = EvalHandler.bServicesNodeProperties | !bInstancePropertyExists; - - if (CustomPropertyNode) - { - EvalHandler.bServicesInstanceProperties = EvalHandler.bServicesInstanceProperties | bInstancePropertyExists; - - FAnimNodeSinglePropertyHandler* SinglePropHandler = EvalHandler.ServicedProperties.Find(SourcePinProperty->GetFName()); - check(SinglePropHandler); // Should have been added in RegisterPin - - // Flag that the target property is actually on the instance class and not the node - SinglePropHandler->bInstanceIsTarget = bInstancePropertyExists; - } - - bConsumed = true; - } - - UEdGraphPin* TrueSourcePin = MessageLog.FindSourcePin(SourcePin); - if (TrueSourcePin) - { - NewAnimBlueprintClass->GetDebugData().RegisterClassPropertyAssociation(TrueSourcePin, SourcePinProperty); - } - } - } - - if (!bConsumed && (SourcePin->Direction == EGPD_Input)) - { - //@TODO: ANIMREFACTOR: It's probably OK to have certain pins ignored eventually, but this is very helpful during development - MessageLog.Note(TEXT("@@ was visible but ignored"), SourcePin); - } - } - - // Generate a new event to update the value of these properties - for (auto HandlerIt = StructEvalHandlers.CreateIterator(); HandlerIt; ++HandlerIt) - { - FName EvaluationHandlerName = HandlerIt.Key(); - FEvaluationHandlerRecord& Record = HandlerIt.Value(); - - if (Record.NodeVariableProperty) - { - // Disable fast-path generation for nativized anim BPs, we dont run the VM anyways and - // the property names are 'decorated' by the backend, so records dont match. - if(Blueprint->NativizationFlag == EBlueprintNativizationFlag::Disabled) - { - // build fast path copy records here - // we need to do this at this point as they rely on traversing the original wire path - // to determine source data. After we call CreateEvaluationHandlerStruct (etc) the original - // graph is modified to hook up to the evaluation handler custom functions & pins are no longer - // available - Record.BuildFastPathCopyRecords(); - } - - NewAnimBlueprintClass->EvaluateGraphExposedInputs.AddDefaulted(StructEvalHandlers.Num()); - Record.EvaluationHandlerIdx = NewAnimBlueprintClass->EvaluateGraphExposedInputs.Num() - 1; - - CreateEvaluationHandler(VisualAnimNode, Record); - - ValidEvaluationHandlerList.Add(Record); - } - else - { - MessageLog.Error(*FString::Printf(TEXT("A property on @@ references a non-existent %s property named %s"), *(AnimGraphDefaultSchema->NAME_OnEvaluate.ToString()), *(EvaluationHandlerName.ToString())), VisualAnimNode); - } - } -} - -void FAnimBlueprintCompilerContext::ProcessRoot(UAnimGraphNode_Root* Root) -{ - UAnimGraphNode_Root* TrueNode = MessageLog.FindSourceObjectTypeChecked(Root); - - Root->Node.Name = TrueNode->GetGraph()->GetFName(); -} - -void FAnimBlueprintCompilerContext::ProcessStateResult(UAnimGraphNode_StateResult* StateResult) -{ - UAnimGraphNode_StateResult* TrueNode = MessageLog.FindSourceObjectTypeChecked(StateResult); - - StateResult->Node.Name = TrueNode->GetGraph()->GetFName(); -} - -void FAnimBlueprintCompilerContext::ProcessUseCachedPose(UAnimGraphNode_UseCachedPose* UseCachedPose) -{ - bool bSuccessful = false; - - // Link to the saved cached pose - if(UseCachedPose->SaveCachedPoseNode.IsValid()) - { - if (UAnimGraphNode_SaveCachedPose* AssociatedSaveNode = SaveCachedPoseNodes.FindRef(UseCachedPose->SaveCachedPoseNode->CacheName)) - { - FStructProperty* LinkProperty = FindFProperty(FAnimNode_UseCachedPose::StaticStruct(), TEXT("LinkToCachingNode")); - check(LinkProperty); - - FPoseLinkMappingRecord LinkRecord = FPoseLinkMappingRecord::MakeFromMember(UseCachedPose, AssociatedSaveNode, LinkProperty); - if (LinkRecord.IsValid()) - { - ValidPoseLinkList.Add(LinkRecord); - } - bSuccessful = true; - - // Save CachePoseName for debug - FName CachePoseName = FName(*UseCachedPose->SaveCachedPoseNode->CacheName); - UseCachedPose->SaveCachedPoseNode->Node.CachePoseName = CachePoseName; - UseCachedPose->Node.CachePoseName = CachePoseName; - } - } - - if(!bSuccessful) - { - MessageLog.Error(*LOCTEXT("NoAssociatedSaveNode", "@@ does not have an associated Save Cached Pose node").ToString(), UseCachedPose); - } -} - -void FAnimBlueprintCompilerContext::ProcessCustomPropertyNode(UAnimGraphNode_CustomProperty* CustomPropNode) -{ - if (!CustomPropNode) - { - return; - } - - const UAnimationGraphSchema* AnimGraphSchema = GetDefault(); - - for (UEdGraphPin* Pin : CustomPropNode->Pins) - { - if (!Pin->bOrphanedPin && !AnimGraphSchema->IsPosePin(Pin->PinType)) - { - // avoid to add properties which already exist on the custom node. - // for example the ControlRig_CustomNode has a pin called "alpha" which is not custom. - if (FStructProperty* NodeProperty = CastField(CustomPropNode->GetClass()->FindPropertyByName(TEXT("Node")))) - { - if(NodeProperty->Struct->FindPropertyByName(Pin->GetFName())) - { - continue; - } - } - - // Add prefix to avoid collisions - FString PrefixedName = CustomPropNode->GetPinTargetVariableName(Pin); - - // Create a property on the new class to hold the pin data - FProperty* NewProperty = FKismetCompilerUtilities::CreatePropertyOnScope(NewAnimBlueprintClass, FName(*PrefixedName), Pin->PinType, NewAnimBlueprintClass, CPF_None, GetSchema(), MessageLog); - if (NewProperty) - { - FKismetCompilerUtilities::LinkAddedProperty(NewAnimBlueprintClass, NewProperty); - - // Add mappings to the node - if (!bGenerateLinkedAnimGraphVariables) - { - UClass* InstClass = CustomPropNode->GetTargetSkeletonClass(); - if (FProperty* FoundProperty = FindFProperty(InstClass, Pin->PinName)) - { - CustomPropNode->AddSourceTargetProperties(NewProperty->GetFName(), FoundProperty->GetFName()); - } - else - { - CustomPropNode->AddSourceTargetProperties(NewProperty->GetFName(), Pin->GetFName()); - } - } - } - } - } -} - -void FAnimBlueprintCompilerContext::ProcessLinkedAnimGraph(UAnimGraphNode_LinkedAnimGraphBase* LinkedAnimGraph, bool bCheckForCycles) -{ - if(!LinkedAnimGraph) - { - return; - } - - FAnimNode_LinkedAnimGraph& RuntimeNode = *LinkedAnimGraph->GetLinkedAnimGraphNode(); - - if(!bGenerateLinkedAnimGraphVariables) - { - RuntimeNode.InputPoses.Empty(); - RuntimeNode.InputPoseNames.Empty(); - } - for(UEdGraphPin* Pin : LinkedAnimGraph->Pins) - { - if(!Pin->bOrphanedPin) - { - if (UAnimationGraphSchema::IsPosePin(Pin->PinType)) - { - if(Pin->Direction == EGPD_Input) - { - if(!bGenerateLinkedAnimGraphVariables) - { - RuntimeNode.InputPoses.AddDefaulted(); - RuntimeNode.InputPoseNames.Add(Pin->GetFName()); - } - } - } - } - } - - if(bCheckForCycles) - { - // Check for duplicated slot and state machine names to warn the user about how these - // are boxed - NameToCountMap SlotNameToCountMap; - NameToCountMap StateMachineNameToCountMap; - - GetDuplicatedSlotAndStateNames(LinkedAnimGraph, StateMachineNameToCountMap, SlotNameToCountMap); - - - for(TPair& Pair : SlotNameToCountMap) - { - if(Pair.Value > 1) - { - // Duplicated slot node - FString CompilerMessage = FString::Printf(TEXT("Slot name \"%s\" found across multiple instances. Slots are not visible outside of instances so duplicates or linked instances may not perform as expected."), *Pair.Key.ToString()); - MessageLog.Warning(*CompilerMessage); - } - } - - for(TPair& Pair : StateMachineNameToCountMap) - { - if(Pair.Value > 1) - { - // Duplicated slot node - FString CompilerMessage = FString::Printf(TEXT("State machine \"%s\" found across multiple instances. States are not visible outside of instances so duplicates or linked instances may not perform as expected."), *Pair.Key.ToString()); - MessageLog.Warning(*CompilerMessage); - } - } - } -} - -void FAnimBlueprintCompilerContext::GetDuplicatedSlotAndStateNames(UAnimGraphNode_LinkedAnimGraphBase* InLinkedAnimGraph, NameToCountMap& OutStateMachineNameToCountMap, NameToCountMap& OutSlotNameToCountMap) -{ - if(!InLinkedAnimGraph) - { - // Nothing to inspect - return; - } - - if(UClass* InstanceClass = InLinkedAnimGraph->GetTargetClass()) - { - UBlueprint* ClassBP = UBlueprint::GetBlueprintFromClass(InstanceClass); - - TArray AllGraphs; - ClassBP->GetAllGraphs(AllGraphs); - - for(UEdGraph* Graph : AllGraphs) - { - TArray StateMachineNodes; - TArray SlotNodes; - TArray LinkedAnimGraphNodes; - - Graph->GetNodesOfClass(StateMachineNodes); - Graph->GetNodesOfClass(SlotNodes); - Graph->GetNodesOfClass(LinkedAnimGraphNodes); - - for(UAnimGraphNode_StateMachine* StateMachineNode : StateMachineNodes) - { - int32& Count = OutStateMachineNameToCountMap.FindOrAdd(FName(*StateMachineNode->GetStateMachineName())); - // Add one to count as we've encountered this name - Count++; - } - - for(UAnimGraphNode_Slot* SlotNode : SlotNodes) - { - int32& Count = OutSlotNameToCountMap.FindOrAdd(SlotNode->Node.SlotName); - Count++; - } - - for(UAnimGraphNode_LinkedAnimGraph* LinkedAnimGraphNode : LinkedAnimGraphNodes) - { - GetDuplicatedSlotAndStateNames(LinkedAnimGraphNode, OutStateMachineNameToCountMap, OutSlotNameToCountMap); - } - } - } -} - -int32 FAnimBlueprintCompilerContext::GetAllocationIndexOfNode(UAnimGraphNode_Base* VisualAnimNode) -{ - ProcessAnimationNode(VisualAnimNode); - int32* pResult = AllocatedAnimNodeIndices.Find(VisualAnimNode); - return (pResult != NULL) ? *pResult : INDEX_NONE; -} - -void FAnimBlueprintCompilerContext::PruneIsolatedAnimationNodes(const TArray& RootSet, TArray& GraphNodes) -{ - struct FNodeVisitorDownPoseWires - { - TSet VisitedNodes; - const UAnimationGraphSchema* Schema; - - FNodeVisitorDownPoseWires() - { - Schema = GetDefault(); - } - - void TraverseNodes(UEdGraphNode* Node) - { - VisitedNodes.Add(Node); - - // Follow every exec output pin - for (int32 i = 0; i < Node->Pins.Num(); ++i) - { - UEdGraphPin* MyPin = Node->Pins[i]; - - if ((MyPin->Direction == EGPD_Input) && (Schema->IsPosePin(MyPin->PinType))) - { - for (int32 j = 0; j < MyPin->LinkedTo.Num(); ++j) - { - UEdGraphPin* OtherPin = MyPin->LinkedTo[j]; - UEdGraphNode* OtherNode = OtherPin->GetOwningNode(); - if (!VisitedNodes.Contains(OtherNode)) - { - TraverseNodes(OtherNode); - } - } - } - } - } - }; - - // Prune the nodes that aren't reachable via an animation pose link - FNodeVisitorDownPoseWires Visitor; - - for (auto RootIt = RootSet.CreateConstIterator(); RootIt; ++RootIt) - { - UAnimGraphNode_Base* RootNode = *RootIt; - Visitor.TraverseNodes(RootNode); - } - - for (int32 NodeIndex = 0; NodeIndex < GraphNodes.Num(); ++NodeIndex) - { - UAnimGraphNode_Base* Node = GraphNodes[NodeIndex]; - - // We cant prune linked input poses as even if they are not linked to the root, they are needed for the dynamic link phase at runtime - if (!Visitor.VisitedNodes.Contains(Node) && !IsNodePure(Node) && !Node->IsA()) - { - Node->BreakAllNodeLinks(); - GraphNodes.RemoveAtSwap(NodeIndex); - --NodeIndex; - } - } -} - -void FAnimBlueprintCompilerContext::ProcessAnimationNodesGivenRoot(TArray& AnimNodeList, const TArray& RootSet) -{ - // Now prune based on the root set - if (MessageLog.NumErrors == 0) - { - PruneIsolatedAnimationNodes(RootSet, AnimNodeList); - } - - // Process the remaining nodes - for (auto SourceNodeIt = AnimNodeList.CreateIterator(); SourceNodeIt; ++SourceNodeIt) - { - UAnimGraphNode_Base* VisualAnimNode = *SourceNodeIt; - ProcessAnimationNode(VisualAnimNode); - } -} - -TAutoConsoleVariable CVarAnimDebugCachePoseNodeUpdateOrder(TEXT("a.Compiler.CachePoseNodeUpdateOrderDebug.Enable"), 0, TEXT("Toggle debugging for CacheNodeUpdateOrder debug during AnimBP compilation")); - -void FAnimBlueprintCompilerContext::BuildCachedPoseNodeUpdateOrder() -{ - TArray RootNodes; - ConsolidatedEventGraph->GetNodesOfClass(RootNodes); - - // State results are also "root" nodes, need to find the true roots - RootNodes.RemoveAll([](UAnimGraphNode_Root* InPossibleRootNode) - { - return InPossibleRootNode->GetClass() != UAnimGraphNode_Root::StaticClass(); - }); - - const bool bEnableDebug = (CVarAnimDebugCachePoseNodeUpdateOrder.GetValueOnAnyThread() == 1); - - for(UAnimGraphNode_Root* RootNode : RootNodes) - { - TArray OrderedSavePoseNodes; - - TArray VisitedRootNodes; - - UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("CachePoseNodeOrdering BEGIN")); - - CachePoseNodeOrdering_StartNewTraversal(RootNode, OrderedSavePoseNodes, VisitedRootNodes); - - UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("CachePoseNodeOrdering END")); - - if (bEnableDebug) - { - UE_LOG(LogAnimation, Display, TEXT("Ordered Save Pose Node List:")); - for (UAnimGraphNode_SaveCachedPose* SavedPoseNode : OrderedSavePoseNodes) - { - UE_LOG(LogAnimation, Display, TEXT("\t%s"), *SavedPoseNode->Node.CachePoseName.ToString()) - } - UE_LOG(LogAnimation, Display, TEXT("End List")); - } - - FCachedPoseIndices& OrderedSavedPoseIndices = NewAnimBlueprintClass->OrderedSavedPoseIndicesMap.FindOrAdd(RootNode->Node.Name); - - for(UAnimGraphNode_SaveCachedPose* PoseNode : OrderedSavePoseNodes) - { - if(int32* NodeIndex = AllocatedAnimNodeIndices.Find(PoseNode)) - { - OrderedSavedPoseIndices.OrderedSavedPoseNodeIndices.Add(*NodeIndex); - } - else - { - MessageLog.Error(TEXT("Failed to find index for a saved pose node while building ordered pose list.")); - } - } - } -} - -void FAnimBlueprintCompilerContext::CachePoseNodeOrdering_StartNewTraversal(UAnimGraphNode_Base* InRootNode, TArray &OrderedSavePoseNodes, TArray VisitedRootNodes) -{ - check(InRootNode); - UAnimGraphNode_SaveCachedPose* RootCacheNode = Cast(InRootNode); - FString RootName = RootCacheNode ? RootCacheNode->CacheName : InRootNode->GetName(); - - const bool bEnableDebug = (CVarAnimDebugCachePoseNodeUpdateOrder.GetValueOnAnyThread() == 1); - - UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("StartNewTraversal %s"), *RootName); - - // Track which root nodes we've visited to prevent infinite recursion. - VisitedRootNodes.Add(InRootNode); - - // Need a list of only what we find here to recurse, we can't do that with the total list - TArray InternalOrderedNodes; - - // Traverse whole graph from root collecting SaveCachePose nodes we've touched. - CachePoseNodeOrdering_TraverseInternal(InRootNode, InternalOrderedNodes); - - // Process nodes that we've touched - UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("Process Queue for %s"), *RootName); - for (UAnimGraphNode_SaveCachedPose* QueuedCacheNode : InternalOrderedNodes) - { - if (VisitedRootNodes.Contains(QueuedCacheNode)) - { - UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("Process Queue SaveCachePose %s. ALREADY VISITED, INFINITE RECURSION DETECTED! SKIPPING"), *QueuedCacheNode->CacheName); - MessageLog.Error(*FString::Printf(TEXT("Infinite recursion detected with SaveCachePose %s and %s"), *RootName, *QueuedCacheNode->CacheName)); - continue; - } - else - { - OrderedSavePoseNodes.Remove(QueuedCacheNode); - OrderedSavePoseNodes.Add(QueuedCacheNode); - - CachePoseNodeOrdering_StartNewTraversal(QueuedCacheNode, OrderedSavePoseNodes, VisitedRootNodes); - } - } - - UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("EndNewTraversal %s"), *RootName); -} - -void FAnimBlueprintCompilerContext::CachePoseNodeOrdering_TraverseInternal(UAnimGraphNode_Base* InAnimGraphNode, TArray &OrderedSavePoseNodes) -{ - TArray LinkedAnimNodes; - GetLinkedAnimNodes(InAnimGraphNode, LinkedAnimNodes); - - const bool bEnableDebug = (CVarAnimDebugCachePoseNodeUpdateOrder.GetValueOnAnyThread() == 1); - - for (UAnimGraphNode_Base* LinkedNode : LinkedAnimNodes) - { - UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("\t Processing %s"), *LinkedNode->GetName()); - if (UAnimGraphNode_UseCachedPose* UsePoseNode = Cast(LinkedNode)) - { - if (UAnimGraphNode_SaveCachedPose* SaveNode = UsePoseNode->SaveCachedPoseNode.Get()) - { - UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("\t Queueing SaveCachePose %s"), *SaveNode->CacheName); - - // Requeue the node we found - OrderedSavePoseNodes.Remove(SaveNode); - OrderedSavePoseNodes.Add(SaveNode); - } - } - else if (UAnimGraphNode_StateMachine* StateMachineNode = Cast(LinkedNode)) - { - for (UEdGraph* StateGraph : StateMachineNode->EditorStateMachineGraph->SubGraphs) - { - TArray ResultNodes; - StateGraph->GetNodesOfClass(ResultNodes); - - // We should only get one here but doesn't hurt to loop here in case that changes - for (UAnimGraphNode_StateResult* ResultNode : ResultNodes) - { - CachePoseNodeOrdering_TraverseInternal(ResultNode, OrderedSavePoseNodes); - } - } - } - else - { - CachePoseNodeOrdering_TraverseInternal(LinkedNode, OrderedSavePoseNodes); - } - } -} - -void FAnimBlueprintCompilerContext::GetLinkedAnimNodes(UAnimGraphNode_Base* InGraphNode, TArray &LinkedAnimNodes) -{ - for(UEdGraphPin* Pin : InGraphNode->Pins) - { - if(Pin->Direction == EEdGraphPinDirection::EGPD_Input && - Pin->PinType.PinCategory == TEXT("struct")) - { - if(UScriptStruct* Struct = Cast(Pin->PinType.PinSubCategoryObject.Get())) - { - if(Struct->IsChildOf(FPoseLinkBase::StaticStruct())) - { - GetLinkedAnimNodes_TraversePin(Pin, LinkedAnimNodes); - } - } - } - } -} - -void FAnimBlueprintCompilerContext::GetLinkedAnimNodes_TraversePin(UEdGraphPin* InPin, TArray& LinkedAnimNodes) -{ - if(!InPin) - { - return; - } - - for(UEdGraphPin* LinkedPin : InPin->LinkedTo) - { - if(!LinkedPin) - { - continue; - } - - UEdGraphNode* OwningNode = LinkedPin->GetOwningNode(); - - if(UK2Node_Knot* InnerKnot = Cast(OwningNode)) - { - GetLinkedAnimNodes_TraversePin(InnerKnot->GetInputPin(), LinkedAnimNodes); - } - else if(UAnimGraphNode_Base* AnimNode = Cast(OwningNode)) - { - GetLinkedAnimNodes_ProcessAnimNode(AnimNode, LinkedAnimNodes); - } - } -} - -void FAnimBlueprintCompilerContext::GetLinkedAnimNodes_ProcessAnimNode(UAnimGraphNode_Base* AnimNode, TArray &LinkedAnimNodes) -{ - if(!AllocatedAnimNodes.Contains(AnimNode)) - { - UAnimGraphNode_Base* TrueSourceNode = MessageLog.FindSourceObjectTypeChecked(AnimNode); - - if(UAnimGraphNode_Base** AllocatedNode = SourceNodeToProcessedNodeMap.Find(TrueSourceNode)) - { - LinkedAnimNodes.Add(*AllocatedNode); - } - else - { - FString ErrorString = FText::Format(LOCTEXT("MissingLinkFmt", "Missing allocated node for {0} while searching for node links - likely due to the node having outstanding errors."), FText::FromString(AnimNode->GetName())).ToString(); - MessageLog.Error(*ErrorString); - } - } - else - { - LinkedAnimNodes.Add(AnimNode); - } -} - -void FAnimBlueprintCompilerContext::ProcessAllAnimationNodes() -{ - // Validate the graph - ValidateGraphIsWellFormed(ConsolidatedEventGraph); - - // Validate that we have a skeleton - if ((AnimBlueprint->TargetSkeleton == nullptr) && !AnimBlueprint->bIsNewlyCreated) - { - MessageLog.Error(*LOCTEXT("NoSkeleton", "@@ - The skeleton asset for this animation Blueprint is missing, so it cannot be compiled!").ToString(), AnimBlueprint); - return; - } - - // Build the raw node list - TArray AnimNodeList; - ConsolidatedEventGraph->GetNodesOfClass(/*out*/ AnimNodeList); - - TArray Getters; - ConsolidatedEventGraph->GetNodesOfClass(/*out*/ Getters); - - // Get anim getters from the root anim graph (processing the nodes below will collect them in nested graphs) - TArray RootGraphAnimGetters; - ConsolidatedEventGraph->GetNodesOfClass(RootGraphAnimGetters); - - // Find the root node - TArray RootSet; - - AllocateNodeIndexCounter = 0; - - for (auto SourceNodeIt = AnimNodeList.CreateIterator(); SourceNodeIt; ++SourceNodeIt) - { - UAnimGraphNode_Base* SourceNode = *SourceNodeIt; - UAnimGraphNode_Base* TrueNode = MessageLog.FindSourceObjectTypeChecked(SourceNode); - TrueNode->BlueprintUsage = EBlueprintUsage::NoProperties; - - if (UAnimGraphNode_Root* PossibleRoot = Cast(SourceNode)) - { - if (UAnimGraphNode_Root* Root = ExactCast(PossibleRoot)) - { - RootSet.Add(Root); - } - } - else if (UAnimGraphNode_SaveCachedPose* SavePoseRoot = Cast(SourceNode)) - { - //@TODO: Ideally we only add these if there is a UseCachedPose node referencing them, but those can be anywhere and are hard to grab - SaveCachedPoseNodes.Add(SavePoseRoot->CacheName, SavePoseRoot); - RootSet.Add(SavePoseRoot); - } - } - - if (RootSet.Num() > 0) - { - // Process the animation nodes - ProcessAnimationNodesGivenRoot(AnimNodeList, RootSet); - - // Process the getter nodes in the graph if there were any - for (auto GetterIt = Getters.CreateIterator(); GetterIt; ++GetterIt) - { - ProcessTransitionGetter(*GetterIt, nullptr); // transition nodes should not appear at top-level - } - - // Wire root getters - for(UK2Node_AnimGetter* RootGraphGetter : RootGraphAnimGetters) - { - AutoWireAnimGetter(RootGraphGetter, nullptr); - } - - // Wire nested getters - for(UK2Node_AnimGetter* Getter : FoundGetterNodes) - { - AutoWireAnimGetter(Getter, nullptr); - } - } - else - { - MessageLog.Error(*LOCTEXT("ExpectedAFunctionEntry_Error", "Expected at least one animation root, but did not find any").ToString()); - } - - // Build cached pose map - BuildCachedPoseNodeUpdateOrder(); -} - -int32 FAnimBlueprintCompilerContext::ExpandGraphAndProcessNodes(UEdGraph* SourceGraph, UAnimGraphNode_Base* SourceRootNode, UAnimStateTransitionNode* TransitionNode, TArray* ClonedNodes) -{ - // Clone the nodes from the source graph - // Note that we outer this graph to the ConsolidatedEventGraph to allow ExpandSplitPins to - // correctly retrieve the context for any expanded function calls (custom make/break structs etc.) - UEdGraph* ClonedGraph = FEdGraphUtilities::CloneGraph(SourceGraph, ConsolidatedEventGraph, &MessageLog, true); - - // Grab all the animation nodes and find the corresponding root node in the cloned set - UAnimGraphNode_Base* TargetRootNode = NULL; - TArray AnimNodeList; - TArray Getters; - TArray AnimGetterNodes; - - for (auto NodeIt = ClonedGraph->Nodes.CreateIterator(); NodeIt; ++NodeIt) - { - UEdGraphNode* Node = *NodeIt; - - if (UK2Node_TransitionRuleGetter* GetterNode = Cast(Node)) - { - Getters.Add(GetterNode); - } - else if(UK2Node_AnimGetter* NewGetterNode = Cast(Node)) - { - AnimGetterNodes.Add(NewGetterNode); - } - else if (UAnimGraphNode_Base* TestNode = Cast(Node)) - { - AnimNodeList.Add(TestNode); - - //@TODO: There ought to be a better way to determine this - if (MessageLog.FindSourceObject(TestNode) == MessageLog.FindSourceObject(SourceRootNode)) - { - TargetRootNode = TestNode; - } - } - - if (ClonedNodes != NULL) - { - ClonedNodes->Add(Node); - } - } - check(TargetRootNode); - - // Run another expansion pass to catch the graph we just added (this is slightly wasteful - ExpandSplitPins(ClonedGraph); - - // Move the cloned nodes into the consolidated event graph - const bool bIsLoading = Blueprint->bIsRegeneratingOnLoad || IsAsyncLoading(); - const bool bIsCompiling = Blueprint->bBeingCompiled; - ClonedGraph->MoveNodesToAnotherGraph(ConsolidatedEventGraph, bIsLoading, bIsCompiling); - - // Process any animation nodes - { - TArray RootSet; - RootSet.Add(TargetRootNode); - ProcessAnimationNodesGivenRoot(AnimNodeList, RootSet); - } - - // Process the getter nodes in the graph if there were any - for (auto GetterIt = Getters.CreateIterator(); GetterIt; ++GetterIt) - { - ProcessTransitionGetter(*GetterIt, TransitionNode); - } - - // Wire anim getter nodes - for(UK2Node_AnimGetter* GetterNode : AnimGetterNodes) - { - FoundGetterNodes.Add(GetterNode); - } - - // Returns the index of the processed cloned version of SourceRootNode - return GetAllocationIndexOfNode(TargetRootNode); -} - -void FAnimBlueprintCompilerContext::ProcessStateMachine(UAnimGraphNode_StateMachineBase* StateMachineInstance) -{ - struct FMachineCreator - { - public: - int32 MachineIndex; - TMap StateIndexTable; - TMap TransitionIndexTable; - UAnimBlueprintGeneratedClass* AnimBlueprintClass; - UAnimGraphNode_StateMachineBase* StateMachineInstance; - FCompilerResultsLog& MessageLog; - public: - FMachineCreator(FCompilerResultsLog& InMessageLog, UAnimGraphNode_StateMachineBase* InStateMachineInstance, int32 InMachineIndex, UAnimBlueprintGeneratedClass* InNewClass) - : MachineIndex(InMachineIndex) - , AnimBlueprintClass(InNewClass) - , StateMachineInstance(InStateMachineInstance) - , MessageLog(InMessageLog) - { - FStateMachineDebugData& MachineInfo = GetMachineSpecificDebugData(); - MachineInfo.MachineIndex = MachineIndex; - MachineInfo.MachineInstanceNode = MessageLog.FindSourceObjectTypeChecked(InStateMachineInstance); - - StateMachineInstance->GetNode().StateMachineIndexInClass = MachineIndex; - - FBakedAnimationStateMachine& BakedMachine = GetMachine(); - BakedMachine.MachineName = StateMachineInstance->EditorStateMachineGraph->GetFName(); - BakedMachine.InitialState = INDEX_NONE; - } - - FBakedAnimationStateMachine& GetMachine() - { - return AnimBlueprintClass->BakedStateMachines[MachineIndex]; - } - - FStateMachineDebugData& GetMachineSpecificDebugData() - { - UAnimationStateMachineGraph* SourceGraph = MessageLog.FindSourceObjectTypeChecked(StateMachineInstance->EditorStateMachineGraph); - return AnimBlueprintClass->GetAnimBlueprintDebugData().StateMachineDebugData.FindOrAdd(SourceGraph); - } - - int32 FindOrAddState(UAnimStateNodeBase* StateNode) - { - if (int32* pResult = StateIndexTable.Find(StateNode)) - { - return *pResult; - } - else - { - FBakedAnimationStateMachine& BakedMachine = GetMachine(); - - const int32 StateIndex = BakedMachine.States.Num(); - StateIndexTable.Add(StateNode, StateIndex); - new (BakedMachine.States) FBakedAnimationState(); - - UAnimStateNodeBase* SourceNode = MessageLog.FindSourceObjectTypeChecked(StateNode); - GetMachineSpecificDebugData().NodeToStateIndex.Add(SourceNode, StateIndex); - if (UAnimStateNode* SourceStateNode = Cast(SourceNode)) - { - AnimBlueprintClass->GetAnimBlueprintDebugData().StateGraphToNodeMap.Add(SourceStateNode->BoundGraph, SourceStateNode); - } - - return StateIndex; - } - } - - int32 FindOrAddTransition(UAnimStateTransitionNode* TransitionNode) - { - if (int32* pResult = TransitionIndexTable.Find(TransitionNode)) - { - return *pResult; - } - else - { - FBakedAnimationStateMachine& BakedMachine = GetMachine(); - - const int32 TransitionIndex = BakedMachine.Transitions.Num(); - TransitionIndexTable.Add(TransitionNode, TransitionIndex); - new (BakedMachine.Transitions) FAnimationTransitionBetweenStates(); - - UAnimStateTransitionNode* SourceTransitionNode = MessageLog.FindSourceObjectTypeChecked(TransitionNode); - GetMachineSpecificDebugData().NodeToTransitionIndex.Add(SourceTransitionNode, TransitionIndex); - AnimBlueprintClass->GetAnimBlueprintDebugData().TransitionGraphToNodeMap.Add(SourceTransitionNode->BoundGraph, SourceTransitionNode); - - if (SourceTransitionNode->CustomTransitionGraph != NULL) - { - AnimBlueprintClass->GetAnimBlueprintDebugData().TransitionBlendGraphToNodeMap.Add(SourceTransitionNode->CustomTransitionGraph, SourceTransitionNode); - } - - return TransitionIndex; - } - } - - void Validate() - { - FBakedAnimationStateMachine& BakedMachine = GetMachine(); - - // Make sure there is a valid entry point - if (BakedMachine.InitialState == INDEX_NONE) - { - MessageLog.Warning(*LOCTEXT("NoEntryNode", "There was no entry state connection in @@").ToString(), StateMachineInstance); - BakedMachine.InitialState = 0; - } - else - { - // Make sure the entry node is a state and not a conduit - if (BakedMachine.States[BakedMachine.InitialState].bIsAConduit) - { - UEdGraphNode* StateNode = GetMachineSpecificDebugData().FindNodeFromStateIndex(BakedMachine.InitialState); - MessageLog.Error(*LOCTEXT("BadStateEntryNode", "A conduit (@@) cannot be used as the entry node for a state machine").ToString(), StateNode); - } - } - } - }; - - if (StateMachineInstance->EditorStateMachineGraph == NULL) - { - MessageLog.Error(*LOCTEXT("BadStateMachineNoGraph", "@@ does not have a corresponding graph").ToString(), StateMachineInstance); - return; - } - - TMap AlreadyMergedTransitionList; - - const int32 MachineIndex = NewAnimBlueprintClass->BakedStateMachines.Num(); - new (NewAnimBlueprintClass->BakedStateMachines) FBakedAnimationStateMachine(); - FMachineCreator Oven(MessageLog, StateMachineInstance, MachineIndex, NewAnimBlueprintClass); - - // Map of states that contain a single player node (from state root node index to associated sequence player) - TMap SimplePlayerStatesMap; - - // Process all the states/transitions - for (auto StateNodeIt = StateMachineInstance->EditorStateMachineGraph->Nodes.CreateIterator(); StateNodeIt; ++StateNodeIt) - { - UEdGraphNode* Node = *StateNodeIt; - - if (UAnimStateEntryNode* EntryNode = Cast(Node)) - { - // Handle the state graph entry - FBakedAnimationStateMachine& BakedMachine = Oven.GetMachine(); - if (BakedMachine.InitialState != INDEX_NONE) - { - MessageLog.Error(*LOCTEXT("TooManyStateMachineEntryNodes", "Found an extra entry node @@").ToString(), EntryNode); - } - else if (UAnimStateNodeBase* StartState = Cast(EntryNode->GetOutputNode())) - { - BakedMachine.InitialState = Oven.FindOrAddState(StartState); - } - else - { - MessageLog.Warning(*LOCTEXT("NoConnection", "Entry node @@ is not connected to state").ToString(), EntryNode); - } - } - else if (UAnimStateTransitionNode* TransitionNode = Cast(Node)) - { - TransitionNode->ValidateNodeDuringCompilation(MessageLog); - - const int32 TransitionIndex = Oven.FindOrAddTransition(TransitionNode); - FAnimationTransitionBetweenStates& BakedTransition = Oven.GetMachine().Transitions[TransitionIndex]; - - BakedTransition.CrossfadeDuration = TransitionNode->CrossfadeDuration; - BakedTransition.StartNotify = FindOrAddNotify(TransitionNode->TransitionStart); - BakedTransition.EndNotify = FindOrAddNotify(TransitionNode->TransitionEnd); - BakedTransition.InterruptNotify = FindOrAddNotify(TransitionNode->TransitionInterrupt); - BakedTransition.BlendMode = TransitionNode->BlendMode; - BakedTransition.CustomCurve = TransitionNode->CustomBlendCurve; - BakedTransition.BlendProfile = TransitionNode->BlendProfile; - BakedTransition.LogicType = TransitionNode->LogicType; - - UAnimStateNodeBase* PreviousState = TransitionNode->GetPreviousState(); - UAnimStateNodeBase* NextState = TransitionNode->GetNextState(); - - if ((PreviousState != NULL) && (NextState != NULL)) - { - const int32 PreviousStateIndex = Oven.FindOrAddState(PreviousState); - const int32 NextStateIndex = Oven.FindOrAddState(NextState); - - if (TransitionNode->Bidirectional) - { - MessageLog.Warning(*LOCTEXT("BidirectionalTransWarning", "Bidirectional transitions aren't supported yet @@").ToString(), TransitionNode); - } - - BakedTransition.PreviousState = PreviousStateIndex; - BakedTransition.NextState = NextStateIndex; - } - else - { - MessageLog.Warning(*LOCTEXT("BogusTransition", "@@ is incomplete, without a previous and next state").ToString(), TransitionNode); - BakedTransition.PreviousState = INDEX_NONE; - BakedTransition.NextState = INDEX_NONE; - } - } - else if (UAnimStateNode* StateNode = Cast(Node)) - { - StateNode->ValidateNodeDuringCompilation(MessageLog); - - const int32 StateIndex = Oven.FindOrAddState(StateNode); - FBakedAnimationState& BakedState = Oven.GetMachine().States[StateIndex]; - - if (StateNode->BoundGraph != NULL) - { - BakedState.StateName = StateNode->BoundGraph->GetFName(); - BakedState.StartNotify = FindOrAddNotify(StateNode->StateEntered); - BakedState.EndNotify = FindOrAddNotify(StateNode->StateLeft); - BakedState.FullyBlendedNotify = FindOrAddNotify(StateNode->StateFullyBlended); - BakedState.bIsAConduit = false; - BakedState.bAlwaysResetOnEntry = StateNode->bAlwaysResetOnEntry; - - // Process the inner graph of this state - if (UAnimGraphNode_StateResult* AnimGraphResultNode = CastChecked(StateNode->BoundGraph)->GetResultNode()) - { - ValidateGraphIsWellFormed(StateNode->BoundGraph); - - BakedState.StateRootNodeIndex = ExpandGraphAndProcessNodes(StateNode->BoundGraph, AnimGraphResultNode); - - // See if the state consists of a single sequence player node, and remember the index if so - for (UEdGraphPin* TestPin : AnimGraphResultNode->Pins) - { - if ((TestPin->Direction == EGPD_Input) && (TestPin->LinkedTo.Num() == 1)) - { - if (UAnimGraphNode_SequencePlayer* SequencePlayer = Cast(TestPin->LinkedTo[0]->GetOwningNode())) - { - SimplePlayerStatesMap.Add(BakedState.StateRootNodeIndex, MessageLog.FindSourceObject(SequencePlayer)); - } - } - } - } - else - { - BakedState.StateRootNodeIndex = INDEX_NONE; - MessageLog.Error(*LOCTEXT("StateWithNoResult", "@@ has no result node").ToString(), StateNode); - } - } - else - { - BakedState.StateName = NAME_None; - MessageLog.Error(*LOCTEXT("StateWithBadGraph", "@@ has no bound graph").ToString(), StateNode); - } - - // If this check fires, then something in the machine has changed causing the states array to not - // be a separate allocation, and a state machine inside of this one caused stuff to shift around - checkSlow(&BakedState == &(Oven.GetMachine().States[StateIndex])); - } - else if (UAnimStateConduitNode* ConduitNode = Cast(Node)) - { - ConduitNode->ValidateNodeDuringCompilation(MessageLog); - - const int32 StateIndex = Oven.FindOrAddState(ConduitNode); - FBakedAnimationState& BakedState = Oven.GetMachine().States[StateIndex]; - - BakedState.StateName = ConduitNode->BoundGraph ? ConduitNode->BoundGraph->GetFName() : TEXT("OLD CONDUIT"); - BakedState.bIsAConduit = true; - - if (ConduitNode->BoundGraph != NULL) - { - if (UAnimGraphNode_TransitionResult* EntryRuleResultNode = CastChecked(ConduitNode->BoundGraph)->GetResultNode()) - { - BakedState.EntryRuleNodeIndex = ExpandGraphAndProcessNodes(ConduitNode->BoundGraph, EntryRuleResultNode); - } - } - - // If this check fires, then something in the machine has changed causing the states array to not - // be a separate allocation, and a state machine inside of this one caused stuff to shift around - checkSlow(&BakedState == &(Oven.GetMachine().States[StateIndex])); - } - } - - // Process transitions after all the states because getters within custom graphs may want to - // reference back to other states, which are only valid if they have already been baked - for (auto StateNodeIt = Oven.StateIndexTable.CreateIterator(); StateNodeIt; ++StateNodeIt) - { - UAnimStateNodeBase* StateNode = StateNodeIt.Key(); - const int32 StateIndex = StateNodeIt.Value(); - - FBakedAnimationState& BakedState = Oven.GetMachine().States[StateIndex]; - - // Add indices to all player and layer nodes - TArray GraphsToCheck; - GraphsToCheck.Add(StateNode->GetBoundGraph()); - StateNode->GetBoundGraph()->GetAllChildrenGraphs(GraphsToCheck); - - TArray LinkedAnimLayerNodes; - TArray AssetPlayerNodes; - for (UEdGraph* ChildGraph : GraphsToCheck) - { - ChildGraph->GetNodesOfClass(AssetPlayerNodes); - ChildGraph->GetNodesOfClass(LinkedAnimLayerNodes); - } - - for (UAnimGraphNode_AssetPlayerBase* Node : AssetPlayerNodes) - { - if (int32* IndexPtr = NewAnimBlueprintClass->AnimBlueprintDebugData.NodeGuidToIndexMap.Find(Node->NodeGuid)) - { - BakedState.PlayerNodeIndices.Add(*IndexPtr); - } - } - - for (UAnimGraphNode_LinkedAnimLayer* Node : LinkedAnimLayerNodes) - { - if (int32* IndexPtr = NewAnimBlueprintClass->AnimBlueprintDebugData.NodeGuidToIndexMap.Find(Node->NodeGuid)) - { - BakedState.LayerNodeIndices.Add(*IndexPtr); - } - } - // Handle all the transitions out of this node - TArray TransitionList; - StateNode->GetTransitionList(/*out*/ TransitionList, /*bWantSortedList=*/ true); - - for (auto TransitionIt = TransitionList.CreateIterator(); TransitionIt; ++TransitionIt) - { - UAnimStateTransitionNode* TransitionNode = *TransitionIt; - const int32 TransitionIndex = Oven.FindOrAddTransition(TransitionNode); - - // Validate the blend profile for this transition - incase the skeleton of the node has - // changed or the blend profile no longer exists. - TransitionNode->ValidateBlendProfile(); - - FBakedStateExitTransition& Rule = *new (BakedState.Transitions) FBakedStateExitTransition(); - Rule.bDesiredTransitionReturnValue = (TransitionNode->GetPreviousState() == StateNode); - Rule.TransitionIndex = TransitionIndex; - - if (UAnimGraphNode_TransitionResult* TransitionResultNode = CastChecked(TransitionNode->BoundGraph)->GetResultNode()) - { - if (int32* pIndex = AlreadyMergedTransitionList.Find(TransitionResultNode)) - { - Rule.CanTakeDelegateIndex = *pIndex; - } - else - { - Rule.CanTakeDelegateIndex = ExpandGraphAndProcessNodes(TransitionNode->BoundGraph, TransitionResultNode, TransitionNode); - AlreadyMergedTransitionList.Add(TransitionResultNode, Rule.CanTakeDelegateIndex); - } - } - else - { - Rule.CanTakeDelegateIndex = INDEX_NONE; - MessageLog.Error(*LOCTEXT("TransitionWithNoResult", "@@ has no result node").ToString(), TransitionNode); - } - - // Handle automatic time remaining rules - Rule.bAutomaticRemainingTimeRule = TransitionNode->bAutomaticRuleBasedOnSequencePlayerInState; - Rule.SyncGroupNameToRequireValidMarkersRule = TransitionNode->SyncGroupNameToRequireValidMarkersRule; - - // Handle custom transition graphs - Rule.CustomResultNodeIndex = INDEX_NONE; - if (UAnimationCustomTransitionGraph* CustomTransitionGraph = Cast(TransitionNode->CustomTransitionGraph)) - { - TArray ClonedNodes; - if (CustomTransitionGraph->GetResultNode()) - { - Rule.CustomResultNodeIndex = ExpandGraphAndProcessNodes(TransitionNode->CustomTransitionGraph, CustomTransitionGraph->GetResultNode(), NULL, &ClonedNodes); - } - - // Find all the pose evaluators used in this transition, save handles to them because we need to populate some pose data before executing - TArray TransitionPoseList; - for (auto ClonedNodesIt = ClonedNodes.CreateIterator(); ClonedNodesIt; ++ClonedNodesIt) - { - UEdGraphNode* Node = *ClonedNodesIt; - if (UAnimGraphNode_TransitionPoseEvaluator* TypedNode = Cast(Node)) - { - TransitionPoseList.Add(TypedNode); - } - } - - Rule.PoseEvaluatorLinks.Empty(TransitionPoseList.Num()); - - for (auto TransitionPoseListIt = TransitionPoseList.CreateIterator(); TransitionPoseListIt; ++TransitionPoseListIt) - { - UAnimGraphNode_TransitionPoseEvaluator* TransitionPoseNode = *TransitionPoseListIt; - Rule.PoseEvaluatorLinks.Add( GetAllocationIndexOfNode(TransitionPoseNode) ); - } - } - } - } - - Oven.Validate(); -} - -void FAnimBlueprintCompilerContext::CopyTermDefaultsToDefaultObject(UObject* DefaultObject) -{ - Super::CopyTermDefaultsToDefaultObject(DefaultObject); - - UAnimInstance* DefaultAnimInstance = Cast(DefaultObject); - - if (bIsDerivedAnimBlueprint && DefaultAnimInstance) - { - // If we are a derived animation graph; apply any stored overrides. - // Restore values from the root class to catch values where the override has been removed. - UAnimBlueprintGeneratedClass* RootAnimClass = NewAnimBlueprintClass; - while (UAnimBlueprintGeneratedClass* NextClass = Cast(RootAnimClass->GetSuperClass())) - { - RootAnimClass = NextClass; - } - UObject* RootDefaultObject = RootAnimClass->GetDefaultObject(); - - for (TFieldIterator It(RootAnimClass); It; ++It) - { - FProperty* RootProp = *It; - - if (FStructProperty* RootStructProp = CastField(RootProp)) - { - if (RootStructProp->Struct->IsChildOf(FAnimNode_Base::StaticStruct())) - { - FStructProperty* ChildStructProp = FindFProperty(NewAnimBlueprintClass, *RootStructProp->GetName()); - check(ChildStructProp); - uint8* SourcePtr = RootStructProp->ContainerPtrToValuePtr(RootDefaultObject); - uint8* DestPtr = ChildStructProp->ContainerPtrToValuePtr(DefaultAnimInstance); - check(SourcePtr && DestPtr); - RootStructProp->CopyCompleteValue(DestPtr, SourcePtr); - } - } - } - } - - // Give game-specific logic a chance to replace animations - if(DefaultAnimInstance) - { - DefaultAnimInstance->ApplyAnimOverridesToCDO(MessageLog); - } - - if (bIsDerivedAnimBlueprint && DefaultAnimInstance) - { - // Patch the overridden values into the CDO - TArray AssetOverrides; - AnimBlueprint->GetAssetOverrides(AssetOverrides); - for (FAnimParentNodeAssetOverride* Override : AssetOverrides) - { - if (Override->NewAsset) - { - FAnimNode_Base* BaseNode = NewAnimBlueprintClass->GetPropertyInstance(DefaultAnimInstance, Override->ParentNodeGuid, EPropertySearchMode::Hierarchy); - if (BaseNode) - { - BaseNode->OverrideAsset(Override->NewAsset); - } - } - } - - return; - } - - if(DefaultAnimInstance) - { - int32 LinkIndexCount = 0; - TMap LinkIndexMap; - TMap NodeBaseAddresses; - - // Initialize animation nodes from their templates - for (TFieldIterator It(DefaultAnimInstance->GetClass(), EFieldIteratorFlags::ExcludeSuper); It; ++It) - { - FProperty* TargetProperty = *It; - - if (UAnimGraphNode_Base* VisualAnimNode = AllocatedNodePropertiesToNodes.FindRef(TargetProperty)) - { - const FStructProperty* SourceNodeProperty = VisualAnimNode->GetFNodeProperty(); - check(SourceNodeProperty != NULL); - check(CastFieldChecked(TargetProperty)->Struct == SourceNodeProperty->Struct); - - uint8* DestinationPtr = TargetProperty->ContainerPtrToValuePtr(DefaultAnimInstance); - uint8* SourcePtr = SourceNodeProperty->ContainerPtrToValuePtr(VisualAnimNode); - - if(UAnimGraphNode_Root* RootNode = ExactCast(VisualAnimNode)) - { - // patch graph name into root nodes - FAnimNode_Root NewRoot = *reinterpret_cast(SourcePtr); - NewRoot.Name = Cast(MessageLog.FindSourceObject(RootNode))->GetGraph()->GetFName(); - TargetProperty->CopyCompleteValue(DestinationPtr, &NewRoot); - } - else if(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode = ExactCast(VisualAnimNode)) - { - // patch graph name into linked input pose nodes - FAnimNode_LinkedInputPose NewLinkedInputPose = *reinterpret_cast(SourcePtr); - NewLinkedInputPose.Graph = Cast(MessageLog.FindSourceObject(LinkedInputPoseNode))->GetGraph()->GetFName(); - TargetProperty->CopyCompleteValue(DestinationPtr, &NewLinkedInputPose); - } - else if(UAnimGraphNode_LinkedAnimGraph* LinkedAnimGraphNode = ExactCast(VisualAnimNode)) - { - // patch node index into linked anim graph nodes - FAnimNode_LinkedAnimGraph NewLinkedAnimGraph = *reinterpret_cast(SourcePtr); - NewLinkedAnimGraph.NodeIndex = LinkIndexCount; - TargetProperty->CopyCompleteValue(DestinationPtr, &NewLinkedAnimGraph); - } - else if(UAnimGraphNode_LinkedAnimLayer* LinkedAnimLayerNode = ExactCast(VisualAnimNode)) - { - // patch node index into linked anim layer nodes - FAnimNode_LinkedAnimLayer NewLinkedAnimLayer = *reinterpret_cast(SourcePtr); - NewLinkedAnimLayer.NodeIndex = LinkIndexCount; - TargetProperty->CopyCompleteValue(DestinationPtr, &NewLinkedAnimLayer); - } - else - { - TargetProperty->CopyCompleteValue(DestinationPtr, SourcePtr); - } - - LinkIndexMap.Add(VisualAnimNode, LinkIndexCount); - NodeBaseAddresses.Add(VisualAnimNode, DestinationPtr); - ++LinkIndexCount; - } - } - - // And wire up node links - for (auto PoseLinkIt = ValidPoseLinkList.CreateIterator(); PoseLinkIt; ++PoseLinkIt) - { - FPoseLinkMappingRecord& Record = *PoseLinkIt; - - UAnimGraphNode_Base* LinkingNode = Record.GetLinkingNode(); - UAnimGraphNode_Base* LinkedNode = Record.GetLinkedNode(); - - // @TODO this is quick solution for crash - if there were previous errors and some nodes were not added, they could still end here - - // this check avoids that and since there are already errors, compilation won't be successful. - // but I'd prefer stopping compilation earlier to avoid getting here in first place - if (LinkIndexMap.Contains(LinkingNode) && LinkIndexMap.Contains(LinkedNode)) - { - const int32 SourceNodeIndex = LinkIndexMap.FindChecked(LinkingNode); - const int32 LinkedNodeIndex = LinkIndexMap.FindChecked(LinkedNode); - uint8* DestinationPtr = NodeBaseAddresses.FindChecked(LinkingNode); - - Record.PatchLinkIndex(DestinationPtr, LinkedNodeIndex, SourceNodeIndex); - } - } - - // And patch evaluation function entry names - for (auto EvalLinkIt = ValidEvaluationHandlerList.CreateIterator(); EvalLinkIt; ++EvalLinkIt) - { - FEvaluationHandlerRecord& Record = *EvalLinkIt; - - // validate fast path copy records before patching - Record.ValidateFastPath(DefaultAnimInstance->GetClass()); - - // patch either fast-path copy records or generated function names into the CDO - Record.PatchFunctionNameAndCopyRecordsInto(NewAnimBlueprintClass->EvaluateGraphExposedInputs[ Record.EvaluationHandlerIdx ]); - } - - // And patch in constant values that don't need to be re-evaluated every frame - for (auto LiteralLinkIt = ValidAnimNodePinConstants.CreateIterator(); LiteralLinkIt; ++LiteralLinkIt) - { - FEffectiveConstantRecord& ConstantRecord = *LiteralLinkIt; - - //const FString ArrayClause = (ConstantRecord.ArrayIndex != INDEX_NONE) ? FString::Printf(TEXT("[%d]"), ConstantRecord.ArrayIndex) : FString(); - //const FString ValueClause = ConstantRecord.LiteralSourcePin->GetDefaultAsString(); - //MessageLog.Note(*FString::Printf(TEXT("Want to set %s.%s%s to %s"), *ConstantRecord.NodeVariableProperty->GetName(), *ConstantRecord.ConstantProperty->GetName(), *ArrayClause, *ValueClause)); - - if (!ConstantRecord.Apply(DefaultAnimInstance)) - { - MessageLog.Error(TEXT("ICE: Failed to push literal value from @@ into CDO"), ConstantRecord.LiteralSourcePin); - } - } - - UAnimBlueprintGeneratedClass* AnimBlueprintGeneratedClass = CastChecked(NewClass); - - // copy threaded update flag to CDO - DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = AnimBlueprint->bUseMultiThreadedAnimationUpdate; - - // Verify thread-safety - if(GetDefault()->bAllowMultiThreadedAnimationUpdate && DefaultAnimInstance->bUseMultiThreadedAnimationUpdate) - { - // If we are a child anim BP, check parent classes & their CDOs - if (UAnimBlueprintGeneratedClass* ParentClass = Cast(AnimBlueprintGeneratedClass->GetSuperClass())) - { - UAnimBlueprint* ParentAnimBlueprint = Cast(ParentClass->ClassGeneratedBy); - if (ParentAnimBlueprint && !ParentAnimBlueprint->bUseMultiThreadedAnimationUpdate) - { - DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false; - } - - UAnimInstance* ParentDefaultObject = Cast(ParentClass->GetDefaultObject(false)); - if (ParentDefaultObject && !ParentDefaultObject->bUseMultiThreadedAnimationUpdate) - { - DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false; - } - } - - // iterate all properties to determine validity - for (FStructProperty* Property : TFieldRange(AnimBlueprintGeneratedClass, EFieldIteratorFlags::IncludeSuper)) - { - if(Property->Struct->IsChildOf(FAnimNode_Base::StaticStruct())) - { - FAnimNode_Base* AnimNode = Property->ContainerPtrToValuePtr(DefaultAnimInstance); - if(!AnimNode->CanUpdateInWorkerThread()) - { - MessageLog.Warning(*FText::Format(LOCTEXT("HasIncompatibleNode", "Found incompatible node \"{0}\" in blend graph. Disable threaded update or use member variable access."), FText::FromName(Property->Struct->GetFName())).ToString()) - ->AddToken(FDocumentationToken::Create(TEXT("Engine/Animation/AnimBlueprints/AnimGraph")));; - - DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false; - } - } - } - - if (FunctionList.Num() > 0) - { - // find the ubergraph in the function list - FKismetFunctionContext* UbergraphFunctionContext = nullptr; - for (FKismetFunctionContext& FunctionContext : FunctionList) - { - if (FunctionList[0].Function->GetName().StartsWith(TEXT("ExecuteUbergraph"))) - { - UbergraphFunctionContext = &FunctionContext; - break; - } - } - - if (UbergraphFunctionContext) - { - // run through the per-node compiled statements looking for struct-sets used by anim nodes - for (auto& StatementPair : UbergraphFunctionContext->StatementsPerNode) - { - if (UK2Node_StructMemberSet* StructMemberSetNode = Cast(StatementPair.Key)) - { - UObject* SourceNode = MessageLog.FindSourceObject(StructMemberSetNode); - - if (SourceNode && StructMemberSetNode->StructType->IsChildOf(FAnimNode_Base::StaticStruct())) - { - for (FBlueprintCompiledStatement* Statement : StatementPair.Value) - { - if (Statement->Type == KCST_CallFunction && Statement->FunctionToCall) - { - // pure function? - const bool bPureFunctionCall = Statement->FunctionToCall->HasAnyFunctionFlags(FUNC_BlueprintPure); - - // function called on something other than function library or anim instance? - UClass* FunctionClass = CastChecked(Statement->FunctionToCall->GetOuter()); - const bool bFunctionLibraryCall = FunctionClass->IsChildOf(); - const bool bAnimInstanceCall = FunctionClass->IsChildOf(); - - // Whitelisted/blacklisted? Some functions are not really 'pure', so we give people the opportunity to mark them up. - // Mark up the class if it is generally thread safe, then unsafe functions can be marked up individually. We assume - // that classes are unsafe by default, as well as if they are marked up NotBlueprintThreadSafe. - const bool bClassThreadSafe = FunctionClass->HasMetaData(TEXT("BlueprintThreadSafe")); - const bool bClassNotThreadSafe = FunctionClass->HasMetaData(TEXT("NotBlueprintThreadSafe")) || !FunctionClass->HasMetaData(TEXT("BlueprintThreadSafe")); - const bool bFunctionThreadSafe = Statement->FunctionToCall->HasMetaData(TEXT("BlueprintThreadSafe")); - const bool bFunctionNotThreadSafe = Statement->FunctionToCall->HasMetaData(TEXT("NotBlueprintThreadSafe")); - - const bool bThreadSafe = (bClassThreadSafe && !bFunctionNotThreadSafe) || (bClassNotThreadSafe && bFunctionThreadSafe); - - const bool bValidForUsage = bPureFunctionCall && bThreadSafe && (bFunctionLibraryCall || bAnimInstanceCall); - - if (!bValidForUsage) - { - UEdGraphNode* FunctionNode = nullptr; - if (Statement->FunctionContext && Statement->FunctionContext->SourcePin) - { - FunctionNode = Statement->FunctionContext->SourcePin->GetOwningNode(); - } - else if (Statement->LHS && Statement->LHS->SourcePin) - { - FunctionNode = Statement->LHS->SourcePin->GetOwningNode(); - } - - if (FunctionNode) - { - MessageLog.Warning(*LOCTEXT("NotThreadSafeWarningNodeContext", "Node @@ uses potentially thread-unsafe call @@. Disable threaded update or use a thread-safe call. Function may need BlueprintThreadSafe metadata adding.").ToString(), SourceNode, FunctionNode) - ->AddToken(FDocumentationToken::Create(TEXT("Engine/Animation/AnimBlueprints/AnimGraph"))); - } - else if (Statement->FunctionToCall) - { - MessageLog.Warning(*FText::Format(LOCTEXT("NotThreadSafeWarningFunctionContext", "Node @@ uses potentially thread-unsafe call {0}. Disable threaded update or use a thread-safe call. Function may need BlueprintThreadSafe metadata adding."), Statement->FunctionToCall->GetDisplayNameText()).ToString(), SourceNode) - ->AddToken(FDocumentationToken::Create(TEXT("Engine/Animation/AnimBlueprints/AnimGraph"))); - } - else - { - MessageLog.Warning(*LOCTEXT("NotThreadSafeWarningUnknownContext", "Node @@ uses potentially thread-unsafe call. Disable threaded update or use a thread-safe call.").ToString(), SourceNode) - ->AddToken(FDocumentationToken::Create(TEXT("Engine/Animation/AnimBlueprints/AnimGraph"))); - } - - DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false; - } - } - } - } - } - } - } - } - } - - for (const FEffectiveConstantRecord& ConstantRecord : ValidAnimNodePinConstants) - { - UAnimGraphNode_Base* Node = CastChecked(ConstantRecord.LiteralSourcePin->GetOwningNode()); - UAnimGraphNode_Base* TrueNode = MessageLog.FindSourceObjectTypeChecked(Node); - TrueNode->BlueprintUsage = EBlueprintUsage::DoesNotUseBlueprint; - } - - for(const FEvaluationHandlerRecord& EvaluationHandler : ValidEvaluationHandlerList) - { - if(EvaluationHandler.ServicedProperties.Num() > 0) - { - const FAnimNodeSinglePropertyHandler& Handler = EvaluationHandler.ServicedProperties.CreateConstIterator()->Value; - check(Handler.CopyRecords.Num() > 0); - check(Handler.CopyRecords[0].DestPin != nullptr); - UAnimGraphNode_Base* Node = CastChecked(Handler.CopyRecords[0].DestPin->GetOwningNode()); - UAnimGraphNode_Base* TrueNode = MessageLog.FindSourceObjectTypeChecked(Node); - - FExposedValueHandler* HandlerPtr = &NewAnimBlueprintClass->EvaluateGraphExposedInputs[ EvaluationHandler.EvaluationHandlerIdx ]; - TrueNode->BlueprintUsage = HandlerPtr->BoundFunction != NAME_None ? EBlueprintUsage::UsesBlueprint : EBlueprintUsage::DoesNotUseBlueprint; - -#if WITH_EDITORONLY_DATA // ANIMINST_PostCompileValidation - const bool bWarnAboutBlueprintUsage = AnimBlueprint->bWarnAboutBlueprintUsage || DefaultAnimInstance->PCV_ShouldWarnAboutNodesNotUsingFastPath(); - const bool bNotifyAboutBlueprintUsage = DefaultAnimInstance->PCV_ShouldNotifyAboutNodesNotUsingFastPath(); -#else - const bool bWarnAboutBlueprintUsage = AnimBlueprint->bWarnAboutBlueprintUsage; - const bool bNotifyAboutBlueprintUsage = false; -#endif - if ((TrueNode->BlueprintUsage == EBlueprintUsage::UsesBlueprint) && (bWarnAboutBlueprintUsage || bNotifyAboutBlueprintUsage)) - { - const FString MessageString = LOCTEXT("BlueprintUsageWarning", "Node @@ uses Blueprint to update its values, access member variables directly or use a constant value for better performance.").ToString(); - if (bWarnAboutBlueprintUsage) - { - MessageLog.Warning(*MessageString, Node); - } - else - { - MessageLog.Note(*MessageString, Node); - } - } - } - } - } -} - -void FAnimBlueprintCompilerContext::ExpandSplitPins(UEdGraph* InGraph) -{ - for (TArray::TIterator NodeIt(InGraph->Nodes); NodeIt; ++NodeIt) - { - UK2Node* K2Node = Cast(*NodeIt); - if (K2Node != nullptr) - { - K2Node->ExpandSplitPins(*this, InGraph); - } - } -} - -// Merges in any all ubergraph pages into the gathering ubergraph -void FAnimBlueprintCompilerContext::MergeUbergraphPagesIn(UEdGraph* Ubergraph) -{ - Super::MergeUbergraphPagesIn(Ubergraph); - - if (bIsDerivedAnimBlueprint) - { - // Skip any work related to an anim graph, it's all done by the parent class - } - else - { - // Move all animation graph nodes and associated pure logic chains into the consolidated event graph - auto MoveGraph = [this](UEdGraph* InGraph) - { - if (InGraph->Schema->IsChildOf(UAnimationGraphSchema::StaticClass())) - { - // Merge all the animation nodes, contents, etc... into the ubergraph - UEdGraph* ClonedGraph = FEdGraphUtilities::CloneGraph(InGraph, NULL, &MessageLog, true); - const bool bIsLoading = Blueprint->bIsRegeneratingOnLoad || IsAsyncLoading(); - const bool bIsCompiling = Blueprint->bBeingCompiled; - ClonedGraph->MoveNodesToAnotherGraph(ConsolidatedEventGraph, bIsLoading, bIsCompiling); - } - }; - - for (UEdGraph* Graph : Blueprint->FunctionGraphs) - { - MoveGraph(Graph); - } - - for(FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) - { - for(UEdGraph* Graph : InterfaceDesc.Graphs) - { - MoveGraph(Graph); - } - } - - // Make sure we expand any split pins here before we process animation nodes. - ExpandSplitPins(ConsolidatedEventGraph); - - // Compile the animation graph - ProcessAllAnimationNodes(); - } -} - -void FAnimBlueprintCompilerContext::ProcessOneFunctionGraph(UEdGraph* SourceGraph, bool bInternalFunction) -{ - if (SourceGraph->Schema->IsChildOf(UAnimationGraphSchema::StaticClass())) - { - // Animation graph - // Do nothing, as this graph has already been processed - } - else if (SourceGraph->Schema->IsChildOf(UAnimationStateMachineSchema::StaticClass())) - { - // Animation state machine - // Do nothing, as this graph has already been processed - - //@TODO: These should all have been moved to be child graphs by now - //ensure(false); - } - else - { - // Let the regular K2 compiler handle this one - Super::ProcessOneFunctionGraph(SourceGraph, bInternalFunction); - } -} - -void FAnimBlueprintCompilerContext::ProcessLinkedInputPose(UAnimGraphNode_LinkedInputPose* InLinkedInputPose) -{ - InLinkedInputPose->IterateFunctionParameters([this, InLinkedInputPose](const FName& InName, FEdGraphPinType InPinType) - { - if(!UAnimationGraphSchema::IsPosePin(InPinType)) - { - // create properties for 'local' linked input pose pins - FProperty* NewLinkedInputPoseProperty = CreateVariable(InName, InPinType); - if(NewLinkedInputPoseProperty) - { - if(bIsFullCompile) - { - UEdGraphPin* Pin = InLinkedInputPose->FindPin(InName, EGPD_Output); - if(Pin) - { - // Create new node for property access - UK2Node_VariableGet* VariableGetNode = SpawnIntermediateNode(InLinkedInputPose, InLinkedInputPose->GetGraph()); - VariableGetNode->SetFromProperty(NewLinkedInputPoseProperty, true, NewLinkedInputPoseProperty->GetOwnerClass()); - VariableGetNode->AllocateDefaultPins(); - - // Add pin to generated variable association, used for pin watching - UEdGraphPin* TrueSourcePin = MessageLog.FindSourcePin(Pin); - if (TrueSourcePin) - { - NewClass->GetDebugData().RegisterClassPropertyAssociation(TrueSourcePin, NewLinkedInputPoseProperty); - } - - // link up to new node - note that this is not a FindPinChecked because if an interface changes without the - // implementing class being loaded, then its graphs will not be conformed until AFTER the skeleton class - // has been compiled, so the variable cannot be created. This also doesnt matter, as there wont be anything connected - // to the pin yet anyways. - UEdGraphPin* VariablePin = VariableGetNode->FindPin(NewLinkedInputPoseProperty->GetFName()); - if(VariablePin) - { - TArray Links = Pin->LinkedTo; - Pin->BreakAllPinLinks(); - - for(UEdGraphPin* LinkPin : Links) - { - VariablePin->MakeLinkTo(LinkPin); - } - } - } - } - } - } - }); -} - -void FAnimBlueprintCompilerContext::EnsureProperGeneratedClass(UClass*& TargetUClass) -{ - if( TargetUClass && !((UObject*)TargetUClass)->IsA(UAnimBlueprintGeneratedClass::StaticClass()) ) - { - FKismetCompilerUtilities::ConsignToOblivion(TargetUClass, Blueprint->bIsRegeneratingOnLoad); - TargetUClass = NULL; - } -} - -void FAnimBlueprintCompilerContext::SpawnNewClass(const FString& NewClassName) -{ - NewAnimBlueprintClass = FindObject(Blueprint->GetOutermost(), *NewClassName); - - if (NewAnimBlueprintClass == NULL) - { - NewAnimBlueprintClass = NewObject(Blueprint->GetOutermost(), FName(*NewClassName), RF_Public | RF_Transactional); - } - else - { - // Already existed, but wasn't linked in the Blueprint yet due to load ordering issues - FBlueprintCompileReinstancer::Create(NewAnimBlueprintClass); - } - NewClass = NewAnimBlueprintClass; -} - -void FAnimBlueprintCompilerContext::OnPostCDOCompiled() -{ - for (UAnimBlueprintGeneratedClass* ClassWithInputHandlers = NewAnimBlueprintClass; ClassWithInputHandlers != nullptr; ClassWithInputHandlers = Cast(ClassWithInputHandlers->GetSuperClass())) - { - FExposedValueHandler::Initialize(ClassWithInputHandlers->EvaluateGraphExposedInputs, NewAnimBlueprintClass->ClassDefaultObject); - - ClassWithInputHandlers->LinkFunctionsToDefaultObjectNodes(NewAnimBlueprintClass->ClassDefaultObject); - } -} - -void FAnimBlueprintCompilerContext::OnNewClassSet(UBlueprintGeneratedClass* ClassToUse) -{ - NewAnimBlueprintClass = CastChecked(ClassToUse); -} - -void FAnimBlueprintCompilerContext::CleanAndSanitizeClass(UBlueprintGeneratedClass* ClassToClean, UObject*& InOldCDO) -{ - Super::CleanAndSanitizeClass(ClassToClean, InOldCDO); - - // Make sure our typed pointer is set - check(ClassToClean == NewClass && NewAnimBlueprintClass == NewClass); - - NewAnimBlueprintClass->AnimBlueprintDebugData = FAnimBlueprintDebugData(); - - // Reset the baked data - //@TODO: Move this into PurgeClass - NewAnimBlueprintClass->BakedStateMachines.Empty(); - NewAnimBlueprintClass->AnimNotifies.Empty(); - NewAnimBlueprintClass->AnimBlueprintFunctions.Empty(); - NewAnimBlueprintClass->OrderedSavedPoseIndicesMap.Empty(); - NewAnimBlueprintClass->AnimNodeProperties.Empty(); - NewAnimBlueprintClass->LinkedAnimGraphNodeProperties.Empty(); - NewAnimBlueprintClass->LinkedAnimLayerNodeProperties.Empty(); - NewAnimBlueprintClass->PreUpdateNodeProperties.Empty(); - NewAnimBlueprintClass->DynamicResetNodeProperties.Empty(); - NewAnimBlueprintClass->StateMachineNodeProperties.Empty(); - NewAnimBlueprintClass->InitializationNodeProperties.Empty(); - NewAnimBlueprintClass->EvaluateGraphExposedInputs.Empty(); - NewAnimBlueprintClass->GraphAssetPlayerInformation.Empty(); - NewAnimBlueprintClass->GraphBlendOptions.Empty(); - - // Copy over runtime data from the blueprint to the class - NewAnimBlueprintClass->TargetSkeleton = AnimBlueprint->TargetSkeleton; - - UAnimBlueprint* RootAnimBP = UAnimBlueprint::FindRootAnimBlueprint(AnimBlueprint); - bIsDerivedAnimBlueprint = RootAnimBP != NULL; - - // Copy up data from a parent anim blueprint - if (bIsDerivedAnimBlueprint) - { - UAnimBlueprintGeneratedClass* RootAnimClass = CastChecked(RootAnimBP->GeneratedClass); - - NewAnimBlueprintClass->BakedStateMachines.Append(RootAnimClass->BakedStateMachines); - NewAnimBlueprintClass->AnimNotifies.Append(RootAnimClass->AnimNotifies); - NewAnimBlueprintClass->OrderedSavedPoseIndicesMap = RootAnimClass->OrderedSavedPoseIndicesMap; - } -} - -void FAnimBlueprintCompilerContext::FinishCompilingClass(UClass* Class) -{ - const UAnimBlueprint* PossibleRoot = UAnimBlueprint::FindRootAnimBlueprint(AnimBlueprint); - const UAnimBlueprint* Src = PossibleRoot ? PossibleRoot : AnimBlueprint; - - UAnimBlueprintGeneratedClass* AnimBlueprintGeneratedClass = CastChecked(Class); - AnimBlueprintGeneratedClass->SyncGroupNames.Reset(); - AnimBlueprintGeneratedClass->SyncGroupNames.Reserve(Src->Groups.Num()); - for (const FAnimGroupInfo& GroupInfo : Src->Groups) - { - AnimBlueprintGeneratedClass->SyncGroupNames.Add(GroupInfo.Name); - } - - // Add graph blend options to class if blend values were actually customized - auto AddBlendOptions = [AnimBlueprintGeneratedClass](UEdGraph* Graph) - { - UAnimationGraph* AnimGraph = Cast(Graph); - if (AnimGraph && (AnimGraph->BlendOptions.BlendInTime >= 0.0f || AnimGraph->BlendOptions.BlendOutTime >= 0.0f)) - { - AnimBlueprintGeneratedClass->GraphBlendOptions.Add(AnimGraph->GetFName(), AnimGraph->BlendOptions); - } - }; - - - for (UEdGraph* Graph : Blueprint->FunctionGraphs) - { - AddBlendOptions(Graph); - } - - for (FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) - { - if (InterfaceDesc.Interface->IsChildOf()) - { - for (UEdGraph* Graph : InterfaceDesc.Graphs) - { - AddBlendOptions(Graph); - } - } - } - - Super::FinishCompilingClass(Class); -} - -void FAnimBlueprintCompilerContext::PostCompile() -{ - Super::PostCompile(); - - for (UPoseWatch* PoseWatch : AnimBlueprint->PoseWatches) - { - AnimationEditorUtils::SetPoseWatch(PoseWatch, AnimBlueprint); - } - - UAnimBlueprintGeneratedClass* AnimBlueprintGeneratedClass = CastChecked(NewClass); - if(UAnimInstance* DefaultAnimInstance = Cast(AnimBlueprintGeneratedClass->GetDefaultObject())) - { - // iterate all anim node and call PostCompile - const USkeleton* CurrentSkeleton = AnimBlueprint->TargetSkeleton; - for (FStructProperty* Property : TFieldRange(AnimBlueprintGeneratedClass, EFieldIteratorFlags::IncludeSuper)) - { - if (Property->Struct->IsChildOf(FAnimNode_Base::StaticStruct())) - { - FAnimNode_Base* AnimNode = Property->ContainerPtrToValuePtr(DefaultAnimInstance); - AnimNode->PostCompile(CurrentSkeleton); - } - } - } -} - -void FAnimBlueprintCompilerContext::CreateFunctionList() -{ - // (These will now be processed after uber graph merge) - - // Build the list of functions and do preprocessing on all of them - Super::CreateFunctionList(); -} - -void FAnimBlueprintCompilerContext::ProcessTransitionGetter(UK2Node_TransitionRuleGetter* Getter, UAnimStateTransitionNode* TransitionNode) -{ - // Get common elements for multiple getters - UEdGraphPin* OutputPin = Getter->GetOutputPin(); - - UEdGraphPin* SourceTimePin = NULL; - UAnimationAsset* AnimAsset= NULL; - int32 PlayerNodeIndex = INDEX_NONE; - - if (UAnimGraphNode_Base* SourcePlayerNode = Getter->AssociatedAnimAssetPlayerNode) - { - // This check should never fail as the source state is always processed first before handling it's rules - UAnimGraphNode_Base* TrueSourceNode = MessageLog.FindSourceObjectTypeChecked(SourcePlayerNode); - UAnimGraphNode_Base* UndertypedPlayerNode = SourceNodeToProcessedNodeMap.FindRef(TrueSourceNode); - - if (UndertypedPlayerNode == NULL) - { - MessageLog.Error(TEXT("ICE: Player node @@ was not processed prior to handling a transition getter @@ that used it"), SourcePlayerNode, Getter); - return; - } - - // Make sure the node is still relevant - UEdGraph* PlayerGraph = UndertypedPlayerNode->GetGraph(); - if (!PlayerGraph->Nodes.Contains(UndertypedPlayerNode)) - { - MessageLog.Error(TEXT("@@ is not associated with a node in @@; please delete and recreate it"), Getter, PlayerGraph); - } - - // Make sure the referenced AnimAsset player has been allocated - PlayerNodeIndex = GetAllocationIndexOfNode(UndertypedPlayerNode); - if (PlayerNodeIndex == INDEX_NONE) - { - MessageLog.Error(*LOCTEXT("BadAnimAssetNodeUsedInGetter", "@@ doesn't have a valid associated AnimAsset node. Delete and recreate it").ToString(), Getter); - } - - // Grab the AnimAsset, and time pin if needed - UScriptStruct* TimePropertyInStructType = NULL; - const TCHAR* TimePropertyName = NULL; - if (UndertypedPlayerNode->DoesSupportTimeForTransitionGetter()) - { - AnimAsset = UndertypedPlayerNode->GetAnimationAsset(); - TimePropertyInStructType = UndertypedPlayerNode->GetTimePropertyStruct(); - TimePropertyName = UndertypedPlayerNode->GetTimePropertyName(); - } - else - { - MessageLog.Error(TEXT("@@ is associated with @@, which is an unexpected type"), Getter, UndertypedPlayerNode); - } - - bool bNeedTimePin = false; - - // Determine if we need to read the current time variable from the specified sequence player - switch (Getter->GetterType) - { - case ETransitionGetter::AnimationAsset_GetCurrentTime: - case ETransitionGetter::AnimationAsset_GetCurrentTimeFraction: - case ETransitionGetter::AnimationAsset_GetTimeFromEnd: - case ETransitionGetter::AnimationAsset_GetTimeFromEndFraction: - bNeedTimePin = true; - break; - default: - bNeedTimePin = false; - break; - } - - if (bNeedTimePin && (PlayerNodeIndex != INDEX_NONE) && (TimePropertyName != NULL) && (TimePropertyInStructType != NULL)) - { - FProperty* NodeProperty = AllocatedPropertiesByIndex.FindChecked(PlayerNodeIndex); - - // Create a struct member read node to grab the current position of the sequence player node - UK2Node_StructMemberGet* TimeReadNode = SpawnIntermediateNode(Getter, ConsolidatedEventGraph); - TimeReadNode->VariableReference.SetSelfMember(NodeProperty->GetFName()); - TimeReadNode->StructType = TimePropertyInStructType; - - TimeReadNode->AllocatePinsForSingleMemberGet(TimePropertyName); - SourceTimePin = TimeReadNode->FindPinChecked(TimePropertyName); - } - } - - // Expand it out - UK2Node_CallFunction* GetterHelper = NULL; - switch (Getter->GetterType) - { - case ETransitionGetter::AnimationAsset_GetCurrentTime: - if ((AnimAsset != NULL) && (SourceTimePin != NULL)) - { - GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceAssetPlayerTime")); - GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); - } - else - { - if (Getter->AssociatedAnimAssetPlayerNode) - { - MessageLog.Error(TEXT("Please replace @@ with Get Relevant Anim Time. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); - } - else - { - MessageLog.Error(TEXT("@@ is not asscociated with an asset player"), Getter); - } - } - break; - case ETransitionGetter::AnimationAsset_GetLength: - if (AnimAsset != NULL) - { - GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceAssetPlayerLength")); - GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); - } - else - { - if (Getter->AssociatedAnimAssetPlayerNode) - { - MessageLog.Error(TEXT("Please replace @@ with Get Relevant Anim Length. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); - } - else - { - MessageLog.Error(TEXT("@@ is not asscociated with an asset player"), Getter); - } - } - break; - case ETransitionGetter::AnimationAsset_GetCurrentTimeFraction: - if ((AnimAsset != NULL) && (SourceTimePin != NULL)) - { - GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceAssetPlayerTimeFraction")); - GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); - } - else - { - if (Getter->AssociatedAnimAssetPlayerNode) - { - MessageLog.Error(TEXT("Please replace @@ with Get Relevant Anim Time Fraction. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); - } - else - { - MessageLog.Error(TEXT("@@ is not asscociated with an asset player"), Getter); - } - } - break; - case ETransitionGetter::AnimationAsset_GetTimeFromEnd: - if ((AnimAsset != NULL) && (SourceTimePin != NULL)) - { - GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceAssetPlayerTimeFromEnd")); - GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); - } - else - { - if (Getter->AssociatedAnimAssetPlayerNode) - { - MessageLog.Error(TEXT("Please replace @@ with Get Relevant Anim Time Remaining. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); - } - else - { - MessageLog.Error(TEXT("@@ is not asscociated with an asset player"), Getter); - } - } - break; - case ETransitionGetter::AnimationAsset_GetTimeFromEndFraction: - if ((AnimAsset != NULL) && (SourceTimePin != NULL)) - { - GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceAssetPlayerTimeFromEndFraction")); - GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); - } - else - { - if (Getter->AssociatedAnimAssetPlayerNode) - { - MessageLog.Error(TEXT("Please replace @@ with Get Relevant Anim Time Remaining Fraction. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); - } - else - { - MessageLog.Error(TEXT("@@ is not asscociated with an asset player"), Getter); - } - } - break; - - case ETransitionGetter::CurrentTransitionDuration: - { - check(TransitionNode); - if(UAnimStateNode* SourceStateNode = MessageLog.FindSourceObjectTypeChecked(TransitionNode->GetPreviousState())) - { - if(UObject* SourceTransitionNode = MessageLog.FindSourceObject(TransitionNode)) - { - if(FStateMachineDebugData* DebugData = NewAnimBlueprintClass->GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) - { - if(int32* pStateIndex = DebugData->NodeToStateIndex.Find(SourceStateNode)) - { - const int32 StateIndex = *pStateIndex; - - // This check should never fail as all animation nodes should be processed before getters are - UAnimGraphNode_Base* CompiledMachineInstanceNode = SourceNodeToProcessedNodeMap.FindChecked(DebugData->MachineInstanceNode.Get()); - const int32 MachinePropertyIndex = AllocatedAnimNodeIndices.FindChecked(CompiledMachineInstanceNode); - int32 TransitionPropertyIndex = INDEX_NONE; - - for(TMap, int32>::TIterator TransIt(DebugData->NodeToTransitionIndex); TransIt; ++TransIt) - { - UEdGraphNode* CurrTransNode = TransIt.Key().Get(); - - if(CurrTransNode == SourceTransitionNode) - { - TransitionPropertyIndex = TransIt.Value(); - break; - } - } - - if(TransitionPropertyIndex != INDEX_NONE) - { - GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceTransitionCrossfadeDuration")); - GetterHelper->FindPinChecked(TEXT("MachineIndex"))->DefaultValue = FString::FromInt(MachinePropertyIndex); - GetterHelper->FindPinChecked(TEXT("TransitionIndex"))->DefaultValue = FString::FromInt(TransitionPropertyIndex); - } - } - } - } - } - } - break; - - case ETransitionGetter::ArbitraryState_GetBlendWeight: - { - if (Getter->AssociatedStateNode) - { - if (UAnimStateNode* SourceStateNode = MessageLog.FindSourceObjectTypeChecked(Getter->AssociatedStateNode)) - { - if (FStateMachineDebugData* DebugData = NewAnimBlueprintClass->GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) - { - if (int32* pStateIndex = DebugData->NodeToStateIndex.Find(SourceStateNode)) - { - const int32 StateIndex = *pStateIndex; - //const int32 MachineIndex = DebugData->MachineIndex; - - // This check should never fail as all animation nodes should be processed before getters are - UAnimGraphNode_Base* CompiledMachineInstanceNode = SourceNodeToProcessedNodeMap.FindChecked(DebugData->MachineInstanceNode.Get()); - const int32 MachinePropertyIndex = AllocatedAnimNodeIndices.FindChecked(CompiledMachineInstanceNode); - - GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceStateWeight")); - GetterHelper->FindPinChecked(TEXT("MachineIndex"))->DefaultValue = FString::FromInt(MachinePropertyIndex); - GetterHelper->FindPinChecked(TEXT("StateIndex"))->DefaultValue = FString::FromInt(StateIndex); - } - } - } - } - - if (GetterHelper == NULL) - { - MessageLog.Error(TEXT("@@ is not associated with a valid state"), Getter); - } - } - break; - - case ETransitionGetter::CurrentState_ElapsedTime: - { - check(TransitionNode); - if (UAnimStateNode* SourceStateNode = MessageLog.FindSourceObjectTypeChecked(TransitionNode->GetPreviousState())) - { - if (FStateMachineDebugData* DebugData = NewAnimBlueprintClass->GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) - { - // This check should never fail as all animation nodes should be processed before getters are - UAnimGraphNode_Base* CompiledMachineInstanceNode = SourceNodeToProcessedNodeMap.FindChecked(DebugData->MachineInstanceNode.Get()); - const int32 MachinePropertyIndex = AllocatedAnimNodeIndices.FindChecked(CompiledMachineInstanceNode); - - GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceCurrentStateElapsedTime")); - GetterHelper->FindPinChecked(TEXT("MachineIndex"))->DefaultValue = FString::FromInt(MachinePropertyIndex); - } - } - if (GetterHelper == NULL) - { - MessageLog.Error(TEXT("@@ is not associated with a valid state"), Getter); - } - } - break; - - case ETransitionGetter::CurrentState_GetBlendWeight: - { - check(TransitionNode); - if (UAnimStateNode* SourceStateNode = MessageLog.FindSourceObjectTypeChecked(TransitionNode->GetPreviousState())) - { - { - if (FStateMachineDebugData* DebugData = NewAnimBlueprintClass->GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) - { - if (int32* pStateIndex = DebugData->NodeToStateIndex.Find(SourceStateNode)) - { - const int32 StateIndex = *pStateIndex; - //const int32 MachineIndex = DebugData->MachineIndex; - - // This check should never fail as all animation nodes should be processed before getters are - UAnimGraphNode_Base* CompiledMachineInstanceNode = SourceNodeToProcessedNodeMap.FindChecked(DebugData->MachineInstanceNode.Get()); - const int32 MachinePropertyIndex = AllocatedAnimNodeIndices.FindChecked(CompiledMachineInstanceNode); - - GetterHelper = SpawnCallAnimInstanceFunction(Getter, TEXT("GetInstanceStateWeight")); - GetterHelper->FindPinChecked(TEXT("MachineIndex"))->DefaultValue = FString::FromInt(MachinePropertyIndex); - GetterHelper->FindPinChecked(TEXT("StateIndex"))->DefaultValue = FString::FromInt(StateIndex); - } - } - } - } - if (GetterHelper == NULL) - { - MessageLog.Error(TEXT("@@ is not associated with a valid state"), Getter); - } - } - break; - - default: - MessageLog.Error(TEXT("Unrecognized getter type on @@"), Getter); - break; - } - - // Finish wiring up a call function if needed - if (GetterHelper != NULL) - { - check(GetterHelper->IsNodePure()); - - UEdGraphPin* NewReturnPin = GetterHelper->FindPinChecked(TEXT("ReturnValue")); - MessageLog.NotifyIntermediatePinCreation(NewReturnPin, OutputPin); - - NewReturnPin->CopyPersistentDataFromOldPin(*OutputPin); - } - - // Remove the getter from the equation - Getter->BreakAllNodeLinks(); -} - -int32 FAnimBlueprintCompilerContext::FindOrAddNotify(FAnimNotifyEvent& Notify) -{ - if ((Notify.NotifyName == NAME_None) && (Notify.Notify == NULL) && (Notify.NotifyStateClass == NULL)) - { - // Non event, don't add it - return INDEX_NONE; - } - - int32 NewIndex = INDEX_NONE; - for (int32 NotifyIdx = 0; NotifyIdx < NewAnimBlueprintClass->AnimNotifies.Num(); NotifyIdx++) - { - if( (NewAnimBlueprintClass->AnimNotifies[NotifyIdx].NotifyName == Notify.NotifyName) - && (NewAnimBlueprintClass->AnimNotifies[NotifyIdx].Notify == Notify.Notify) - && (NewAnimBlueprintClass->AnimNotifies[NotifyIdx].NotifyStateClass == Notify.NotifyStateClass) - ) - { - NewIndex = NotifyIdx; - break; - } - } - - if (NewIndex == INDEX_NONE) - { - NewIndex = NewAnimBlueprintClass->AnimNotifies.Add(Notify); - } - return NewIndex; -} - -void FAnimBlueprintCompilerContext::PostCompileDiagnostics() -{ - FKismetCompilerContext::PostCompileDiagnostics(); - -#if WITH_EDITORONLY_DATA // ANIMINST_PostCompileValidation - // See if AnimInstance implements a PostCompileValidation Class. - // If so, instantiate it, and let it perform Validation of our newly compiled AnimBlueprint. - if (const UAnimInstance* const DefaultAnimInstance = Cast(NewAnimBlueprintClass->GetDefaultObject())) - { - if (DefaultAnimInstance->PostCompileValidationClassName.IsValid()) - { - UClass* PostCompileValidationClass = LoadClass(nullptr, *DefaultAnimInstance->PostCompileValidationClassName.ToString()); - if (PostCompileValidationClass) - { - UAnimBlueprintPostCompileValidation* PostCompileValidation = NewObject(GetTransientPackage(), PostCompileValidationClass); - if (PostCompileValidation) - { - FAnimBPCompileValidationParams PCV_Params(DefaultAnimInstance, NewAnimBlueprintClass, MessageLog, AllocatedNodePropertiesToNodes); - PostCompileValidation->DoPostCompileValidation(PCV_Params); - } - } - } - } -#endif // WITH_EDITORONLY_DATA - - if (!bIsDerivedAnimBlueprint) - { - bool bUsingCopyPoseFromMesh = false; - - // Run thru all nodes and make sure they like the final results - for (auto NodeIt = AllocatedAnimNodeIndices.CreateConstIterator(); NodeIt; ++NodeIt) - { - if (UAnimGraphNode_Base* Node = NodeIt.Key()) - { - Node->ValidateAnimNodePostCompile(MessageLog, NewAnimBlueprintClass, NodeIt.Value()); - bUsingCopyPoseFromMesh = bUsingCopyPoseFromMesh || Node->UsingCopyPoseFromMesh(); - } - } - - // Update CDO - if (UAnimInstance* const DefaultAnimInstance = Cast(NewAnimBlueprintClass->GetDefaultObject())) - { - DefaultAnimInstance->bUsingCopyPoseFromMesh = bUsingCopyPoseFromMesh; - } - } -} - -void FAnimBlueprintCompilerContext::AutoWireAnimGetter(class UK2Node_AnimGetter* Getter, UAnimStateTransitionNode* InTransitionNode) -{ - UEdGraphPin* ReferencedNodeTimePin = nullptr; - int32 ReferencedNodeIndex = INDEX_NONE; - int32 SubNodeIndex = INDEX_NONE; - - UAnimGraphNode_Base* ProcessedNodeCheck = NULL; - - if(UAnimGraphNode_Base* SourceNode = Getter->SourceNode) - { - UAnimGraphNode_Base* ActualSourceNode = MessageLog.FindSourceObjectTypeChecked(SourceNode); - - if(UAnimGraphNode_Base* ProcessedSourceNode = SourceNodeToProcessedNodeMap.FindRef(ActualSourceNode)) - { - ProcessedNodeCheck = ProcessedSourceNode; - - ReferencedNodeIndex = GetAllocationIndexOfNode(ProcessedSourceNode); - - if(ProcessedSourceNode->DoesSupportTimeForTransitionGetter()) - { - UScriptStruct* TimePropertyInStructType = ProcessedSourceNode->GetTimePropertyStruct(); - const TCHAR* TimePropertyName = ProcessedSourceNode->GetTimePropertyName(); - - if(ReferencedNodeIndex != INDEX_NONE && TimePropertyName && TimePropertyInStructType) - { - FProperty* NodeProperty = AllocatedPropertiesByIndex.FindChecked(ReferencedNodeIndex); - - UK2Node_StructMemberGet* ReaderNode = SpawnIntermediateNode(Getter, ConsolidatedEventGraph); - ReaderNode->VariableReference.SetSelfMember(NodeProperty->GetFName()); - ReaderNode->StructType = TimePropertyInStructType; - ReaderNode->AllocatePinsForSingleMemberGet(TimePropertyName); - - ReferencedNodeTimePin = ReaderNode->FindPinChecked(TimePropertyName); - } - } - } - } - - if(Getter->SourceStateNode) - { - UObject* SourceObject = MessageLog.FindSourceObject(Getter->SourceStateNode); - if(UAnimStateNode* SourceStateNode = Cast(SourceObject)) - { - if(FStateMachineDebugData* DebugData = NewAnimBlueprintClass->GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) - { - if(int32* StateIndexPtr = DebugData->NodeToStateIndex.Find(SourceStateNode)) - { - SubNodeIndex = *StateIndexPtr; - } - } - } - else if(UAnimStateTransitionNode* TransitionNode = Cast(SourceObject)) - { - if(FStateMachineDebugData* DebugData = NewAnimBlueprintClass->GetAnimBlueprintDebugData().StateMachineDebugData.Find(TransitionNode->GetGraph())) - { - if(int32* TransitionIndexPtr = DebugData->NodeToTransitionIndex.Find(TransitionNode)) - { - SubNodeIndex = *TransitionIndexPtr; - } - } - } - } - - check(Getter->IsNodePure()); - - for(UEdGraphPin* Pin : Getter->Pins) - { - // Hook up autowired parameters / pins - if(Pin->PinName == TEXT("CurrentTime")) - { - Pin->MakeLinkTo(ReferencedNodeTimePin); - } - else if(Pin->PinName == TEXT("AssetPlayerIndex") || Pin->PinName == TEXT("MachineIndex")) - { - Pin->DefaultValue = FString::FromInt(ReferencedNodeIndex); - } - else if(Pin->PinName == TEXT("StateIndex") || Pin->PinName == TEXT("TransitionIndex")) - { - Pin->DefaultValue = FString::FromInt(SubNodeIndex); - } - } -} - -void FAnimBlueprintCompilerContext::FEvaluationHandlerRecord::PatchFunctionNameAndCopyRecordsInto(FExposedValueHandler& Handler) const -{ - Handler.CopyRecords.Empty(); - Handler.ValueHandlerNodeProperty = NodeVariableProperty; - - if (IsFastPath()) - { - for (const TPair& ServicedPropPair : ServicedProperties) - { - const FName& PropertyName = ServicedPropPair.Key; - const FAnimNodeSinglePropertyHandler& PropertyHandler = ServicedPropPair.Value; - - for (const FPropertyCopyRecord& PropertyCopyRecord : PropertyHandler.CopyRecords) - { - // get the correct property sizes for the type we are dealing with (array etc.) - int32 DestPropertySize = PropertyCopyRecord.DestProperty->GetSize(); - if (FArrayProperty* DestArrayProperty = CastField(PropertyCopyRecord.DestProperty)) - { - DestPropertySize = DestArrayProperty->Inner->GetSize(); - } - - FExposedValueCopyRecord CopyRecord; - CopyRecord.DestProperty = PropertyCopyRecord.DestProperty; - CopyRecord.DestArrayIndex = PropertyCopyRecord.DestArrayIndex == INDEX_NONE ? 0 : PropertyCopyRecord.DestArrayIndex; - CopyRecord.SourcePropertyName = PropertyCopyRecord.SourcePropertyName; - CopyRecord.SourceSubPropertyName = PropertyCopyRecord.SourceSubStructPropertyName; - CopyRecord.SourceArrayIndex = 0; - CopyRecord.Size = DestPropertySize; - CopyRecord.PostCopyOperation = PropertyCopyRecord.Operation; - CopyRecord.bInstanceIsTarget = PropertyHandler.bInstanceIsTarget; - Handler.CopyRecords.Add(CopyRecord); - } - } - } - else - { - // not all of our pins use copy records so we will need to call our exposed value handler - Handler.BoundFunction = HandlerFunctionName; - } -} - -static UEdGraphPin* FindFirstInputPin(UEdGraphNode* InNode) -{ - const UAnimationGraphSchema* Schema = GetDefault(); - - for(UEdGraphPin* Pin : InNode->Pins) - { - if(Pin && Pin->Direction == EGPD_Input && !Schema->IsExecPin(*Pin) && !Schema->IsSelfPin(*Pin)) - { - return Pin; - } - } - - return nullptr; -} - -static UEdGraphNode* FollowKnots(UEdGraphPin* FromPin, UEdGraphPin*& ToPin) -{ - if (FromPin->LinkedTo.Num() == 0) - { - return nullptr; - } - - UEdGraphPin* LinkedPin = FromPin->LinkedTo[0]; - ToPin = LinkedPin; - if(LinkedPin) - { - UEdGraphNode* LinkedNode = LinkedPin->GetOwningNode(); - UK2Node_Knot* KnotNode = Cast(LinkedNode); - while(KnotNode) - { - if(UEdGraphPin* InputPin = FindFirstInputPin(KnotNode)) - { - if (InputPin->LinkedTo.Num() > 0 && InputPin->LinkedTo[0]) - { - ToPin = InputPin->LinkedTo[0]; - LinkedNode = InputPin->LinkedTo[0]->GetOwningNode(); - KnotNode = Cast(LinkedNode); - } - else - { - KnotNode = nullptr; - } - } - } - return LinkedNode; - } - - return nullptr; -} - -void FAnimBlueprintCompilerContext::FEvaluationHandlerRecord::RegisterPin(UEdGraphPin* DestPin, FProperty* AssociatedProperty, int32 AssociatedPropertyArrayIndex) -{ - FAnimNodeSinglePropertyHandler& Handler = ServicedProperties.FindOrAdd(AssociatedProperty->GetFName()); - Handler.CopyRecords.Emplace(DestPin, AssociatedProperty, AssociatedPropertyArrayIndex); -} - -void FAnimBlueprintCompilerContext::FEvaluationHandlerRecord::BuildFastPathCopyRecords() -{ - if (GetDefault()->bOptimizeAnimBlueprintMemberVariableAccess) - { - for (TPair& ServicedPropPair : ServicedProperties) - { - for (FPropertyCopyRecord& CopyRecord : ServicedPropPair.Value.CopyRecords) - { - typedef bool (FAnimBlueprintCompilerContext::FEvaluationHandlerRecord::*GraphCheckerFunc)(FPropertyCopyRecord&, UEdGraphPin*); - - GraphCheckerFunc GraphCheckerFuncs[] = - { - &FAnimBlueprintCompilerContext::FEvaluationHandlerRecord::CheckForVariableGet, - &FAnimBlueprintCompilerContext::FEvaluationHandlerRecord::CheckForLogicalNot, - &FAnimBlueprintCompilerContext::FEvaluationHandlerRecord::CheckForStructMemberAccess, - }; - - for (GraphCheckerFunc& CheckFunc : GraphCheckerFuncs) - { - if ((this->*CheckFunc)(CopyRecord, CopyRecord.DestPin)) - { - break; - } - } - - CheckForMemberOnlyAccess(CopyRecord, CopyRecord.DestPin); - } - } - } -} - -static FName RecoverSplitStructPinName(UEdGraphPin* OutputPin) -{ - check(OutputPin->ParentPin); - - FString PinName = OutputPin->PinName.ToString(); - const FString ParentPinName = OutputPin->ParentPin->PinName.ToString() + TEXT("_"); - - PinName.ReplaceInline(*ParentPinName, TEXT("")); - - return *PinName; -} - -bool FAnimBlueprintCompilerContext::FEvaluationHandlerRecord::CheckForVariableGet(FPropertyCopyRecord& CopyRecord, UEdGraphPin* DestPin) -{ - if(DestPin) - { - UEdGraphPin* SourcePin = nullptr; - if(UK2Node_VariableGet* VariableGetNode = Cast(FollowKnots(DestPin, SourcePin))) - { - if(VariableGetNode && VariableGetNode->IsNodePure() && VariableGetNode->VariableReference.IsSelfContext()) - { - if(SourcePin) - { - // variable get could be a 'split' struct - if(SourcePin->ParentPin != nullptr) - { - CopyRecord.SourcePropertyName = VariableGetNode->VariableReference.GetMemberName(); - CopyRecord.SourceSubStructPropertyName = RecoverSplitStructPinName(SourcePin); - } - else - { - CopyRecord.SourcePropertyName = VariableGetNode->VariableReference.GetMemberName(); - } - return true; - } - } - } - } - - return false; -} - -bool FAnimBlueprintCompilerContext::FEvaluationHandlerRecord::CheckForLogicalNot(FPropertyCopyRecord& CopyRecord, UEdGraphPin* DestPin) -{ - if(DestPin) - { - UEdGraphPin* SourcePin = nullptr; - UK2Node_CallFunction* CallFunctionNode = Cast(FollowKnots(DestPin, SourcePin)); - if(CallFunctionNode && CallFunctionNode->FunctionReference.GetMemberName() == FName(TEXT("Not_PreBool"))) - { - // find and follow input pin - if(UEdGraphPin* InputPin = FindFirstInputPin(CallFunctionNode)) - { - check(InputPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean); - if(CheckForVariableGet(CopyRecord, InputPin) || CheckForStructMemberAccess(CopyRecord, InputPin)) - { - check(CopyRecord.SourcePropertyName != NAME_None); // this should have been filled in by CheckForVariableGet() or CheckForStructMemberAccess() above - CopyRecord.Operation = EPostCopyOperation::LogicalNegateBool; - return true; - } - } - } - } - - return false; -} - -/** The functions that we can safely native-break */ -static const FName NativeBreakFunctionNameWhitelist[] = -{ - FName(TEXT("BreakVector")), - FName(TEXT("BreakVector2D")), - FName(TEXT("BreakRotator")), -}; - -/** Check whether a native break function can be safely used in the fast-path copy system (ie. source and dest data will be the same) */ -static bool IsWhitelistedNativeBreak(const FName& InFunctionName) -{ - for(const FName& FunctionName : NativeBreakFunctionNameWhitelist) - { - if(InFunctionName == FunctionName) - { - return true; - } - } - - return false; -} - -bool FAnimBlueprintCompilerContext::FEvaluationHandlerRecord::CheckForStructMemberAccess(FPropertyCopyRecord& CopyRecord, UEdGraphPin* DestPin) -{ - if(DestPin) - { - UEdGraphPin* SourcePin = nullptr; - if(UK2Node_BreakStruct* BreakStructNode = Cast(FollowKnots(DestPin, SourcePin))) - { - if(UEdGraphPin* InputPin = FindFirstInputPin(BreakStructNode)) - { - if(CheckForVariableGet(CopyRecord, InputPin)) - { - check(CopyRecord.SourcePropertyName != NAME_None); // this should have been filled in by CheckForVariableGet() above - CopyRecord.SourceSubStructPropertyName = SourcePin->PinName; - return true; - } - } - } - // could be a native break - else if(UK2Node_CallFunction* NativeBreakNode = Cast(FollowKnots(DestPin, SourcePin))) - { - UFunction* Function = NativeBreakNode->FunctionReference.ResolveMember(UKismetMathLibrary::StaticClass()); - if(Function && Function->HasMetaData(TEXT("NativeBreakFunc")) && IsWhitelistedNativeBreak(Function->GetFName())) - { - if(UEdGraphPin* InputPin = FindFirstInputPin(NativeBreakNode)) - { - if(CheckForVariableGet(CopyRecord, InputPin)) - { - check(CopyRecord.SourcePropertyName != NAME_None); // this should have been filled in by CheckForVariableGet() above - CopyRecord.SourceSubStructPropertyName = SourcePin->PinName; - return true; - } - } - } - } - } - - return false; -} - -bool FAnimBlueprintCompilerContext::FEvaluationHandlerRecord::CheckForMemberOnlyAccess(FPropertyCopyRecord& CopyRecord, UEdGraphPin* DestPin) -{ - const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault(); - - if(DestPin) - { - // traverse pins to leaf nodes and check for member access/pure only - TArray PinStack; - PinStack.Add(DestPin); - while(PinStack.Num() > 0) - { - UEdGraphPin* CurrentPin = PinStack.Pop(false); - for(auto& LinkedPin : CurrentPin->LinkedTo) - { - UEdGraphNode* LinkedNode = LinkedPin->GetOwningNode(); - if(LinkedNode) - { - bool bLeafNode = true; - for(auto& Pin : LinkedNode->Pins) - { - if(Pin != LinkedPin && Pin->Direction == EGPD_Input && !AnimGraphDefaultSchema->IsPosePin(Pin->PinType)) - { - bLeafNode = false; - PinStack.Add(Pin); - } - } - - if(bLeafNode) - { - if(UK2Node_VariableGet* LinkedVariableGetNode = Cast(LinkedNode)) - { - if(!LinkedVariableGetNode->IsNodePure() || !LinkedVariableGetNode->VariableReference.IsSelfContext()) - { - // only local variable access is allowed for leaf nodes - CopyRecord.InvalidateFastPath(); - } - } - else if(UK2Node_CallFunction* CallFunctionNode = Cast(LinkedNode)) - { - if(!CallFunctionNode->IsNodePure()) - { - // only allow pure function calls - CopyRecord.InvalidateFastPath(); - } - } - else if(!LinkedNode->IsA()) - { - CopyRecord.InvalidateFastPath(); - } - } - } - } - } - } - - return CopyRecord.IsFastPath(); -} - -void FAnimBlueprintCompilerContext::FEvaluationHandlerRecord::ValidateFastPath(UClass* InCompiledClass) -{ - for (TPair& ServicedPropPair : ServicedProperties) - { - for (FPropertyCopyRecord& CopyRecord : ServicedPropPair.Value.CopyRecords) - { - CopyRecord.ValidateFastPath(InCompiledClass); - } - } -} - -void FAnimBlueprintCompilerContext::FPropertyCopyRecord::ValidateFastPath(UClass* InCompiledClass) -{ - if (IsFastPath()) - { - int32 DestPropertySize = DestProperty->GetSize(); - if (FArrayProperty* DestArrayProperty = CastField(DestProperty)) - { - DestPropertySize = DestArrayProperty->Inner->GetSize(); - } - - FProperty* SourceProperty = InCompiledClass->FindPropertyByName(SourcePropertyName); - if (SourceProperty) - { - if (FArrayProperty* SourceArrayProperty = CastField(SourceProperty)) - { - // We dont support arrays as source properties - InvalidateFastPath(); - return; - } - - int32 SourcePropertySize = SourceProperty->GetSize(); - if (SourceSubStructPropertyName != NAME_None) - { - FProperty* SourceSubStructProperty = CastFieldChecked(SourceProperty)->Struct->FindPropertyByName(SourceSubStructPropertyName); - if (SourceSubStructProperty) - { - SourcePropertySize = SourceSubStructProperty->GetSize(); - } - else - { - InvalidateFastPath(); - return; - } - } - - if (SourcePropertySize != DestPropertySize) - { - InvalidateFastPath(); - return; - } - - // If the property is not one of the following types or categories, then we ignore the fast - // path. This matches the supported copyable types in FExposedValueHandler::Initialize - FProperty* DestPropertyOrInner = DestProperty; - if (FArrayProperty* DestArrayProperty = CastField(DestProperty)) - { - DestPropertyOrInner = DestArrayProperty->Inner; - } - - if (!(CastField(DestPropertyOrInner) || - CastField(DestPropertyOrInner) || - CastField(DestPropertyOrInner) || - CastField(DestPropertyOrInner) || - (DestPropertyOrInner->PropertyFlags & CPF_IsPlainOldData) != 0)) - { - InvalidateFastPath(); - return; - } - } - else - { - InvalidateFastPath(); - return; - } - } -} - -void FAnimBlueprintCompilerContext::CreateAnimGraphStubFunctions() -{ - TArray NewGraphs; - - auto CreateStubForGraph = [this, &NewGraphs](UEdGraph* InGraph) - { - if(InGraph->Schema->IsChildOf(UAnimationGraphSchema::StaticClass())) - { - // Check to see if we are implementing an interface, and if so, use the signature from that graph instead - // as we may not have yet been conformed to it (it happens later in compilation) - UEdGraph* GraphToUseforSignature = InGraph; - for(const FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) - { - UClass* InterfaceClass = InterfaceDesc.Interface; - if(InterfaceClass) - { - if(UAnimBlueprint* InterfaceAnimBlueprint = Cast(InterfaceClass->ClassGeneratedBy)) - { - TArray AllGraphs; - InterfaceAnimBlueprint->GetAllGraphs(AllGraphs); - UEdGraph** FoundSourceGraph = AllGraphs.FindByPredicate([InGraph](UEdGraph* InGraphToCheck){ return InGraphToCheck->GetFName() == InGraph->GetFName(); }); - if(FoundSourceGraph) - { - GraphToUseforSignature = *FoundSourceGraph; - break; - } - } - } - } - - // Find the root and linked input pose nodes - TArray Roots; - GraphToUseforSignature->GetNodesOfClass(Roots); - - TArray LinkedInputPoseNodes; - GraphToUseforSignature->GetNodesOfClass(LinkedInputPoseNodes); - - if(Roots.Num() > 0) - { - UAnimGraphNode_Root* RootNode = Roots[0]; - - // Make sure there was only one root node - for (int32 RootIndex = 1; RootIndex < Roots.Num(); ++RootIndex) - { - MessageLog.Error( - *LOCTEXT("ExpectedOneRoot_Error", "Expected only one root node in graph @@, but found both @@ and @@").ToString(), - InGraph, - RootNode, - Roots[RootIndex] - ); - } - - // Verify no duplicate inputs - for(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode0 : LinkedInputPoseNodes) - { - for(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode1 : LinkedInputPoseNodes) - { - if(LinkedInputPoseNode0 != LinkedInputPoseNode1) - { - if(LinkedInputPoseNode0->Node.Name == LinkedInputPoseNode1->Node.Name) - { - MessageLog.Error( - *LOCTEXT("DuplicateInputNode_Error", "Found duplicate input node @@ in graph @@").ToString(), - LinkedInputPoseNode1, - InGraph - ); - } - } - } - } - - // Create a simple generated graph for our anim 'function'. Decorate it to avoid naming conflicts with the original graph. - FName NewGraphName(*(GraphToUseforSignature->GetName() + ANIM_FUNC_DECORATOR)); - - UEdGraph* StubGraph = NewObject(Blueprint, NewGraphName); - NewGraphs.Add(StubGraph); - StubGraph->Schema = UEdGraphSchema_K2::StaticClass(); - StubGraph->SetFlags(RF_Transient); - - // Add an entry node - UK2Node_FunctionEntry* EntryNode = SpawnIntermediateNode(RootNode, StubGraph); - EntryNode->NodePosX = -200; - EntryNode->CustomGeneratedFunctionName = GraphToUseforSignature->GetFName(); // Note that the function generated from this temporary graph is undecorated - EntryNode->MetaData.Category = RootNode->Node.Group == NAME_None ? FText::GetEmpty() : FText::FromName(RootNode->Node.Group); - - // Add linked input poses as parameters - for(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode : LinkedInputPoseNodes) - { - // Add user defined pins for each linked input pose - TSharedPtr PosePinInfo = MakeShared(); - PosePinInfo->PinType = UAnimationGraphSchema::MakeLocalSpacePosePin(); - PosePinInfo->PinName = LinkedInputPoseNode->Node.Name; - PosePinInfo->DesiredPinDirection = EGPD_Output; - EntryNode->UserDefinedPins.Add(PosePinInfo); - - // Add user defined pins for each linked input pose parameter - for(UEdGraphPin* LinkedInputPoseNodePin : LinkedInputPoseNode->Pins) - { - if(!LinkedInputPoseNodePin->bOrphanedPin && LinkedInputPoseNodePin->Direction == EGPD_Output && !UAnimationGraphSchema::IsPosePin(LinkedInputPoseNodePin->PinType)) - { - TSharedPtr ParameterPinInfo = MakeShared(); - ParameterPinInfo->PinType = LinkedInputPoseNodePin->PinType; - ParameterPinInfo->PinName = LinkedInputPoseNodePin->PinName; - ParameterPinInfo->DesiredPinDirection = EGPD_Output; - EntryNode->UserDefinedPins.Add(ParameterPinInfo); - } - } - } - EntryNode->AllocateDefaultPins(); - - UEdGraphPin* EntryExecPin = EntryNode->FindPinChecked(UEdGraphSchema_K2::PN_Then, EGPD_Output); - - UK2Node_FunctionResult* ResultNode = SpawnIntermediateNode(RootNode, StubGraph); - ResultNode->NodePosX = 200; - - // Add root as the 'return value' - TSharedPtr PinInfo = MakeShared(); - PinInfo->PinType = UAnimationGraphSchema::MakeLocalSpacePosePin(); - PinInfo->PinName = GraphToUseforSignature->GetFName(); - PinInfo->DesiredPinDirection = EGPD_Input; - ResultNode->UserDefinedPins.Add(PinInfo); - - ResultNode->AllocateDefaultPins(); - - UEdGraphPin* ResultExecPin = ResultNode->FindPinChecked(UEdGraphSchema_K2::PN_Execute, EGPD_Input); - - // Link up entry to exit - EntryExecPin->MakeLinkTo(ResultExecPin); - } - else - { - MessageLog.Error(*LOCTEXT("NoRootNodeFound_Error", "Could not find a root node for the graph @@").ToString(), InGraph); - } - } - }; - - for(UEdGraph* Graph : Blueprint->FunctionGraphs) - { - CreateStubForGraph(Graph); - } - - for(FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) - { - for(UEdGraph* Graph : InterfaceDesc.Graphs) - { - CreateStubForGraph(Graph); - } - } - - Blueprint->FunctionGraphs.Append(NewGraphs); - GeneratedStubGraphs.Append(NewGraphs); -} - -void FAnimBlueprintCompilerContext::DestroyAnimGraphStubFunctions() -{ - Blueprint->FunctionGraphs.RemoveAll([this](UEdGraph* InGraph) - { - return GeneratedStubGraphs.Contains(InGraph); - }); - - GeneratedStubGraphs.Empty(); -} - -void FAnimBlueprintCompilerContext::PrecompileFunction(FKismetFunctionContext& Context, EInternalCompilerFlags InternalFlags) -{ - Super::PrecompileFunction(Context, InternalFlags); - - if(Context.Function) - { - auto CompareEntryPointName = - [Function = Context.Function](UEdGraph* InGraph) - { - if(InGraph) - { - TArray EntryPoints; - InGraph->GetNodesOfClass(EntryPoints); - if(EntryPoints.Num() == 1 && EntryPoints[0]) - { - return EntryPoints[0]->CustomGeneratedFunctionName == Function->GetFName(); - } - } - return true; - }; - - if(GeneratedStubGraphs.ContainsByPredicate(CompareEntryPointName)) - { - Context.Function->SetMetaData(FBlueprintMetadata::MD_BlueprintInternalUseOnly, TEXT("true")); - Context.Function->SetMetaData(FBlueprintMetadata::MD_AnimBlueprintFunction, TEXT("true")); - } - } -} - -void FAnimBlueprintCompilerContext::SetCalculatedMetaDataAndFlags(UFunction* Function, UK2Node_FunctionEntry* EntryNode, const UEdGraphSchema_K2* K2Schema) -{ - Super::SetCalculatedMetaDataAndFlags(Function, EntryNode, K2Schema); - - if(Function) - { - auto CompareEntryPointName = - [Function](UEdGraph* InGraph) - { - if(InGraph) - { - TArray EntryPoints; - InGraph->GetNodesOfClass(EntryPoints); - if(EntryPoints.Num() == 1 && EntryPoints[0]) - { - return EntryPoints[0]->CustomGeneratedFunctionName == Function->GetFName(); - } - } - return true; - }; - - // Match by name to generated graph's entry points - if(GeneratedStubGraphs.ContainsByPredicate(CompareEntryPointName)) - { - Function->SetMetaData(FBlueprintMetadata::MD_BlueprintInternalUseOnly, TEXT("true")); - Function->SetMetaData(FBlueprintMetadata::MD_AnimBlueprintFunction, TEXT("true")); - } - } -} - -////////////////////////////////////////////////////////////////////////// - -#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Source/Editor/KismetCompiler/Private/AnimBlueprintCompiler.h b/Engine/Source/Editor/KismetCompiler/Private/AnimBlueprintCompiler.h deleted file mode 100644 index 9045f26ffc04..000000000000 --- a/Engine/Source/Editor/KismetCompiler/Private/AnimBlueprintCompiler.h +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "KismetCompiler.h" -#include "Animation/AnimNodeBase.h" -#include "AnimGraphNode_Base.h" -#include "KismetCompilerModule.h" - -class UAnimationGraphSchema; -class UAnimGraphNode_SaveCachedPose; -class UAnimGraphNode_StateMachineBase; -class UAnimGraphNode_StateResult; -class UAnimGraphNode_CustomProperty; - -class UAnimGraphNode_UseCachedPose; -class UAnimStateTransitionNode; -class UK2Node_CallFunction; - -// -// Forward declarations. -// -class UAnimGraphNode_SaveCachedPose; -class UAnimGraphNode_UseCachedPose; -class UAnimGraphNode_LinkedInputPose; -class UAnimGraphNode_LinkedAnimGraphBase; -class UAnimGraphNode_LinkedAnimGraph; -class UAnimGraphNode_Root; - -class FStructProperty; -class UBlueprintGeneratedClass; -struct FPoseLinkMappingRecord; - -////////////////////////////////////////////////////////////////////////// -// FAnimBlueprintCompilerContext -class FAnimBlueprintCompilerContext : public FKismetCompilerContext -{ -protected: - typedef FKismetCompilerContext Super; -public: - FAnimBlueprintCompilerContext(UAnimBlueprint* SourceSketch, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompileOptions); - virtual ~FAnimBlueprintCompilerContext(); - - virtual void PostCompile() override; - -protected: - // Implementation of FKismetCompilerContext interface - virtual void CreateClassVariablesFromBlueprint() override; - virtual UEdGraphSchema_K2* CreateSchema() override; - virtual void MergeUbergraphPagesIn(UEdGraph* Ubergraph) override; - virtual void ProcessOneFunctionGraph(UEdGraph* SourceGraph, bool bInternalFunction = false) override; - virtual void CreateFunctionList() override; - virtual void SpawnNewClass(const FString& NewClassName) override; - virtual void OnNewClassSet(UBlueprintGeneratedClass* ClassToUse) override; - virtual void OnPostCDOCompiled() override; - virtual void CopyTermDefaultsToDefaultObject(UObject* DefaultObject) override; - virtual void PostCompileDiagnostics() override; - virtual void EnsureProperGeneratedClass(UClass*& TargetClass) override; - virtual void CleanAndSanitizeClass(UBlueprintGeneratedClass* ClassToClean, UObject*& InOldCDO) override; - virtual void FinishCompilingClass(UClass* Class) override; - virtual void PrecompileFunction(FKismetFunctionContext& Context, EInternalCompilerFlags InternalFlags) override; - virtual void SetCalculatedMetaDataAndFlags(UFunction* Function, UK2Node_FunctionEntry* EntryNode, const UEdGraphSchema_K2* Schema ) override; - // End of FKismetCompilerContext interface - -protected: - typedef TArray UEdGraphPinArray; - -protected: - /** Record of a single copy operation */ - struct FPropertyCopyRecord - { - FPropertyCopyRecord(UEdGraphPin* InDestPin, FProperty* InDestProperty, int32 InDestArrayIndex) - : DestPin(InDestPin) - , DestProperty(InDestProperty) - , DestArrayIndex(InDestArrayIndex) - , SourcePropertyName(NAME_None) - , SourceSubStructPropertyName(NAME_None) - , Operation(EPostCopyOperation::None) - {} - - bool IsFastPath() const - { - return DestProperty != nullptr && SourcePropertyName != NAME_None; - } - - void InvalidateFastPath() - { - SourcePropertyName = NAME_None; - SourceSubStructPropertyName = NAME_None; - } - - void ValidateFastPath(UClass* InCompiledClass); - - /** The destination pin we are copying to */ - UEdGraphPin* DestPin; - - /** The destination property we are copying to (on an animation node) */ - FProperty* DestProperty; - - /** The array index we use if the destination property is an array */ - int32 DestArrayIndex; - - /** The source property we are copying from (on an anim instance) */ - FName SourcePropertyName; - - /** The source sub-struct property we are copying from (if the source property is a struct property) */ - FName SourceSubStructPropertyName; - - /** Any operation we want to perform post-copy on the destination data */ - EPostCopyOperation Operation; - }; - - // Wireup record for a single anim node property (which might be an array) - struct FAnimNodeSinglePropertyHandler - { - /** Copy records */ - TArray CopyRecords; - - // If the anim instance is the container target instead of the node. - bool bInstanceIsTarget; - - FAnimNodeSinglePropertyHandler() - : bInstanceIsTarget(false) - { - } - }; - - // Record for a property that was exposed as a pin, but wasn't wired up (just a literal) - struct FEffectiveConstantRecord - { - public: - // The node variable that the handler is in - class FStructProperty* NodeVariableProperty; - - // The property within the struct to set - class FProperty* ConstantProperty; - - // The array index if ConstantProperty is an array property, or INDEX_NONE otherwise - int32 ArrayIndex; - - // The pin to pull the DefaultValue/DefaultObject from - UEdGraphPin* LiteralSourcePin; - - FEffectiveConstantRecord() - : NodeVariableProperty(NULL) - , ConstantProperty(NULL) - , ArrayIndex(INDEX_NONE) - , LiteralSourcePin(NULL) - { - } - - FEffectiveConstantRecord(FStructProperty* ContainingNodeProperty, UEdGraphPin* SourcePin, FProperty* SourcePinProperty, int32 SourceArrayIndex) - : NodeVariableProperty(ContainingNodeProperty) - , ConstantProperty(SourcePinProperty) - , ArrayIndex(SourceArrayIndex) - , LiteralSourcePin(SourcePin) - { - } - - bool Apply(UObject* Object); - }; - - /** BP execution handler for Anim node - possibly */ - struct FEvaluationHandlerRecord - { - public: - - // The node variable that the handler is in - FStructProperty* NodeVariableProperty; - - // The specific evaluation handler inside the specified node - int32 EvaluationHandlerIdx; - - // Whether or not our serviced properties are actually on the anim node - bool bServicesNodeProperties; - - // Whether or not our serviced properties are actually on the instance instead of the node - bool bServicesInstanceProperties; - - // Set of properties serviced by this handler (Map from property name to the record for that property) - TMap ServicedProperties; - - // The resulting function name - FName HandlerFunctionName; - - public: - - FEvaluationHandlerRecord() - : NodeVariableProperty(nullptr) - , EvaluationHandlerIdx(INDEX_NONE) - , bServicesNodeProperties(false) - , bServicesInstanceProperties(false) - , HandlerFunctionName(NAME_None) - {} - - bool IsFastPath() const - { - for(TMap::TConstIterator It(ServicedProperties); It; ++It) - { - const FAnimNodeSinglePropertyHandler& AnimNodeSinglePropertyHandler = It.Value(); - for (const FPropertyCopyRecord& CopyRecord : AnimNodeSinglePropertyHandler.CopyRecords) - { - if (!CopyRecord.IsFastPath()) - { - return false; - } - } - } - - return true; - } - - bool IsValid() const - { - return NodeVariableProperty != nullptr; - } - - void PatchFunctionNameAndCopyRecordsInto(FExposedValueHandler& Handler) const; - - void RegisterPin(UEdGraphPin* DestPin, FProperty* AssociatedProperty, int32 AssociatedPropertyArrayIndex); - - FStructProperty* GetHandlerNodeProperty() const { return NodeVariableProperty; } - - void BuildFastPathCopyRecords(); - - void ValidateFastPath(UClass* InCompiledClass); - - private: - - bool CheckForVariableGet(FPropertyCopyRecord& CopyRecord, UEdGraphPin* DestPin); - - bool CheckForLogicalNot(FPropertyCopyRecord& CopyRecord, UEdGraphPin* DestPin); - - bool CheckForStructMemberAccess(FPropertyCopyRecord& CopyRecord, UEdGraphPin* DestPin); - - bool CheckForMemberOnlyAccess(FPropertyCopyRecord& CopyRecord, UEdGraphPin* DestPin); - }; - - // State machines may get processed before their inner graphs, so their node index needs to be patched up later - // This structure records pending fixups. - struct FStateRootNodeIndexFixup - { - public: - int32 MachineIndex; - int32 StateIndex; - UAnimGraphNode_StateResult* ResultNode; - - public: - FStateRootNodeIndexFixup(int32 InMachineIndex, int32 InStateIndex, UAnimGraphNode_StateResult* InResultNode) - : MachineIndex(InMachineIndex) - , StateIndex(InStateIndex) - , ResultNode(InResultNode) - { - } - }; - -protected: - UAnimBlueprintGeneratedClass* NewAnimBlueprintClass; - UAnimBlueprint* AnimBlueprint; - - UAnimationGraphSchema* AnimSchema; - - // Map of allocated v3 nodes that are members of the class - TMap AllocatedAnimNodes; - TMap AllocatedNodePropertiesToNodes; - TMap AllocatedPropertiesByIndex; - - // Map of true source objects (user edited ones) to the cloned ones that are actually compiled - TMap SourceNodeToProcessedNodeMap; - - // Index of the nodes (must match up with the runtime discovery process of nodes, which runs thru the property chain) - int32 AllocateNodeIndexCounter; - TMap AllocatedAnimNodeIndices; - - // Map from pose link LinkID address - //@TODO: Bad structure for a list of these - TArray ValidPoseLinkList; - - // List of successfully created evaluation handlers - TArray ValidEvaluationHandlerList; - - // List of animation node literals (values exposed as pins but never wired up) that need to be pushed into the CDO - TArray ValidAnimNodePinConstants; - - // Map of cache name to encountered save cached pose nodes - TMap SaveCachedPoseNodes; - - // List of getter node's we've found so the auto-wire can be deferred till after state machine compilation - TArray FoundGetterNodes; - - // Set of used handler function names - TSet HandlerFunctionNames; - - // Stub graphs we generated for animation graph functions - TArray GeneratedStubGraphs; - - // True if any parent class is also generated from an animation blueprint - bool bIsDerivedAnimBlueprint; -private: - int32 FindOrAddNotify(FAnimNotifyEvent& Notify); - - UK2Node_CallFunction* SpawnCallAnimInstanceFunction(UEdGraphNode* SourceNode, FName FunctionName); - - // Creates an evaluation handler for an FExposedValue property in an animation node - void CreateEvaluationHandler(UAnimGraphNode_Base* VisualAnimNode, FEvaluationHandlerRecord& Record); - - // Prunes any nodes that aren't reachable via a pose link - void PruneIsolatedAnimationNodes(const TArray& RootSet, TArray& GraphNodes); - - // Compiles one animation node - void ProcessAnimationNode(UAnimGraphNode_Base* VisualAnimNode); - - // Compiles one state machine - void ProcessStateMachine(UAnimGraphNode_StateMachineBase* StateMachineInstance); - - // Compiles one use cached pose instance - void ProcessUseCachedPose(UAnimGraphNode_UseCachedPose* UseCachedPose); - - // Compiles one custom property node - void ProcessCustomPropertyNode(UAnimGraphNode_CustomProperty* CustomPropNode); - - // Compiles one linked anim graph node - void ProcessLinkedAnimGraph(UAnimGraphNode_LinkedAnimGraphBase* InLinkedAnimGraph, bool bCheckForCycles); - - // Compiles one linked input pose - void ProcessLinkedInputPose(UAnimGraphNode_LinkedInputPose* InLinkedInputPose); - - // Compiles one root node - void ProcessRoot(UAnimGraphNode_Root* Root); - - // Compiles one state result node - void ProcessStateResult(UAnimGraphNode_StateResult* StateResult); - - // Traverses linked anim graph links looking for slot names and state machine names, returning their count in a name map - typedef TMap NameToCountMap; - void GetDuplicatedSlotAndStateNames(UAnimGraphNode_LinkedAnimGraphBase* InLinkedAnimGraph, NameToCountMap& OutStateMachineNameToCountMap, NameToCountMap& OutSlotNameToCountMap); - - // Compiles an entire animation graph - void ProcessAllAnimationNodes(); - - // Convert transition getters into a function call/etc... - void ProcessTransitionGetter(class UK2Node_TransitionRuleGetter* Getter, UAnimStateTransitionNode* TransitionNode); - - // - void ProcessAnimationNodesGivenRoot(TArray& AnimNodeList, const TArray& RootSet); - - // Builds the update order list for saved pose nodes in this blueprint - void BuildCachedPoseNodeUpdateOrder(); - - // Traverses a graph to collect save pose nodes starting at InRootNode, then processes each node - void CachePoseNodeOrdering_StartNewTraversal(UAnimGraphNode_Base* InRootNode, TArray &OrderedSavePoseNodes, TArray VisitedRootNodes); - - // Traverses a graph to collect save pose nodes starting at InAnimGraphNode, does NOT process saved pose nodes afterwards - void CachePoseNodeOrdering_TraverseInternal(UAnimGraphNode_Base* InAnimGraphNode, TArray &OrderedSavePoseNodes); - - // Gets all anim graph nodes that are piped into the provided node (traverses input pins) - void GetLinkedAnimNodes(UAnimGraphNode_Base* InGraphNode, TArray& LinkedAnimNodes); - void GetLinkedAnimNodes_TraversePin(UEdGraphPin* InPin, TArray& LinkedAnimNodes); - void GetLinkedAnimNodes_ProcessAnimNode(UAnimGraphNode_Base* AnimNode, TArray& LinkedAnimNodes); - - // Automatically fill in parameters for the specified Getter node - void AutoWireAnimGetter(class UK2Node_AnimGetter* Getter, UAnimStateTransitionNode* InTransitionNode); - - // This function does the following steps: - // Clones the nodes in the specified source graph - // Merges them into the ConsolidatedEventGraph - // Processes any animation nodes - // Returns the index of the processed cloned version of SourceRootNode - // If supplied, will also return an array of all cloned nodes - int32 ExpandGraphAndProcessNodes(UEdGraph* SourceGraph, UAnimGraphNode_Base* SourceRootNode, UAnimStateTransitionNode* TransitionNode = NULL, TArray* ClonedNodes = NULL); - - // Returns the allocation index of the specified node, processing it if it was pending - int32 GetAllocationIndexOfNode(UAnimGraphNode_Base* VisualAnimNode); - - // Create transient stub functions for each anim graph we are compiling - void CreateAnimGraphStubFunctions(); - - // Clean up transient stub functions - void DestroyAnimGraphStubFunctions(); - - // Expands split pins for a graph - void ExpandSplitPins(UEdGraph* InGraph); -}; - diff --git a/Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp b/Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp index 300091d6a700..45c5c22da100 100644 --- a/Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp +++ b/Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp @@ -54,7 +54,6 @@ #include "Engine/InheritableComponentHandler.h" #include "BlueprintCompilerCppBackendInterface.h" #include "Serialization/ArchiveScriptReferenceCollector.h" -#include "AnimBlueprintCompiler.h" #include "UObject/UnrealTypePrivate.h" static bool bDebugPropertyPropagation = false; @@ -140,7 +139,6 @@ FKismetCompilerContext::FKismetCompilerContext(UBlueprint* SourceSketch, FCompil , OldLinker(nullptr) , TargetClass(nullptr) , bAssignDelegateSignatureFunction(false) - , bGenerateLinkedAnimGraphVariables(false) { MacroRowMaxHeight = 0; @@ -1481,6 +1479,16 @@ bool FKismetCompilerContext::ShouldForceKeepNode(const UEdGraphNode* Node) const } } +void FKismetCompilerContext::PruneIsolatedNodes(UEdGraph* InGraph, bool bInIncludeNodesThatCouldBeExpandedToRootSet) +{ + TArray RootSet; + // Find any all entry points caused by special nodes + GatherRootSet(InGraph, RootSet, bInIncludeNodesThatCouldBeExpandedToRootSet); + + // Find the connected subgraph starting at the root node and prune out unused nodes + PruneIsolatedNodes(RootSet, InGraph->Nodes); +} + /** Prunes any nodes that weren't visited from the graph, printing out a warning */ void FKismetCompilerContext::PruneIsolatedNodes(const TArray& RootSet, TArray& GraphNodes) { @@ -1685,15 +1693,9 @@ void FKismetCompilerContext::PrecompileFunction(FKismetFunctionContext& Context, ); } - { - TArray RootSet; - const bool bIncludePotentialRootNodes = false; - // Find any all entry points caused by special nodes - GatherRootSet(Context.SourceGraph, RootSet, bIncludePotentialRootNodes); - - // Find the connected subgraph starting at the root node and prune out unused nodes - PruneIsolatedNodes(RootSet, Context.SourceGraph->Nodes); - } + // Find the connected subgraph starting at the root node and prune out unused nodes + const bool bIncludePotentialRootNodes = false; + PruneIsolatedNodes(Context.SourceGraph, bIncludePotentialRootNodes); if (bIsFullCompile) { @@ -3132,13 +3134,9 @@ void FKismetCompilerContext::ExpansionStep(UEdGraph* Graph, bool bAllowUbergraph { auto PruneInner = [=]() { - TArray RootSet; - const bool bIncludePotentialRootNodes = true; - // Find any all entry points caused by special nodes - GatherRootSet(Graph, RootSet, bIncludePotentialRootNodes); - // Find the connected subgraph starting at the root node and prune out unused nodes - PruneIsolatedNodes(RootSet, Graph->Nodes); + const bool bIncludePotentialRootNodes = true; + PruneIsolatedNodes(Graph, bIncludePotentialRootNodes); }; // Node expansion may affect the signature of a static function @@ -3183,6 +3181,8 @@ void FKismetCompilerContext::ExpansionStep(UEdGraph* Graph, bool bAllowUbergraph // Expand timeline nodes, in skeleton classes only the events will be generated ExpandTimelineNodes(Graph); } + + PostExpansionStep(Graph); } void FKismetCompilerContext::DetermineNodeExecLinks(UEdGraphNode* SourceNode, TMap& SourceNodeLinks) const @@ -4835,14 +4835,7 @@ TMap< UClass*, CompilerContextFactoryFunction> CustomCompilerMap; TSharedPtr FKismetCompilerContext::GetCompilerForBP(UBlueprint* BP, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompileOptions) { - // Typically whatever loads the compiler module can also register it (or the module can self register). Due to load order - // issues anim blueprint is part of Engine and so there is no obvious place to register FAnimBlueprintCompilerContext, - // so I have simply hard-coded it: - if(UAnimBlueprint* AnimBP = Cast(BP)) - { - return TSharedPtr(new FAnimBlueprintCompilerContext(AnimBP, InMessageLog, InCompileOptions)); - } - else if(CompilerContextFactoryFunction* FactoryFunction = CustomCompilerMap.Find(BP->GetClass())) + if(CompilerContextFactoryFunction* FactoryFunction = CustomCompilerMap.Find(BP->GetClass())) { return (*FactoryFunction)(BP, InMessageLog, InCompileOptions); } diff --git a/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerMisc.cpp b/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerMisc.cpp index 52d4548bed63..9f1c089223c6 100644 --- a/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerMisc.cpp +++ b/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerMisc.cpp @@ -89,11 +89,11 @@ static bool DoesTypeNotMatchProperty(UEdGraphPin* SourcePin, const FEdGraphPinTy else { const UClass* MetaClass = NULL; - if (auto ClassProperty = CastField(TestProperty)) + if (FClassProperty* ClassProperty = CastField(TestProperty)) { MetaClass = ClassProperty->MetaClass; } - else if (auto SoftClassProperty = CastField(TestProperty)) + else if (FSoftClassProperty* SoftClassProperty = CastField(TestProperty)) { MetaClass = SoftClassProperty->MetaClass; } @@ -248,7 +248,7 @@ static bool DoesTypeNotMatchProperty(UEdGraphPin* SourcePin, const FEdGraphPinTy if (StructProperty != NULL) { bool bMatchingStructs = (StructType == StructProperty->Struct); - if (auto UserDefinedStructFromProperty = Cast(StructProperty->Struct)) + if (const UUserDefinedStruct* UserDefinedStructFromProperty = Cast(StructProperty->Struct)) { bMatchingStructs |= (UserDefinedStructFromProperty->PrimaryStruct.Get() == StructType); } @@ -472,7 +472,7 @@ void FKismetCompilerUtilities::ConsignToOblivion(UClass* OldClass, bool bForceNo if (OldClass != NULL) { // Use the Kismet class reinstancer to ensure that the CDO and any existing instances of this class are cleaned up! - auto CTOResinstancer = FBlueprintCompileReinstancer::Create(OldClass); + TSharedPtr CTOResinstancer = FBlueprintCompileReinstancer::Create(OldClass); UPackage* OwnerOutermost = OldClass->GetOutermost(); if( OldClass->ClassDefaultObject ) @@ -1354,7 +1354,7 @@ void FKismetCompilerUtilities::ValidateProperEndExecutionPath(FKismetFunctionCon if (!bAlreadyVisited && !SourceNode->IsA()) { const bool bIsExecutionSequence = IsExecutionSequence(SourceNode); - for (auto CurrentPin : SourceNode->Pins) + for (UEdGraphPin* CurrentPin : SourceNode->Pins) { if (CurrentPin && (CurrentPin->Direction == EEdGraphPinDirection::EGPD_Output) @@ -1372,8 +1372,8 @@ void FKismetCompilerUtilities::ValidateProperEndExecutionPath(FKismetFunctionCon } continue; } - auto LinkedPin = CurrentPin->LinkedTo[0]; - auto NextNode = ensure(LinkedPin) ? Cast(LinkedPin->GetOwningNodeUnchecked()) : nullptr; + UEdGraphPin* LinkedPin = CurrentPin->LinkedTo[0]; + const UK2Node* NextNode = ensure(LinkedPin) ? Cast(LinkedPin->GetOwningNodeUnchecked()) : nullptr; ensure(NextNode); if (CurrentNode) { @@ -1398,16 +1398,16 @@ void FKismetCompilerUtilities::ValidateProperEndExecutionPath(FKismetFunctionCon BreakableNodes.Add(SourceNode, &bAlreadyVisited); if (!bAlreadyVisited) { - for (auto CurrentPin : SourceNode->Pins) + for (UEdGraphPin* CurrentPin : SourceNode->Pins) { if (CurrentPin && (CurrentPin->Direction == EEdGraphPinDirection::EGPD_Input) && (CurrentPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) && CurrentPin->LinkedTo.Num()) { - for (auto LinkedPin : CurrentPin->LinkedTo) + for (UEdGraphPin* LinkedPin : CurrentPin->LinkedTo) { - auto NextNode = ensure(LinkedPin) ? Cast(LinkedPin->GetOwningNodeUnchecked()) : nullptr; + const UK2Node* NextNode = ensure(LinkedPin) ? Cast(LinkedPin->GetOwningNodeUnchecked()) : nullptr; ensure(NextNode); if (!FRecrursiveHelper::IsExecutionSequence(NextNode)) { @@ -1429,19 +1429,19 @@ void FKismetCompilerUtilities::ValidateProperEndExecutionPath(FKismetFunctionCon , TSet& BreakableNodes , FKismetFunctionContext& InContext) { - for (auto SequenceNode : UnBreakableExecutionSequenceNodes) + for (const UK2Node_ExecutionSequence* SequenceNode : UnBreakableExecutionSequenceNodes) { bool bIsBreakable = true; // Sequence is breakable when all it's outputs are breakable - for (auto CurrentPin : SequenceNode->Pins) + for (UEdGraphPin* CurrentPin : SequenceNode->Pins) { if (CurrentPin && (CurrentPin->Direction == EEdGraphPinDirection::EGPD_Output) && (CurrentPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) && CurrentPin->LinkedTo.Num()) { - auto LinkedPin = CurrentPin->LinkedTo[0]; - auto NextNode = ensure(LinkedPin) ? Cast(LinkedPin->GetOwningNodeUnchecked()) : nullptr; + UEdGraphPin* LinkedPin = CurrentPin->LinkedTo[0]; + const UK2Node* NextNode = ensure(LinkedPin) ? Cast(LinkedPin->GetOwningNodeUnchecked()) : nullptr; ensure(NextNode); if (!BreakableNodes.Contains(NextNode)) { @@ -1465,7 +1465,7 @@ void FKismetCompilerUtilities::ValidateProperEndExecutionPath(FKismetFunctionCon static void CheckDeadExecutionPath(TSet& BreakableNodesSeeds, FKismetFunctionContext& InContext) { TSet UnBreakableExecutionSequenceNodes; - for (auto Node : InContext.SourceGraph->Nodes) + for (UEdGraphNode* Node : InContext.SourceGraph->Nodes) { if (FRecrursiveHelper::IsExecutionSequence(Node)) { @@ -1476,7 +1476,7 @@ void FKismetCompilerUtilities::ValidateProperEndExecutionPath(FKismetFunctionCon TSet BreakableNodes; while (BreakableNodesSeeds.Num()) { - for (auto StartingNode : BreakableNodesSeeds) + for (const UK2Node* StartingNode : BreakableNodesSeeds) { GatherBreakableNodes(StartingNode, BreakableNodes, InContext); } @@ -1484,10 +1484,10 @@ void FKismetCompilerUtilities::ValidateProperEndExecutionPath(FKismetFunctionCon FRecrursiveHelper::GatherBreakableNodesSeedsFromSequences(UnBreakableExecutionSequenceNodes, BreakableNodesSeeds, BreakableNodes, InContext); } - for (auto UnBreakableExecutionSequenceNode : UnBreakableExecutionSequenceNodes) + for (const UK2Node_ExecutionSequence* UnBreakableExecutionSequenceNode : UnBreakableExecutionSequenceNodes) { bool bUnBreakableOutputWasFound = false; - for (auto CurrentPin : UnBreakableExecutionSequenceNode->Pins) + for (UEdGraphPin* CurrentPin : UnBreakableExecutionSequenceNode->Pins) { if (CurrentPin && (CurrentPin->Direction == EEdGraphPinDirection::EGPD_Output) @@ -1500,8 +1500,8 @@ void FKismetCompilerUtilities::ValidateProperEndExecutionPath(FKismetFunctionCon break; } - auto LinkedPin = CurrentPin->LinkedTo[0]; - auto NextNode = ensure(LinkedPin) ? Cast(LinkedPin->GetOwningNodeUnchecked()) : nullptr; + UEdGraphPin* LinkedPin = CurrentPin->LinkedTo[0]; + const UK2Node* NextNode = ensure(LinkedPin) ? Cast(LinkedPin->GetOwningNodeUnchecked()) : nullptr; ensure(NextNode); if (!BreakableNodes.Contains(NextNode)) { @@ -1872,6 +1872,11 @@ FString FNetNameMapping::MakeBaseName(const UEdGraphNode* Net) return FString::Printf(TEXT("%s"), *Net->GetDescriptiveCompiledName()); } +FString FNetNameMapping::MakeBaseName(const UObject* Net) +{ + return FString::Printf(TEXT("%s"), *Net->GetFName().GetPlainNameString()); +} + ////////////////////////////////////////////////////////////////////////// // FKismetFunctionContext @@ -1950,12 +1955,12 @@ bool FKismetFunctionContext::DoesStatementRequiresSwitch(const FBlueprintCompile bool FKismetFunctionContext::MustUseSwitchState(const FBlueprintCompiledStatement* ExcludeThisOne) const { - for (auto Node : LinearExecutionList) + for (UEdGraphNode* Node : LinearExecutionList) { - auto StatementList = StatementsPerNode.Find(Node); + const TArray* StatementList = StatementsPerNode.Find(Node); if (StatementList) { - for (auto Statement : (*StatementList)) + for (FBlueprintCompiledStatement* Statement : (*StatementList)) { if (Statement && (Statement != ExcludeThisOne) && DoesStatementRequiresSwitch(Statement)) { @@ -1972,21 +1977,21 @@ void FKismetFunctionContext::MergeAdjacentStates() for (int32 ExecIndex = 0; ExecIndex < LinearExecutionList.Num(); ++ExecIndex) { // if the last statement in current node jumps to the first statement in next node, then it's redundant - const auto CurrentNode = LinearExecutionList[ExecIndex]; - auto CurStatementList = StatementsPerNode.Find(CurrentNode); + const UEdGraphNode* CurrentNode = LinearExecutionList[ExecIndex]; + TArray* CurStatementList = StatementsPerNode.Find(CurrentNode); const bool CurrentNodeIsValid = CurrentNode && CurStatementList && CurStatementList->Num(); - const auto LastStatementInCurrentNode = CurrentNodeIsValid ? CurStatementList->Last() : NULL; + const FBlueprintCompiledStatement* LastStatementInCurrentNode = CurrentNodeIsValid ? CurStatementList->Last() : nullptr; if (LastStatementInCurrentNode && LastStatementInCurrentNode->TargetLabel && (LastStatementInCurrentNode->Type == KCST_UnconditionalGoto) && !LastStatementInCurrentNode->bIsJumpTarget) { - const auto NextNodeIndex = ExecIndex + 1; - const auto NextNode = LinearExecutionList.IsValidIndex(NextNodeIndex) ? LinearExecutionList[NextNodeIndex] : NULL; - const auto NextNodeStatements = StatementsPerNode.Find(NextNode); + const int32 NextNodeIndex = ExecIndex + 1; + const UEdGraphNode* NextNode = LinearExecutionList.IsValidIndex(NextNodeIndex) ? LinearExecutionList[NextNodeIndex] : nullptr; + const TArray* NextNodeStatements = StatementsPerNode.Find(NextNode); const bool bNextNodeValid = NextNode && NextNodeStatements && NextNodeStatements->Num(); - const auto FirstStatementInNextNode = bNextNodeValid ? (*NextNodeStatements)[0] : NULL; + const FBlueprintCompiledStatement* FirstStatementInNextNode = bNextNodeValid ? (*NextNodeStatements)[0] : nullptr; if (FirstStatementInNextNode == LastStatementInCurrentNode->TargetLabel) { CurStatementList->RemoveAt(CurStatementList->Num() - 1); @@ -1996,9 +2001,9 @@ void FKismetFunctionContext::MergeAdjacentStates() // Remove unnecessary GotoReturn statements // if it's last statement generated by last node (in LinearExecution) then it can be removed - const auto LastExecutedNode = LinearExecutionList.Num() ? LinearExecutionList.Last() : NULL; + const UEdGraphNode* LastExecutedNode = LinearExecutionList.Num() ? LinearExecutionList.Last() : nullptr; TArray* StatementList = StatementsPerNode.Find(LastExecutedNode); - FBlueprintCompiledStatement* LastStatementInLastNode = (StatementList && StatementList->Num()) ? StatementList->Last() : NULL; + FBlueprintCompiledStatement* LastStatementInLastNode = (StatementList && StatementList->Num()) ? StatementList->Last() : nullptr; const bool SafeForNativeCode = !bGeneratingCpp || !MustUseSwitchState(LastStatementInLastNode); if (LastStatementInLastNode && SafeForNativeCode && (KCST_GotoReturn == LastStatementInLastNode->Type) && !LastStatementInLastNode->bIsJumpTarget) { @@ -2034,8 +2039,8 @@ struct FGotoMapUtils static UEdGraphNode* TargetNodeFromMap(const FBlueprintCompiledStatement* GotoStatement, const TMap< FBlueprintCompiledStatement*, UEdGraphPin* >& GotoFixupRequestMap) { - auto ExecNetPtr = GotoFixupRequestMap.Find(GotoStatement); - auto ExecNet = ExecNetPtr ? *ExecNetPtr : NULL; + UEdGraphPin* const * ExecNetPtr = GotoFixupRequestMap.Find(GotoStatement); + UEdGraphPin* ExecNet = ExecNetPtr ? *ExecNetPtr : nullptr; return TargetNodeFromPin(GotoStatement, ExecNet); } }; @@ -2102,10 +2107,10 @@ void FKismetFunctionContext::ResolveGotoFixups() void FKismetFunctionContext::FinalSortLinearExecList() { - auto K2Schema = Schema; + const UEdGraphSchema_K2* K2Schema = Schema; LinearExecutionList.RemoveAllSwap([&](UEdGraphNode* CurrentNode) { - auto CurStatementList = StatementsPerNode.Find(CurrentNode); + TArray* CurStatementList = StatementsPerNode.Find(CurrentNode); return !(CurrentNode && CurStatementList && CurStatementList->Num()); }); @@ -2121,30 +2126,30 @@ void FKismetFunctionContext::FinalSortLinearExecList() while (UnsortedExecutionSet.Num()) { - UEdGraphNode* NextNode = NULL; + UEdGraphNode* NextNode = nullptr; // get last state target - const auto CurrentNode = SortedLinearExecutionList.Last(); - const auto CurStatementList = StatementsPerNode.Find(CurrentNode); + const UEdGraphNode* CurrentNode = SortedLinearExecutionList.Last(); + const TArray* CurStatementList = StatementsPerNode.Find(CurrentNode); const bool CurrentNodeIsValid = CurrentNode && CurStatementList && CurStatementList->Num(); - const auto LastStatementInCurrentNode = CurrentNodeIsValid ? CurStatementList->Last() : NULL; + const FBlueprintCompiledStatement* LastStatementInCurrentNode = CurrentNodeIsValid ? CurStatementList->Last() : nullptr; // Find next element in current chain if (LastStatementInCurrentNode && (LastStatementInCurrentNode->Type == KCST_UnconditionalGoto)) { - auto TargetNode = FGotoMapUtils::TargetNodeFromMap(LastStatementInCurrentNode, GotoFixupRequestMap); - NextNode = UnsortedExecutionSet.Remove(TargetNode) ? TargetNode : NULL; + UEdGraphNode* TargetNode = FGotoMapUtils::TargetNodeFromMap(LastStatementInCurrentNode, GotoFixupRequestMap); + NextNode = UnsortedExecutionSet.Remove(TargetNode) ? TargetNode : nullptr; } if (CurrentNode) { - for (auto Pin : CurrentNode->Pins) + for (UEdGraphPin* Pin : CurrentNode->Pins) { if (Pin && (EEdGraphPinDirection::EGPD_Output == Pin->Direction) && K2Schema->IsExecPin(*Pin) && Pin->LinkedTo.Num()) { - for (auto Link : Pin->LinkedTo) + for (UEdGraphPin* Link : Pin->LinkedTo) { - auto LinkedNode = Link->GetOwningNodeUnchecked(); + UEdGraphNode* LinkedNode = Link->GetOwningNodeUnchecked(); if (LinkedNode && (LinkedNode != NextNode) && UnsortedExecutionSet.Contains(LinkedNode)) { NodesToStartNextChain.Add(LinkedNode); @@ -2213,7 +2218,7 @@ struct FEventGraphUtils { bResult |= Node->IsA(); bResult |= Node->IsA(); - if (auto CallNode = Cast(Node)) + if (const UK2Node_CallFunction* CallNode = Cast(Node)) { bResult |= CallNode->IsLatentFunction(); } @@ -2248,16 +2253,16 @@ struct FEventGraphUtils const UEdGraphSchema_K2* Schema = CastChecked(Node->GetSchema()); const bool bIsPure = Node->IsNodePure(); - for (auto Pin : Node->Pins) + for (UEdGraphPin* Pin : Node->Pins) { const bool bProperPure = bIsPure && Pin && (Pin->Direction == EEdGraphPinDirection::EGPD_Output); const bool bProperNotPure = !bIsPure && Pin && (Pin->Direction == EEdGraphPinDirection::EGPD_Input) && Schema->IsExecPin(*Pin); if (bProperPure || bProperNotPure) { - for (auto Link : Pin->LinkedTo) + for (UEdGraphPin* Link : Pin->LinkedTo) { - auto LinkOwner = Link ? Link->GetOwningNodeUnchecked() : NULL; - auto NodeToCheck = LinkOwner ? CastChecked(LinkOwner) : NULL; + UEdGraphNode* LinkOwner = Link ? Link->GetOwningNodeUnchecked() : nullptr; + const UK2Node* NodeToCheck = LinkOwner ? CastChecked(LinkOwner) : nullptr; FindEventsCallingTheNodeRecursive(NodeToCheck, Results, CheckedNodes, StopOn); } } @@ -2321,22 +2326,22 @@ struct FEventGraphUtils } // - auto SourceEntryPoints = FEventGraphUtils::FindExecutionNodes(OwnerNode, NULL); + TSet SourceEntryPoints = FEventGraphUtils::FindExecutionNodes(OwnerNode, nullptr); if (1 != SourceEntryPoints.Num()) { return true; } // - for (auto Link : Net.LinkedTo) + for (UEdGraphPin* Link : Net.LinkedTo) { - auto LinkOwnerNode = Cast(Link->GetOwningNodeUnchecked()); + const UK2Node* LinkOwnerNode = Cast(Link->GetOwningNodeUnchecked()); ensure(LinkOwnerNode); if (Link->PinType.bIsReference) { return true; } - auto EventsCallingDestination = FEventGraphUtils::FindExecutionNodes(LinkOwnerNode, OwnerNode); + TSet EventsCallingDestination = FEventGraphUtils::FindExecutionNodes(LinkOwnerNode, OwnerNode); if (0 != EventsCallingDestination.Num()) { return true; diff --git a/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerModule.cpp b/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerModule.cpp index 0f79d52335bf..1353699d777d 100644 --- a/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerModule.cpp +++ b/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerModule.cpp @@ -19,8 +19,6 @@ #include "KismetCompilerMisc.h" #include "KismetCompiler.h" -#include "AnimBlueprintCompiler.h" - #include "Kismet2/KismetDebugUtilities.h" #include "Kismet2/KismetReinstanceUtilities.h" #include "Kismet2/BlueprintEditorUtils.h" diff --git a/Engine/Source/Editor/KismetCompiler/Public/KismetCompiler.h b/Engine/Source/Editor/KismetCompiler/Public/KismetCompiler.h index e2777456eb70..2476bcd6010f 100644 --- a/Engine/Source/Editor/KismetCompiler/Public/KismetCompiler.h +++ b/Engine/Source/Editor/KismetCompiler/Public/KismetCompiler.h @@ -139,9 +139,6 @@ public: // CreateClassVariablesFromBlueprint: bool bAssignDelegateSignatureFunction; - // Flag to trigger ProcessLinkedGraph in CreateClassVariablesFromBlueprint: - bool bGenerateLinkedAnimGraphVariables; - static FSimpleMulticastDelegate OnPreCompile; static FSimpleMulticastDelegate OnPostCompile; @@ -354,6 +351,9 @@ protected: */ void CheckConnectionResponse(const FPinConnectionResponse &Response, const UEdGraphNode *Node); + /** Prune isolated nodes given the specified graph */ + void PruneIsolatedNodes(UEdGraph* InGraph, bool bInIncludeNodesThatCouldBeExpandedToRootSet); + protected: // FGraphCompilerContext interface virtual void ValidateLink(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const override; @@ -387,6 +387,9 @@ protected: virtual void PreCompile() { OnPreCompile.Broadcast(); } virtual void PostCompile() { OnPostCompile.Broadcast(); } + // Gives derived classes a chance to process post-node expansion + virtual void PostExpansionStep(UEdGraph* Graph) {} + /** Determines if a node is pure */ virtual bool IsNodePure(const UEdGraphNode* Node) const; diff --git a/Engine/Source/Editor/KismetCompiler/Public/KismetCompilerMisc.h b/Engine/Source/Editor/KismetCompiler/Public/KismetCompilerMisc.h index ee02208e2ba9..d71148f17ae4 100644 --- a/Engine/Source/Editor/KismetCompiler/Public/KismetCompilerMisc.h +++ b/Engine/Source/Editor/KismetCompiler/Public/KismetCompilerMisc.h @@ -204,10 +204,12 @@ public: // The resulting name is stable across multiple calls if given the same pointer. FString MakeValidName(const UEdGraphNode* Net, const FString& Context = TEXT("")) { return MakeValidNameImpl(Net, Context); } FString MakeValidName(const UEdGraphPin* Net, const FString& Context = TEXT("")) { return MakeValidNameImpl(Net, Context); } + FString MakeValidName(const UObject* Net, const FString& Context = TEXT("")) { return MakeValidNameImpl(Net, Context); } private: KISMETCOMPILER_API static FString MakeBaseName(const UEdGraphNode* Net); KISMETCOMPILER_API static FString MakeBaseName(const UEdGraphPin* Net); + KISMETCOMPILER_API static FString MakeBaseName(const UObject* Net); template< typename NetType > FString MakeValidNameImpl(NetType Net, const FString& Context) diff --git a/Engine/Source/Editor/KismetWidgets/Private/SPinTypeSelector.cpp b/Engine/Source/Editor/KismetWidgets/Private/SPinTypeSelector.cpp index b05d1e622b37..0f1450e5b832 100644 --- a/Engine/Source/Editor/KismetWidgets/Private/SPinTypeSelector.cpp +++ b/Engine/Source/Editor/KismetWidgets/Private/SPinTypeSelector.cpp @@ -4,6 +4,7 @@ #include "Widgets/Layout/SSpacer.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Images/SImage.h" +#include "Widgets/Images/SLayeredImage.h" #include "Widgets/Layout/SMenuOwner.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SCheckBox.h" @@ -19,43 +20,6 @@ #define LOCTEXT_NAMESPACE "PinTypeSelector" -/** I need a widget that draws two images on top of each other. This is to represent a TMap (key type and value type): */ -class SDoubleImage : public SImage -{ -public: - void Construct(const FArguments& InArgs, TAttribute InSecondImage, TAttribute InSecondImageColor); - -private: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; - - TAttribute SecondImage; - TAttribute SecondImageColor; -}; - -void SDoubleImage::Construct(const SDoubleImage::FArguments& InArgs, TAttribute InSecondImage, TAttribute InSecondImageColor) -{ - SImage::Construct(InArgs); - SecondImage = InSecondImage; - SecondImageColor = InSecondImageColor; -} - -int32 SDoubleImage::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const -{ - // this will draw Image[0]: - SImage::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - - const bool bIsEnabled = ShouldBeEnabled(bParentEnabled); - const ESlateDrawEffect DrawEffects = bIsEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; - // draw rest of the images, we reuse the LayerId because images are assumed to note overlap: - const FSlateBrush* SecondImageResolved = SecondImage.Get(); - if (SecondImageResolved && SecondImageResolved->DrawAs != ESlateBrushDrawType::NoDrawType) - { - const FLinearColor FinalColorAndOpacity(InWidgetStyle.GetColorAndOpacityTint() * SecondImageColor.Get().GetColor(InWidgetStyle) * SecondImageResolved->GetTint(InWidgetStyle)); - FSlateDrawElement::MakeBox(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), SecondImageResolved, DrawEffects, FinalColorAndOpacity); - } - return LayerId; -} - static const FString BigTooltipDocLink = TEXT("Shared/Editor/Blueprint/VariableTypes"); /** Manages items in the Object Reference Type list, the sub-menu of the PinTypeSelector */ @@ -139,7 +103,7 @@ static bool ContainerRequiresGetTypeHash(EPinContainerType InType) TSharedRef SPinTypeSelector::ConstructPinTypeImage(const FSlateBrush* PrimaryIcon, const FSlateColor& PrimaryColor, const FSlateBrush* SecondaryIcon, const FSlateColor& SecondaryColor, TSharedPtr InToolTip) { return - SNew(SDoubleImage, SecondaryIcon, SecondaryColor) + SNew(SLayeredImage, SecondaryIcon, SecondaryColor) .Image(PrimaryIcon) .ToolTip(InToolTip) .ColorAndOpacity(PrimaryColor); @@ -148,7 +112,7 @@ TSharedRef SPinTypeSelector::ConstructPinTypeImage(const FSlateBrush* P TSharedRef SPinTypeSelector::ConstructPinTypeImage(TAttribute PrimaryIcon, TAttribute PrimaryColor, TAttribute SecondaryIcon, TAttribute SecondaryColor ) { return - SNew(SDoubleImage, SecondaryIcon, SecondaryColor) + SNew(SLayeredImage, SecondaryIcon, SecondaryColor) .Image(PrimaryIcon) .ColorAndOpacity(PrimaryColor); } @@ -279,7 +243,7 @@ void SPinTypeSelector::Construct(const FArguments& InArgs, FGetPinTypeTree GetPi .ButtonContent() [ SNew( - SDoubleImage, + SLayeredImage, TAttribute(this, &SPinTypeSelector::GetSecondaryTypeIconImage), TAttribute(this, &SPinTypeSelector::GetSecondaryTypeIconColor) ) @@ -290,7 +254,7 @@ void SPinTypeSelector::Construct(const FArguments& InArgs, FGetPinTypeTree GetPi else if (SelectorType == ESelectorType::None) { Widget = SNew( - SDoubleImage, + SLayeredImage, TAttribute(this, &SPinTypeSelector::GetSecondaryTypeIconImage), TAttribute(this, &SPinTypeSelector::GetSecondaryTypeIconColor) ) @@ -325,7 +289,7 @@ void SPinTypeSelector::Construct(const FArguments& InArgs, FGetPinTypeTree GetPi .Content() [ SNew( - SDoubleImage, + SLayeredImage, SecondaryIcon, TAttribute(this, &SPinTypeSelector::GetSecondaryTypeIconColor) ) @@ -361,7 +325,7 @@ void SPinTypeSelector::Construct(const FArguments& InArgs, FGetPinTypeTree GetPi .ButtonContent() [ SNew( - SDoubleImage, + SLayeredImage, TAttribute(this, &SPinTypeSelector::GetSecondaryTypeIconImage), TAttribute(this, &SPinTypeSelector::GetSecondaryTypeIconColor) ) diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeComponentTools.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeComponentTools.cpp index 742d566b4e95..315fc79c7c86 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeComponentTools.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeComponentTools.cpp @@ -619,7 +619,7 @@ public: ALandscape* Landscape = LandscapeInfo->LandscapeActor.Get(); - bool bHasLandscapeLayersContent = Landscape->HasLayersContent(); + bool bHasLandscapeLayersContent = Landscape && Landscape->HasLayersContent(); if (bHasLandscapeLayersContent) { @@ -661,7 +661,7 @@ public: TMap NeighbourLayerInfoObjectCount; { - FScopedSetLandscapeEditingLayer Scope(Landscape, Landscape->GetLayer(0)->Guid, [=] { }); + FScopedSetLandscapeEditingLayer Scope(Landscape, Landscape ? Landscape->GetLayer(0)->Guid : FGuid(), [=] { }); // Cover 9 tiles around us to determine which object should we use by default for (int32 ComponentIndexX = ComponentIndexX1 - 1; ComponentIndexX <= ComponentIndexX2 + 1; ++ComponentIndexX) @@ -769,7 +769,10 @@ public: } } - GEngine->BroadcastOnActorMoved(Landscape); + if (Landscape) + { + GEngine->BroadcastOnActorMoved(Landscape); + } } } diff --git a/Engine/Source/Editor/LevelEditor/LevelEditor.Build.cs b/Engine/Source/Editor/LevelEditor/LevelEditor.Build.cs index 572255fa20d9..07cde882d4a7 100644 --- a/Engine/Source/Editor/LevelEditor/LevelEditor.Build.cs +++ b/Engine/Source/Editor/LevelEditor/LevelEditor.Build.cs @@ -74,7 +74,8 @@ public class LevelEditor : ModuleRules "StatusBar", "AppFramework", "EditorSubsystem", - "EnvironmentLightingViewer" + "EnvironmentLightingViewer", + "DesktopPlatform", } ); diff --git a/Engine/Source/Editor/LevelEditor/Private/ActorDetailsExtensionContext.cpp b/Engine/Source/Editor/LevelEditor/Private/ActorDetailsExtensionContext.cpp deleted file mode 100644 index 783d24d58aae..000000000000 --- a/Engine/Source/Editor/LevelEditor/Private/ActorDetailsExtensionContext.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "ActorDetailsExtensionContext.h" - -const TArray>& UActorDetailsExtensionContext::GetSelectedObjects() const -{ - if (!GetSelectedObjectsDelegate.IsBound()) - { - static const TArray> EmptySelection; - return EmptySelection; - } - return GetSelectedObjectsDelegate.Execute(); -} diff --git a/Engine/Source/Editor/LevelEditor/Private/LevelEditor.cpp b/Engine/Source/Editor/LevelEditor/Private/LevelEditor.cpp index 784c0be21071..665436df9f5e 100644 --- a/Engine/Source/Editor/LevelEditor/Private/LevelEditor.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/LevelEditor.cpp @@ -33,6 +33,7 @@ #include "PixelInspectorModule.h" #include "CommonMenuExtensionsModule.h" #include "ProjectDescriptor.h" +#include "PlatformInfo.h" // @todo Editor: remove this circular dependency #include "Interfaces/IMainFrameModule.h" @@ -1786,6 +1787,11 @@ void FLevelEditorModule::BindGlobalLevelEditorCommands() FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::SetMaterialQualityLevel, (EMaterialQualityLevel::Type)EMaterialQualityLevel::High ), FCanExecuteAction(), FIsActionChecked::CreateStatic( &FLevelEditorActionCallbacks::IsMaterialQualityLevelChecked, (EMaterialQualityLevel::Type)EMaterialQualityLevel::High ) ); + ActionList.MapAction( + Commands.MaterialQualityLevel_Epic, + FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::SetMaterialQualityLevel, (EMaterialQualityLevel::Type)EMaterialQualityLevel::Epic), + FCanExecuteAction(), + FIsActionChecked::CreateStatic(&FLevelEditorActionCallbacks::IsMaterialQualityLevelChecked, (EMaterialQualityLevel::Type)EMaterialQualityLevel::Epic)); ActionList.MapAction( Commands.ToggleFeatureLevelPreview, @@ -1800,23 +1806,16 @@ void FLevelEditorModule::BindGlobalLevelEditorCommands() FCanExecuteAction(), FIsActionChecked::CreateStatic(&FLevelEditorActionCallbacks::IsPreviewPlatformChecked, FPreviewPlatformInfo(ERHIFeatureLevel::SM5, NAME_None))); - ActionList.MapAction( - Commands.PreviewPlatformOverride_AndroidGLES31, - FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::SetPreviewPlatform, FPreviewPlatformInfo(ERHIFeatureLevel::ES3_1, LegacyShaderPlatformToShaderFormat(SP_OPENGL_ES3_1_ANDROID), true)), - FCanExecuteAction(), - FIsActionChecked::CreateStatic(&FLevelEditorActionCallbacks::IsPreviewPlatformChecked, FPreviewPlatformInfo(ERHIFeatureLevel::ES3_1, LegacyShaderPlatformToShaderFormat(SP_OPENGL_ES3_1_ANDROID)))); + for (auto It = PlatformInfo::GetPreviewPlatformMenuItems().CreateConstIterator(); It; ++It) + { + ERHIFeatureLevel::Type FeatureLevel = GetMaxSupportedFeatureLevel(ShaderFormatToLegacyShaderPlatform(It.Key())); - ActionList.MapAction( - Commands.PreviewPlatformOverride_AndroidVulkanES31, - FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::SetPreviewPlatform, FPreviewPlatformInfo(ERHIFeatureLevel::ES3_1, LegacyShaderPlatformToShaderFormat(SP_VULKAN_ES3_1_ANDROID), true)), - FCanExecuteAction(), - FIsActionChecked::CreateStatic(&FLevelEditorActionCallbacks::IsPreviewPlatformChecked, FPreviewPlatformInfo(ERHIFeatureLevel::ES3_1, LegacyShaderPlatformToShaderFormat(SP_VULKAN_ES3_1_ANDROID)))); - - ActionList.MapAction( - Commands.PreviewPlatformOverride_IOSMetalES31, - FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::SetPreviewPlatform, FPreviewPlatformInfo(ERHIFeatureLevel::ES3_1, LegacyShaderPlatformToShaderFormat(SP_METAL), true)), - FCanExecuteAction(), - FIsActionChecked::CreateStatic(&FLevelEditorActionCallbacks::IsPreviewPlatformChecked, FPreviewPlatformInfo(ERHIFeatureLevel::ES3_1, LegacyShaderPlatformToShaderFormat(SP_METAL)))); + ActionList.MapAction( + *Commands.PreviewPlatformOverrides.Find(It.Key()), + FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::SetPreviewPlatform, FPreviewPlatformInfo(FeatureLevel, It.Key(), true)), + FCanExecuteAction(), + FIsActionChecked::CreateStatic(&FLevelEditorActionCallbacks::IsPreviewPlatformChecked, FPreviewPlatformInfo(FeatureLevel, It.Key()))); + } ActionList.MapAction( Commands.OpenMergeActor, diff --git a/Engine/Source/Editor/LevelEditor/Private/LevelEditorActions.cpp b/Engine/Source/Editor/LevelEditor/Private/LevelEditorActions.cpp index bd236cee65e0..2aea4af079cf 100644 --- a/Engine/Source/Editor/LevelEditor/Private/LevelEditorActions.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/LevelEditorActions.cpp @@ -91,6 +91,8 @@ #include "LevelUtils.h" #include "ISceneOutliner.h" #include "ISettingsModule.h" +#include "PlatformInfo.h" +#include "Misc/CoreMisc.h" #if WITH_LIVE_CODING #include "ILiveCodingModule.h" @@ -600,7 +602,7 @@ bool FLevelEditorActionCallbacks::IsFeatureLevelPreviewActive() bool FLevelEditorActionCallbacks::IsPreviewModeButtonVisible() { - return GEditor->PreviewPlatform.PreviewFeatureLevel != ERHIFeatureLevel::SM5; + return GEditor->IsFeatureLevelPreviewEnabled(); } void FLevelEditorActionCallbacks::SetPreviewPlatform(FPreviewPlatformInfo NewPreviewPlatform) @@ -3358,14 +3360,26 @@ void FLevelEditorCommands::RegisterCommands() UI_COMMAND(MaterialQualityLevel_Low, "Low", "Sets material quality in the scene to low.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(MaterialQualityLevel_Medium, "Medium", "Sets material quality in the scene to medium.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(MaterialQualityLevel_High, "High", "Sets material quality in the scene to high.", EUserInterfaceActionType::RadioButton, FInputChord()); + UI_COMMAND(MaterialQualityLevel_Epic, "Epic", "Sets material quality in the scene to Epic.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(ToggleFeatureLevelPreview, "Preview Mode Toggle", "Toggles the Preview Mode on or off for the currently selected Preview target", EUserInterfaceActionType::ToggleButton, FInputChord()); UI_COMMAND(PreviewPlatformOverride_SM5, "Shader Model 5", "DirectX 11, OpenGL 4.3+, PS4, XB1", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(PreviewPlatformOverride_AndroidGLES31, "Android ES 3.1", "Mobile preview using Android ES3.1 quality settings.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(PreviewPlatformOverride_AndroidVulkanES31, "Android Vulkan", "Mobile preview using Android Vulkan quality settings.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(PreviewPlatformOverride_AndroidVulkanSM5, "Android Vulkan SM5", "Mobile preview using Android Vulkan SM5 quality settings.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(PreviewPlatformOverride_IOSMetalES31, "iOS", "Mobile preview using iOS material quality settings.", EUserInterfaceActionType::Check, FInputChord()); + + // Add preview platforms + for (auto It = PlatformInfo::GetPreviewPlatformMenuItems().CreateConstIterator(); It; ++It) + { + PreviewPlatformOverrides.Add( + It.Key(), + FUICommandInfoDecl( + this->AsShared(), + FName(*(FString(TEXT("PreviewPlatformOverrides"))+It.Key().ToString())), + It.Value().MenuText, + It.Value().MenuTooltip) + .UserInterfaceType(EUserInterfaceActionType::Check) + .DefaultChord(FInputChord()) + ); + } UI_COMMAND(GeometryCollectionSelectAllGeometry, "Select All Geometry In Hierarchy", "Select all geometry in hierarchy", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(GeometryCollectionSelectNone, "Deselect All Geometry In Hierarchy", "Deselect all geometry in hierarchy", EUserInterfaceActionType::Button, FInputChord()); diff --git a/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp b/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp index e62bbb1cb24f..1adcb1e9ac93 100644 --- a/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp @@ -63,6 +63,7 @@ #include "LevelEditorModesActions.h" #include "ISourceControlModule.h" #include "Styling/ToolBarStyle.h" +#include "PlatformInfo.h" static TAutoConsoleVariable CVarAllowMatineeActors( TEXT("Matinee.AllowMatineeActors"), @@ -1303,33 +1304,14 @@ void FLevelEditorToolBar::RegisterLevelEditorToolBar( const TSharedRefPreviewPlatform.PreviewShaderPlatformName); - - switch (ShaderPlatform) - { - case SP_VULKAN_ES3_1_ANDROID: - { - return LOCTEXT("PreviewModeES31_Vulkan_Text", "Vulkan Preview"); - } - } - - switch (GEditor->PreviewPlatform.PreviewFeatureLevel) - { - case ERHIFeatureLevel::ES3_1: - { - return LOCTEXT("PreviewModeES3_1_Text", "ES3.1 Preview"); - } - default: - { - return LOCTEXT("PreviewModeGeneric", "Preview Mode"); - } - } + const FPreviewPlatformMenuItem* Item = PlatformInfo::GetPreviewPlatformMenuItems().Find(GEditor->PreviewPlatform.PreviewShaderFormatName); + return Item ? Item->IconText : FText(); } static FText GetPreviewModeTooltip() { - EShaderPlatform PreviewShaderPlatform = GEditor->PreviewPlatform.PreviewShaderPlatformName != NAME_None ? - ShaderFormatToLegacyShaderPlatform(GEditor->PreviewPlatform.PreviewShaderPlatformName) : + EShaderPlatform PreviewShaderPlatform = GEditor->PreviewPlatform.PreviewShaderFormatName != NAME_None ? + ShaderFormatToLegacyShaderPlatform(GEditor->PreviewPlatform.PreviewShaderFormatName) : GetFeatureLevelShaderPlatform(GEditor->PreviewPlatform.PreviewFeatureLevel); EShaderPlatform MaxRHIFeatureLevelPlatform = GetFeatureLevelShaderPlatform(GMaxRHIFeatureLevel); @@ -1350,31 +1332,18 @@ void FLevelEditorToolBar::RegisterLevelEditorToolBar( const TSharedRefPreviewPlatform.PreviewShaderPlatformName); + const FPreviewPlatformMenuItem* Item = PlatformInfo::GetPreviewPlatformMenuItems().Find(GEditor->PreviewPlatform.PreviewShaderFormatName); + if(Item) + { + return FSlateIcon(FEditorStyle::GetStyleSetName(), GEditor->IsFeatureLevelPreviewActive() ? Item->ActiveIconName : Item->InactiveIconName); + } + + EShaderPlatform ShaderPlatform = ShaderFormatToLegacyShaderPlatform(GEditor->PreviewPlatform.PreviewShaderFormatName); if (ShaderPlatform == SP_NumPlatforms) { ShaderPlatform = GetFeatureLevelShaderPlatform(GEditor->PreviewPlatform.PreviewFeatureLevel); } - switch (ShaderPlatform) - { - case SP_OPENGL_ES3_1_ANDROID: - { - return FSlateIcon(FEditorStyle::GetStyleSetName(), GEditor->IsFeatureLevelPreviewActive() ? "LevelEditor.PreviewMode.AndroidES31.Enabled" : "LevelEditor.PreviewMode.AndroidES31.Disabled"); - } - case SP_VULKAN_ES3_1_ANDROID: - { - return FSlateIcon(FEditorStyle::GetStyleSetName(), GEditor->IsFeatureLevelPreviewActive() ? "LevelEditor.PreviewMode.AndroidVulkan.Enabled" : "LevelEditor.PreviewMode.AndroidVulkan.Disabled"); - } - case SP_METAL: - { - return FSlateIcon(FEditorStyle::GetStyleSetName(), GEditor->IsFeatureLevelPreviewActive() ? "LevelEditor.PreviewMode.iOS.Enabled" : "LevelEditor.PreviewMode.iOS.Disabled"); - } - case SP_VULKAN_PCES3_1: - { - return FSlateIcon(FEditorStyle::GetStyleSetName(), GEditor->IsFeatureLevelPreviewActive() ? "LevelEditor.PreviewMode.AndroidES2.Enabled" : "LevelEditor.PreviewMode.AndroidES2.Disabled"); - } - } switch (GEditor->PreviewPlatform.PreviewFeatureLevel) { case ERHIFeatureLevel::ES3_1: @@ -1484,6 +1453,7 @@ static void MakeMaterialQualityLevelMenu( UToolMenu* InMenu ) Section.AddMenuEntry(FLevelEditorCommands::Get().MaterialQualityLevel_Low); Section.AddMenuEntry(FLevelEditorCommands::Get().MaterialQualityLevel_Medium); Section.AddMenuEntry(FLevelEditorCommands::Get().MaterialQualityLevel_High); + Section.AddMenuEntry(FLevelEditorCommands::Get().MaterialQualityLevel_Epic); } } @@ -1496,35 +1466,10 @@ static void MakeShaderModelPreviewMenu( UToolMenu* InMenu ) // SM5 Section.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_SM5); - // Android - bool bAndroidBuildForES31 = false; - GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bBuildForES31"), bAndroidBuildForES31, GEngineIni); - if (bAndroidBuildForES31) + // Preview platforms discovered from ITargetPlatforms. + for (auto It = FLevelEditorCommands::Get().PreviewPlatformOverrides.CreateConstIterator(); It; ++It) { - Section.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_AndroidGLES31); - } - - bool bAndroidSupportsVulkan = false; - GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bSupportsVulkan"), bAndroidSupportsVulkan, GEngineIni); - if (bAndroidSupportsVulkan) - { - Section.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_AndroidVulkanES31); - } - - bool bAndroidSupportsMobileVulkanSM5 = false; - GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bSupportsAndroidVulkanSM5"), bAndroidSupportsVulkan, GEngineIni); - if (bAndroidSupportsMobileVulkanSM5) - { - Section.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_AndroidVulkanSM5); - } - - - // iOS - bool bIOSSupportsMetal = false; - GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("bSupportsMetal"), bIOSSupportsMetal, GEngineIni); - if (bIOSSupportsMetal) - { - Section.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_IOSMetalES31); + Section.AddMenuEntry(It.Value()); } #undef LOCTEXT_NAMESPACE @@ -1626,7 +1571,7 @@ void FLevelEditorToolBar::RegisterQuickSettingsMenu() Section.AddSubMenu( "MaterialQualityLevel", LOCTEXT( "MaterialQualityLevelSubMenu", "Material Quality Level" ), - LOCTEXT( "MaterialQualityLevelSubMenu_ToolTip", "Sets the value of the CVar \"r.MaterialQualityLevel\" (low=0, high=1, medium=2). This affects materials via the QualitySwitch material expression." ), + LOCTEXT( "MaterialQualityLevelSubMenu_ToolTip", "Sets the value of the CVar \"r.MaterialQualityLevel\" (low=0, high=1, medium=2, Epic=3). This affects materials via the QualitySwitch material expression." ), FNewToolMenuDelegate::CreateStatic( &MakeMaterialQualityLevelMenu ) ); Section.AddSubMenu( @@ -1789,9 +1734,8 @@ void FLevelEditorToolBar::RegisterOpenBlueprintMenu() Config.InitialAssetViewType = EAssetViewType::List; Config.OnAssetSelected = FOnAssetSelected::CreateStatic(&FBlueprintMenus::OnBPSelected); Config.bAllowDragging = false; - // Don't show stuff in Engine - Config.Filter.PackagePaths.Add("/Game"); - Config.Filter.bRecursivePaths = true; + // Allow saving user defined filters via View Options + Config.SaveSettingsName = FString(TEXT("ToolbarOpenBPClass")); TSharedRef Widget = SNew(SBox) diff --git a/Engine/Source/Editor/LevelEditor/Private/SActorDetails.cpp b/Engine/Source/Editor/LevelEditor/Private/SActorDetails.cpp index d3846851a8ae..85d3a6306999 100644 --- a/Engine/Source/Editor/LevelEditor/Private/SActorDetails.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/SActorDetails.cpp @@ -29,12 +29,8 @@ #include "ScopedTransaction.h" #include "SourceCodeNavigation.h" #include "Widgets/Docking/SDockTab.h" -#include "Subsystems/PanelExtensionSubsystem.h" #include "DetailsViewObjectFilter.h" #include "IDetailRootObjectCustomization.h" -#include "ActorDetailsExtensionContext.h" - -IConsoleVariable* SActorDetails::ShowComponents = IConsoleManager::Get().RegisterConsoleVariable(TEXT("ShowFlag.DetailsPanelComponents"), true, TEXT("Show components in editor details panel."), ECVF_Cheat); class SActorDetailsUneditableComponentWarning : public SCompoundWidget @@ -142,10 +138,6 @@ void SActorDetails::Construct(const FArguments& InArgs, const FName TabIdentifie ComponentsBox->SetContent(SCSEditor.ToSharedRef()); - UActorDetailsExtensionContext* ExtensionContext = NewObject(); - ExtensionContext->GetSelectedObjectsDelegate.BindSP(this, &SActorDetails::GetDetailsViewSelectedObjects); - ExtensionContext->AddToRoot(); - TSharedRef ButtonBox = SCSEditor->GetToolButtonsBox().ToSharedRef(); DetailsView->SetNameAreaCustomContent( ButtonBox ); @@ -159,14 +151,6 @@ void SActorDetails::Construct(const FArguments& InArgs, const FName TabIdentifie [ DetailsView->GetNameAreaWidget().ToSharedRef() ] - + SVerticalBox::Slot() - .Padding(0.0f, 0.0f, 0.0f, 0.0f) - .AutoHeight() - [ - SAssignNew(ExtensionPanel, SExtensionPanel) - .ExtensionPanelID("ActorDetailsPanel") - .ExtensionContext(ExtensionContext) - ] +SVerticalBox::Slot() [ SAssignNew(DetailsSplitter, SSplitter) @@ -238,11 +222,6 @@ SActorDetails::~SActorDetails() { LevelEditor->OnComponentsEdited().RemoveAll(this); } - - if (UObject* ExtensionContext = ExtensionPanel->GetExtensionContext()) - { - ExtensionContext->RemoveFromRoot(); - } } void SActorDetails::OnDetailsViewObjectArrayChanged(const FString& InTitle, const TArray& InObjects) @@ -260,8 +239,6 @@ void SActorDetails::SetObjects(const TArray& InObjects, bool bForceRef { DetailsView->SetObjects(InObjects, bForceRefresh); - ExtensionPanel->SetVisibility(InObjects.Num() > 0 ? EVisibility::Visible : EVisibility::Collapsed); - bHasComponentsToShow = false; if (InObjects.Num() == 1 && FKismetEditorUtilities::CanCreateBlueprintOfClass(InObjects[0]->GetClass())) @@ -335,6 +312,14 @@ void SActorDetails::SetActorDetailsRootCustomization(TSharedPtrForceRefresh(); } +void SActorDetails::SetSCSEditorUICustomization(TSharedPtr ActorDetailsSCSEditorUICustomization) +{ + if (SCSEditor.IsValid()) + { + SCSEditor->SetUICustomization(ActorDetailsSCSEditorUICustomization); + } +} + void SActorDetails::OnComponentsEditedInWorld() { if (GetSelectedActorInEditor() == GetActorContext()) @@ -400,11 +385,6 @@ AActor* SActorDetails::GetActorContext() const } } -const TArray>& SActorDetails::GetDetailsViewSelectedObjects() const -{ - return DetailsView->GetSelectedObjects(); -} - void SActorDetails::OnSCSEditorTreeViewSelectionChanged(const TArray& SelectedNodes) { if (!bSelectionGuard && SelectedNodes.Num() > 0) @@ -715,7 +695,7 @@ void SActorDetails::OnNativeComponentWarningHyperlinkClicked(const FSlateHyperli EVisibility SActorDetails::GetComponentsBoxVisibility() const { - return (bHasComponentsToShow && ShowComponents->GetBool()) ? EVisibility::Visible : EVisibility::Collapsed; + return bHasComponentsToShow ? EVisibility::Visible : EVisibility::Collapsed; } EVisibility SActorDetails::GetUCSComponentWarningVisibility() const diff --git a/Engine/Source/Editor/LevelEditor/Private/SActorDetails.h b/Engine/Source/Editor/LevelEditor/Private/SActorDetails.h index 986f552f227e..522ef7023873 100644 --- a/Engine/Source/Editor/LevelEditor/Private/SActorDetails.h +++ b/Engine/Source/Editor/LevelEditor/Private/SActorDetails.h @@ -43,8 +43,6 @@ public: /** FEditorUndoClient Interface */ virtual void PostUndo(bool bSuccess) override; virtual void PostRedo(bool bSuccess) override; - - static LEVELEDITOR_API IConsoleVariable* ShowComponents; /** * Sets the filter that should be used to filter incoming actors in or out of the details panel @@ -53,11 +51,13 @@ public: */ void SetActorDetailsRootCustomization(TSharedPtr ActorDetailsObjectFilter, TSharedPtr ActorDetailsRootCustomization); + /** Sets the UI customization of the SCSEditor inside this details panel. */ + void SetSCSEditorUICustomization(TSharedPtr ActorDetailsSCSEditorUICustomization); + private: AActor* GetSelectedActorInEditor() const; AActor* GetActorContext() const; bool GetAllowComponentTreeEditing() const; - const TArray>& GetDetailsViewSelectedObjects() const; void OnComponentsEditedInWorld(); void OnEditorSelectionChanged(UObject* Object); @@ -84,7 +84,6 @@ private: TSharedPtr DetailsView; TSharedPtr ComponentsBox; TSharedPtr SCSEditor; - TSharedPtr ExtensionPanel; // The actor selected when the details panel was locked TWeakObjectPtr LockedActorSelection; diff --git a/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp b/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp index 96931bf2bd19..fa375c8f6ff2 100644 --- a/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp @@ -1683,18 +1683,11 @@ void SLevelEditor::HandleEditorMapChange( uint32 MapChangeFlags ) void SLevelEditor::OnActorSelectionChanged(const TArray& NewSelection, bool bForceRefresh) { - for( auto It = AllActorDetailPanels.CreateIterator(); It; ++It ) + for (TSharedRef ActorDetails : GetAllActorDetails()) { - TSharedPtr ActorDetails = It->Pin(); - if( ActorDetails.IsValid() ) - { - ActorDetails->SetObjects(NewSelection, bForceRefresh || bNeedsRefresh); - } - else - { - // remove stray entries here - } + ActorDetails->SetObjects(NewSelection, bForceRefresh || bNeedsRefresh); } + bNeedsRefresh = false; } @@ -1798,20 +1791,58 @@ TSharedRef SLevelEditor::CreateActorDetails( const FName TabIdentifier ActorDetails->SetObjects( SelectedActors, bForceRefresh ); } + ActorDetails->SetActorDetailsRootCustomization(ActorDetailsObjectFilter, ActorDetailsRootCustomization); + ActorDetails->SetSCSEditorUICustomization(ActorDetailsSCSEditorUICustomization); + AllActorDetailPanels.Add( ActorDetails ); return ActorDetails; } - -void SLevelEditor::SetActorDetailsRootCustomization(TSharedPtr ActorDetailsObjectFilter, TSharedPtr ActorDetailsRootCustomization) +TArray> SLevelEditor::GetAllActorDetails() const { - for (TWeakPtr Details : AllActorDetailPanels) + TArray> AllValidActorDetails; + AllValidActorDetails.Reserve(AllActorDetailPanels.Num()); + + for (TWeakPtr ActorDetails : AllActorDetailPanels) { - if (TSharedPtr DetailsPinned = Details.Pin()) + if (TSharedPtr ActorDetailsPinned = ActorDetails.Pin()) { - DetailsPinned->SetActorDetailsRootCustomization(ActorDetailsObjectFilter, ActorDetailsRootCustomization); + AllValidActorDetails.Add(ActorDetailsPinned.ToSharedRef()); } } + + if (AllActorDetailPanels.Num() > AllValidActorDetails.Num()) + { + TArray>& AllActorDetailPanelsNonConst = const_cast>&>(AllActorDetailPanels); + AllActorDetailPanelsNonConst.Reset(AllValidActorDetails.Num()); + for (const TSharedRef& ValidActorDetails : AllValidActorDetails) + { + AllActorDetailPanelsNonConst.Add(ValidActorDetails); + } + } + + return AllValidActorDetails; +} + +void SLevelEditor::SetActorDetailsRootCustomization(TSharedPtr InActorDetailsObjectFilter, TSharedPtr InActorDetailsRootCustomization) +{ + ActorDetailsObjectFilter = InActorDetailsObjectFilter; + ActorDetailsRootCustomization = InActorDetailsRootCustomization; + + for (TSharedRef ActorDetails : GetAllActorDetails()) + { + ActorDetails->SetActorDetailsRootCustomization(ActorDetailsObjectFilter, ActorDetailsRootCustomization); + } +} + +void SLevelEditor::SetActorDetailsSCSEditorUICustomization(TSharedPtr InActorDetailsSCSEditorUICustomization) +{ + ActorDetailsSCSEditorUICustomization = InActorDetailsSCSEditorUICustomization; + + for (TSharedRef ActorDetails : GetAllActorDetails()) + { + ActorDetails->SetSCSEditorUICustomization(ActorDetailsSCSEditorUICustomization); + } } TSharedRef SLevelEditor::CreateToolBox() diff --git a/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.h b/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.h index 14273375fc4a..63eca427b209 100644 --- a/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.h +++ b/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.h @@ -121,7 +121,8 @@ public: virtual void OnToolkitHostingFinished( const TSharedRef< class IToolkit >& Toolkit ) override; virtual UWorld* GetWorld() const override; virtual TSharedRef CreateActorDetails( const FName TabIdentifier ) override; - virtual void SetActorDetailsRootCustomization(TSharedPtr ActorDetailsObjectFilter, TSharedPtr ActorDetailsRootCustomization) override; + virtual void SetActorDetailsRootCustomization(TSharedPtr InActorDetailsObjectFilter, TSharedPtr InActorDetailsRootCustomization) override; + virtual void SetActorDetailsSCSEditorUICustomization(TSharedPtr InActorDetailsSCSEditorUICustomization) override; virtual TSharedRef CreateToolBox() override; virtual FEditorModeTools& GetEditorModeManager() const override; @@ -228,6 +229,10 @@ private: /** Registers toolbar options for the level editor status bar */ void RegisterStatusBarTools(); + + /** @return All valid actor details panels */ + TArray> GetAllActorDetails() const; + private: // Tracking the active viewports in this level editor. @@ -285,6 +290,15 @@ private: /** Handle to the registered OnLevelActorOuterChanged delegate */ FDelegateHandle LevelActorOuterChangedHandle; + + /** Actor details object filters */ + TSharedPtr ActorDetailsObjectFilter; + + /** Actor details root customization */ + TSharedPtr ActorDetailsRootCustomization; + + /** Actor details SCS editor customization */ + TSharedPtr ActorDetailsSCSEditorUICustomization; /** If this flag is raised we will force refresh on next selection update. */ bool bNeedsRefresh : 1; diff --git a/Engine/Source/Editor/LevelEditor/Public/ActorDetailsExtensionContext.h b/Engine/Source/Editor/LevelEditor/Public/ActorDetailsExtensionContext.h deleted file mode 100644 index 23307272aad5..000000000000 --- a/Engine/Source/Editor/LevelEditor/Public/ActorDetailsExtensionContext.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "ActorDetailsExtensionContext.generated.h" - -UCLASS() -class LEVELEDITOR_API UActorDetailsExtensionContext : public UObject -{ - GENERATED_BODY() - -public: - const TArray>& GetSelectedObjects() const; - -private: - friend class SActorDetails; - - DECLARE_DELEGATE_RetVal(const TArray>&, FGetSelectedObjects); - FGetSelectedObjects GetSelectedObjectsDelegate; -}; diff --git a/Engine/Source/Editor/LevelEditor/Public/ILevelEditor.h b/Engine/Source/Editor/LevelEditor/Public/ILevelEditor.h index e7449542cc7f..f7d23327895d 100644 --- a/Engine/Source/Editor/LevelEditor/Public/ILevelEditor.h +++ b/Engine/Source/Editor/LevelEditor/Public/ILevelEditor.h @@ -63,6 +63,9 @@ public: /** Set the filter that should be used to determine the set of objects that should be shown in a details panel when an actor in the level editor is selected */ virtual void SetActorDetailsRootCustomization(TSharedPtr ActorDetailsObjectFilter, TSharedPtr ActorDetailsRootCustomization) = 0; + /** Sets the UI customization of the SCSEditor inside the level editor details panel. */ + virtual void SetActorDetailsSCSEditorUICustomization(TSharedPtr ActorDetailsSCSEditorUICustomization) = 0; + /** Spawns a level editor ToolBox widget (aka. "Modes") */ virtual TSharedRef CreateToolBox() = 0; diff --git a/Engine/Source/Editor/LevelEditor/Public/LevelEditorActions.h b/Engine/Source/Editor/LevelEditor/Public/LevelEditorActions.h index e6f2288b30a2..1c0503995bfa 100644 --- a/Engine/Source/Editor/LevelEditor/Public/LevelEditorActions.h +++ b/Engine/Source/Editor/LevelEditor/Public/LevelEditorActions.h @@ -580,14 +580,13 @@ public: TSharedPtr< FUICommandInfo > MaterialQualityLevel_Low; TSharedPtr< FUICommandInfo > MaterialQualityLevel_Medium; TSharedPtr< FUICommandInfo > MaterialQualityLevel_High; + TSharedPtr< FUICommandInfo > MaterialQualityLevel_Epic; TSharedPtr< FUICommandInfo > ToggleFeatureLevelPreview; TSharedPtr< FUICommandInfo > PreviewPlatformOverride_SM5; - TSharedPtr< FUICommandInfo > PreviewPlatformOverride_AndroidGLES31; - TSharedPtr< FUICommandInfo > PreviewPlatformOverride_AndroidVulkanES31; - TSharedPtr< FUICommandInfo > PreviewPlatformOverride_AndroidVulkanSM5; - TSharedPtr< FUICommandInfo > PreviewPlatformOverride_IOSMetalES31; + + TMap > PreviewPlatformOverrides; ///** // * Mode Commands diff --git a/Engine/Source/Editor/MainFrame/Private/Frame/MainFrameHandler.cpp b/Engine/Source/Editor/MainFrame/Private/Frame/MainFrameHandler.cpp index dc339312846b..1bfd2f03c936 100644 --- a/Engine/Source/Editor/MainFrame/Private/Frame/MainFrameHandler.cpp +++ b/Engine/Source/Editor/MainFrame/Private/Frame/MainFrameHandler.cpp @@ -4,6 +4,7 @@ #include "HAL/FileManager.h" #include "ThumbnailRendering/ThumbnailManager.h" #include "Frame/RootWindowLocation.h" +#include "Framework/Application/SlateApplication.h" #include "Widgets/Docking/SDockTab.h" #include "HAL/PlatformApplicationMisc.h" @@ -27,7 +28,8 @@ void FMainFrameHandler::ShutDownEditor() TSharedPtr RootWindow = RootWindowPtr.Pin(); // Save root window placement so we can restore it. - if (!GUsingNullRHI && RootWindow.IsValid()) + bool bRenderOffScreen = FSlateApplication::Get().IsRenderingOffScreen(); + if (!GUsingNullRHI && !bRenderOffScreen && RootWindow.IsValid()) { FSlateRect WindowRect = RootWindow->GetNonMaximizedRectInScreen(); diff --git a/Engine/Source/Editor/MaterialEditor/Private/FindInMaterial.cpp b/Engine/Source/Editor/MaterialEditor/Private/FindInMaterial.cpp index a963a09253b4..08579461f8c3 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/FindInMaterial.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/FindInMaterial.cpp @@ -171,6 +171,7 @@ void SFindInMaterial::Construct(const FArguments& InArgs, TSharedPtr &Tokens) UEdGraphNode* Node = *It; const FString NodeName = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); - FSearchResult NodeResult(new FFindInMaterialResult(NodeName, RootSearchResult, Node)); + const FString NodeType = Node->GetNodeTitle(ENodeTitleType::ListView).ToString(); + FSearchResult NodeResult(new FFindInMaterialResult(NodeName == NodeType ? NodeName : NodeName + " - " + NodeType, RootSearchResult, Node)); - FString NodeSearchString = NodeName + Node->NodeComment; + FString NodeSearchString = NodeName + NodeType + Node->NodeComment; NodeSearchString = NodeSearchString.Replace(TEXT(" "), TEXT("")); bool bNodeMatchesSearch = StringMatchesSearchTokens(Tokens, NodeSearchString); @@ -419,6 +421,14 @@ void SFindInMaterial::OnTreeSelectionChanged(FSearchResult Item, ESelectInfo::Ty } } +void SFindInMaterial::OnTreeSelectionDoubleClick(FSearchResult Item) +{ + if (Item.IsValid()) + { + Item->OnClick(MaterialEditorPtr); + } +} + bool SFindInMaterial::StringMatchesSearchTokens(const TArray& Tokens, const FString& ComparisonString) { bool bFoundAllTokens = true; diff --git a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp index 221570e836d5..37921b1f8a74 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp @@ -30,6 +30,7 @@ #include "Materials/MaterialInstanceDynamic.h" #include "Materials/MaterialInstanceConstant.h" #include "Materials/MaterialParameterCollection.h" +#include "StaticParameterSet.h" #include "Engine/TextureCube.h" #include "Engine/Texture2DArray.h" #include "Dialogs/Dialogs.h" @@ -61,6 +62,7 @@ #include "Materials/MaterialExpressionRuntimeVirtualTextureSampleParameter.h" #include "Materials/MaterialExpressionScalarParameter.h" #include "Materials/MaterialExpressionStaticComponentMaskParameter.h" +#include "Materials/MaterialExpressionStaticSwitchParameter.h" #include "Materials/MaterialExpressionTextureSampleParameter.h" #include "Materials/MaterialExpressionTextureObjectParameter.h" #include "Materials/MaterialExpressionTextureObject.h" @@ -436,6 +438,7 @@ void FMaterialEditor::InitMaterialEditor( const EToolkitMode::Type Mode, const T NodeQualityLevel = EMaterialQualityLevel::Num; NodeFeatureLevel = ERHIFeatureLevel::Num; + bPreviewStaticSwitches = false; bPreviewFeaturesChanged = true; // Register our commands. This will only register them if not previously registered @@ -1233,7 +1236,15 @@ void FMaterialEditor::RegisterToolBar() Section.AddEntry(FToolMenuEntry::InitComboButton( "HideUnrelatedNodesOptions", FUIAction(), - FOnGetContent::CreateSP(this, &FMaterialEditor::MakeHideUnrelatedNodesOptionsMenu), + FNewToolMenuDelegate::CreateLambda([](UToolMenu* InSubMenu) + { + UMaterialEditorMenuContext* SubMenuContext = InSubMenu->FindContext(); + if (SubMenuContext && SubMenuContext->MaterialEditor.IsValid()) + { + TSharedPtr MaterialEditor = StaticCastSharedPtr(SubMenuContext->MaterialEditor.Pin()); + MaterialEditor->MakeHideUnrelatedNodesOptionsMenu(InSubMenu); + } + }), LOCTEXT("HideUnrelatedNodesOptions", "Hide Unrelated Nodes Options"), LOCTEXT("HideUnrelatedNodesOptionsMenu", "Hide Unrelated Nodes options menu"), TAttribute(), @@ -1248,7 +1259,15 @@ void FMaterialEditor::RegisterToolBar() Section.AddEntry(FToolMenuEntry::InitComboButton( "NodePreview", FUIAction(), - FOnGetContent::CreateSP(this, &FMaterialEditor::GeneratePreviewMenuContent), + FNewToolMenuDelegate::CreateLambda([](UToolMenu* InSubMenu) + { + UMaterialEditorMenuContext* SubMenuContext = InSubMenu->FindContext(); + if (SubMenuContext && SubMenuContext->MaterialEditor.IsValid()) + { + TSharedPtr MaterialEditor = StaticCastSharedPtr(SubMenuContext->MaterialEditor.Pin()); + MaterialEditor->GeneratePreviewMenuContent(InSubMenu); + } + }), LOCTEXT("NodePreview_Label", "Preview Nodes"), LOCTEXT("NodePreviewToolTip", "Preview the nodes for a given feature level and/or material quality."), FSlateIcon(FEditorStyle::GetStyleSetName(), "FullBlueprintEditor.SwitchToScriptingMode"), @@ -1401,32 +1420,53 @@ void FMaterialEditor::GenerateInheritanceMenu(UToolMenu* Menu) } } -TSharedRef< SWidget > FMaterialEditor::GeneratePreviewMenuContent() +void FMaterialEditor::GeneratePreviewMenuContent(UToolMenu* Menu) { - bool bShouldCloseWindowAfterMenuSelection = true; - FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, GetToolkitCommands()); + Menu->bShouldCloseWindowAfterMenuSelection = true; - MenuBuilder.BeginSection("MaterialEditorQualityPreview", LOCTEXT("MaterialQualityHeading", "Quality Level")); + FToolMenuSection& QualityLevelSection = Menu->AddSection("MaterialEditorQualityPreview", LOCTEXT("MaterialQualityHeading", "Quality Level")); { - MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_All); - MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_High); - MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Medium); - MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Low); - } - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("MaterialEditorFeaturePreview", LOCTEXT("MaterialFeatureHeading", "Feature Level")); - { - MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_All); - MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_ES31); - MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_SM4); - MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_SM5); + QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_All); + QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Epic); + QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_High); + QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Medium); + QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Low); } - MenuBuilder.EndSection(); + FToolMenuSection& FeatureLevelSection = Menu->AddSection("MaterialEditorFeaturePreview", LOCTEXT("MaterialFeatureHeading", "Feature Level")); + { + FeatureLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_All); + FeatureLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_ES31); + FeatureLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_SM5); + } - return MenuBuilder.MakeWidget(); + FToolMenuSection& StaticSwitchSection = Menu->AddSection("StaticSwitchPreview", LOCTEXT("StaticSwitchHeading", "Switch Params")); + TSharedRef StaticSwitchCheckbox = SNew(SBox) + [ + SNew(SCheckBox) + .IsChecked(bPreviewStaticSwitches ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged_Lambda( + [this](ECheckBoxState NewState) + { + bPreviewStaticSwitches = (NewState == ECheckBoxState::Checked); + bPreviewFeaturesChanged = true; + } + ) + .Style(FEditorStyle::Get(), "Menu.CheckBox") + .ToolTipText(LOCTEXT("StaticSwitchCheckBoxToolTip", "Hide disabled nodes in the graph, according to switch params.")) + .Content() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("DisableInactiveSwitch", "Hide Disabled")) + ] + ] + ]; + StaticSwitchSection.AddEntry(FToolMenuEntry::InitMenuEntry("StaticSwitchCheckbox", FUIAction(), StaticSwitchCheckbox)); } UMaterialInterface* FMaterialEditor::GetMaterialInterface() const @@ -1925,7 +1965,8 @@ bool FMaterialEditor::UpdateOriginalMaterial() for (int32 i = ERHIFeatureLevel::SM5; i >= 0; --i) { ERHIFeatureLevel::Type FeatureLevel = (ERHIFeatureLevel::Type)i; - if( Material->GetMaterialResource(FeatureLevel)->GetCompileErrors().Num() > 0 ) + FMaterialResource* CurrentResource = Material->GetMaterialResource(FeatureLevel); + if(CurrentResource && CurrentResource->GetCompileErrors().Num() > 0 ) { FString FeatureLevelName; GetFeatureLevelName(FeatureLevel, FeatureLevelName); @@ -2203,7 +2244,11 @@ void FMaterialEditor::UpdateMaterialinfoList_Old() else { // Add a compile error message for functions missing an output - CompileErrors = ExpressionPreviewMaterial->GetMaterialResource(FeatureLevel)->GetCompileErrors(); + FMaterialResource* CurrentResource = ExpressionPreviewMaterial->GetMaterialResource(FeatureLevel); + if (CurrentResource) + { + CompileErrors = CurrentResource->GetCompileErrors(); + } bool bFoundFunctionOutput = false; for (int32 ExpressionIndex = 0; ExpressionIndex < Material->Expressions.Num(); ExpressionIndex++) @@ -2466,10 +2511,25 @@ void FMaterialEditor::UpdateGraphNodeStates() TArray VisibleExpressions; + FStaticParameterSet StaticSwitchSet; + if (bPreviewFeaturesChanged && bPreviewStaticSwitches) + { + for (UMaterialExpression* Expression : Material->Expressions) + { + if (UMaterialExpressionStaticSwitchParameter* StaticSwitch = Cast(Expression)) + { + FStaticSwitchParameter SwitchParam; + SwitchParam.Value = StaticSwitch->DefaultValue; + SwitchParam.ExpressionGUID = StaticSwitch->ExpressionGUID; + StaticSwitchSet.StaticSwitchParameters.Add(SwitchParam); + } + } + } + if (bPreviewFeaturesChanged) { - Material->GetAllReferencedExpressions(VisibleExpressions, nullptr, NodeFeatureLevel, NodeQualityLevel); - if (NodeFeatureLevel != ERHIFeatureLevel::Num || NodeQualityLevel != EMaterialQualityLevel::Num) + Material->GetAllReferencedExpressions(VisibleExpressions, StaticSwitchSet.IsEmpty() ? nullptr : &StaticSwitchSet, NodeFeatureLevel, NodeQualityLevel); + if (NodeFeatureLevel != ERHIFeatureLevel::Num || NodeQualityLevel != EMaterialQualityLevel::Num || !StaticSwitchSet.IsEmpty()) { bShowAllNodes = false; } @@ -2482,7 +2542,7 @@ void FMaterialEditor::UpdateGraphNodeStates() if (MaterialNode) { MaterialNode->bIsPreviewExpression = (PreviewExpression == MaterialNode->MaterialExpression); - MaterialNode->bIsErrorExpression = (ErrorMaterialResource->GetErrorExpressions().Find(MaterialNode->MaterialExpression) != INDEX_NONE); + MaterialNode->bIsErrorExpression = (ErrorMaterialResource != nullptr) && (ErrorMaterialResource->GetErrorExpressions().Find(MaterialNode->MaterialExpression) != INDEX_NONE); if (MaterialNode->bIsErrorExpression && !MaterialNode->bHasCompilerMessage) { @@ -2665,6 +2725,11 @@ void FMaterialEditor::BindCommands() FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::Num), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsQualityPreviewChecked, EMaterialQualityLevel::Num)); + ToolkitCommands->MapAction( + Commands.QualityLevel_Epic, + FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::Epic), + FCanExecuteAction(), + FIsActionChecked::CreateSP(this, &FMaterialEditor::IsQualityPreviewChecked, EMaterialQualityLevel::Epic)); ToolkitCommands->MapAction( Commands.QualityLevel_High, FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::High), @@ -2879,23 +2944,9 @@ void FMaterialEditor::HideUnrelatedNodes() GraphEditor->FocusCommentNodes(CommentNodes, RelatedNodes); } -TSharedRef FMaterialEditor::MakeHideUnrelatedNodesOptionsMenu() +void FMaterialEditor::MakeHideUnrelatedNodesOptionsMenu(UToolMenu* Menu) { - const bool bShouldCloseWindowAfterMenuSelection = true; - FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, GetToolkitCommands() ); - - TSharedRef OptionsHeading = SNew(SBox) - .Padding(2.0f) - [ - SNew(SHorizontalBox) - - +SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(LOCTEXT("HideUnrelatedOptions", "Hide Unrelated Options")) - .TextStyle(FEditorStyle::Get(), "Menu.Heading") - ] - ]; + Menu->bShouldCloseWindowAfterMenuSelection = true; TSharedRef LockNodeStateCheckBox = SNew(SBox) [ @@ -2937,13 +2988,9 @@ TSharedRef FMaterialEditor::MakeHideUnrelatedNodesOptionsMenu() ] ]; - MenuBuilder.AddWidget(OptionsHeading, FText::GetEmpty(), true); - - MenuBuilder.AddMenuEntry(FUIAction(), LockNodeStateCheckBox); - - MenuBuilder.AddMenuEntry(FUIAction(), FocusWholeChainCheckBox); - - return MenuBuilder.MakeWidget(); + FToolMenuSection& OptionsSection = Menu->AddSection("OptionsSection", LOCTEXT("HideUnrelatedOptions", "Hide Unrelated Options")); + OptionsSection.AddEntry(FToolMenuEntry::InitMenuEntry("LockNodeStateCheckBox", FUIAction(), LockNodeStateCheckBox)); + OptionsSection.AddEntry(FToolMenuEntry::InitMenuEntry("FocusWholeChainCheckBox", FUIAction(), FocusWholeChainCheckBox)); } void FMaterialEditor::OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState) @@ -4137,6 +4184,44 @@ void FMaterialEditor::AddToSelection(UMaterialExpression* Expression) GraphEditor->SetNodeSelection(Expression->GraphNode, true); } +void FMaterialEditor::JumpToExpression(UMaterialExpression* Expression) +{ + check(Expression); + UEdGraphNode* ExpressionNode = nullptr; + + // Note: 'Expression' may be from a serialized material with no graph, we compare to our material with a graph if this occurs + if (Expression->GraphNode) + { + ExpressionNode = Expression->GraphNode; + } + else if (Expression->bIsParameterExpression) + { + TArray* GraphExpressions = Material->EditorParameters.Find(Expression->GetParameterName()); + if (GraphExpressions && GraphExpressions->Num() == 1) + { + ExpressionNode = GraphExpressions->Last()->GraphNode; + } + else + { + UMaterialExpressionParameter* GraphExpression = Material->FindExpressionByGUID(Expression->GetParameterExpressionId()); + ExpressionNode = GraphExpression ? GraphExpression->GraphNode : nullptr; + } + } + else if (UMaterialExpressionFunctionOutput* ExpressionOutput = Cast(Expression)) + { + TArray FunctionOutputExpressions; + Material->GetAllFunctionOutputExpressions(FunctionOutputExpressions); + UMaterialExpressionFunctionOutput** GraphExpression = FunctionOutputExpressions.FindByPredicate( + [&](const UMaterialExpressionFunctionOutput* GraphExpressionOutput) + { + return GraphExpressionOutput->Id == ExpressionOutput->Id; + }); + ExpressionNode = GraphExpression ? (*GraphExpression)->GraphNode : nullptr; + } + + JumpToNode(ExpressionNode); +} + void FMaterialEditor::SelectAllNodes() { GraphEditor->SelectAllNodes(); @@ -4527,6 +4612,12 @@ void FMaterialEditor::UpdateMaterialAfterGraphChange() RefreshExpressionPreviews(); SetMaterialDirty(); UpdateGenerator(); + + if (NodeFeatureLevel != ERHIFeatureLevel::Num || NodeQualityLevel != EMaterialQualityLevel::Num || bPreviewStaticSwitches) + { + bPreviewFeaturesChanged = true; + } + if (bHideUnrelatedNodes && !bLockNodeFadeState && bSelectRegularNode) { GraphEditor->ResetAllNodesUnrelatedStates(); @@ -4739,6 +4830,12 @@ void FMaterialEditor::NotifyPostChange( const FPropertyChangedEvent& PropertyCha UpdatePreviewViewportsVisibility(); } + else if (bPreviewStaticSwitches && + PropertyThatChanged->GetOwnerClass() == UMaterialExpressionStaticBoolParameter::StaticClass() && + NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterialExpressionStaticBoolParameter, DefaultValue)) + { + bPreviewFeaturesChanged = true; + } FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); diff --git a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.h b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.h index e41b53086736..c98e9083ad01 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.h +++ b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.h @@ -51,7 +51,7 @@ public: { // Register this FMaterial derivative with AddEditorLoadedMaterialResource since it does not have a corresponding UMaterialInterface FMaterial::AddEditorLoadedMaterialResource(this); - SetQualityLevelProperties(EMaterialQualityLevel::High, false, GMaxRHIFeatureLevel); + SetQualityLevelProperties(GMaxRHIFeatureLevel); } FMatExpressionPreview(UMaterialExpression* InExpression) @@ -64,7 +64,7 @@ public: check(InExpression->Material && InExpression->Material->Expressions.Contains(InExpression)); ReferencedTextures = InExpression->Material->GetReferencedTextures(); - SetQualityLevelProperties(EMaterialQualityLevel::High, false, GMaxRHIFeatureLevel); + SetQualityLevelProperties(GMaxRHIFeatureLevel); } ~FMatExpressionPreview() @@ -384,6 +384,7 @@ public: virtual UMaterialExpressionComment* CreateNewMaterialExpressionComment(const FVector2D& NodePos) override; virtual void ForceRefreshExpressionPreviews() override; virtual void AddToSelection(UMaterialExpression* Expression) override; + virtual void JumpToExpression(UMaterialExpression* Expression) override; virtual void DeleteSelectedNodes() override; virtual FText GetOriginalObjectName() const override; virtual void UpdateMaterialAfterGraphChange() override; @@ -563,9 +564,7 @@ private: /** Creates the toolbar buttons. Bound by ExtendToolbar*/ void FillToolbar(FToolBarBuilder& ToolbarBuilder); - TSharedRef GenerateInheritanceMenu(); - - TSharedRef< SWidget > GeneratePreviewMenuContent(); + void GeneratePreviewMenuContent(class UToolMenu* Menu); /** Allows editor to veto the setting of a preview asset */ virtual bool ApproveSetPreviewAsset(UObject* InAsset) override; @@ -639,7 +638,7 @@ private: void HideUnrelatedNodes(); /** Make a drop down menu to control the opacity of unrelated nodes */ - TSharedRef MakeHideUnrelatedNodesOptionsMenu(); + void MakeHideUnrelatedNodesOptionsMenu(class UToolMenu* Menu); TOptional HandleUnrelatedNodesOpacityBoxValue() const; void HandleUnrelatedNodesOpacityBoxChanged(float NewOpacity); void OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState); @@ -893,6 +892,9 @@ private: /** Stores the feature level used to preview the material graph */ ERHIFeatureLevel::Type NodeFeatureLevel; + /** True if we want to preview static switches, disabling inactive nodes in the graph */ + bool bPreviewStaticSwitches; + /** True if the quality level or feature level to preview has been changed */ bool bPreviewFeaturesChanged; diff --git a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorActions.cpp b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorActions.cpp index e13cf2ea335e..c275507f0c74 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorActions.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorActions.cpp @@ -57,13 +57,13 @@ void FMaterialEditorCommands::RegisterCommands() UI_COMMAND( PromoteToParameter, "Promote to Parameter", "Promote selected Pin to parameter of pin type", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(QualityLevel_All, "All", "Sets node preview to show all quality levels.)", EUserInterfaceActionType::RadioButton, FInputChord()); + UI_COMMAND(QualityLevel_Epic, "Epic", "Sets node preview to Epic quality.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(QualityLevel_High, "High", "Sets node preview to high quality.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(QualityLevel_Medium, "Medium", "Sets node preview to medium quality.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(QualityLevel_Low, "Low", "Sets node preview to low quality.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(FeatureLevel_All, "All", "Sets node preview to show all feature levels.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(FeatureLevel_ES31, "ES3.1", "Sets node preview to show the ES3.1 feature level.", EUserInterfaceActionType::RadioButton, FInputChord()); - UI_COMMAND(FeatureLevel_SM4, "SM4", "Sets node preview to show the SM4 feature level.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(FeatureLevel_SM5, "SM5", "Sets node preview to show the SM5 feature level.", EUserInterfaceActionType::RadioButton, FInputChord()); } diff --git a/Engine/Source/Editor/MaterialEditor/Private/MaterialStats.cpp b/Engine/Source/Editor/MaterialEditor/Private/MaterialStats.cpp index d2459da524fd..ad8bd77ba806 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/MaterialStats.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/MaterialStats.cpp @@ -131,7 +131,7 @@ void FShaderPlatformSettings::AllocateMaterialResources() for (int32 QualityLevelIndex = 0; QualityLevelIndex < EMaterialQualityLevel::Num; QualityLevelIndex++) { PlatformData[QualityLevelIndex].MaterialResourcesStats = new FMaterialResourceStats(); - PlatformData[QualityLevelIndex].MaterialResourcesStats->SetMaterial(Material, (EMaterialQualityLevel::Type)QualityLevelIndex, true, (ERHIFeatureLevel::Type)TargetFeatureLevel, MaterialInstance); + PlatformData[QualityLevelIndex].MaterialResourcesStats->SetMaterial(Material, MaterialInstance, (ERHIFeatureLevel::Type)TargetFeatureLevel, (EMaterialQualityLevel::Type)QualityLevelIndex); } } diff --git a/Engine/Source/Editor/MaterialEditor/Private/MaterialStatsCommon.cpp b/Engine/Source/Editor/MaterialEditor/Private/MaterialStatsCommon.cpp index 7babbd2d129d..6099a935d536 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/MaterialStatsCommon.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/MaterialStatsCommon.cpp @@ -52,6 +52,9 @@ FString FMaterialStatsUtils::MaterialQualityToString(const EMaterialQualityLevel case EMaterialQualityLevel::Low: StrQuality = TEXT("Low Quality"); break; + case EMaterialQualityLevel::Epic: + StrQuality = TEXT("Epic Quality"); + break; } return StrQuality; @@ -72,6 +75,9 @@ FString FMaterialStatsUtils::MaterialQualityToShortString(const EMaterialQuality case EMaterialQualityLevel::Low: StrQuality = TEXT("Low"); break; + case EMaterialQualityLevel::Epic: + StrQuality = TEXT("Epic"); + break; } return StrQuality; @@ -91,6 +97,10 @@ EMaterialQualityLevel::Type FMaterialStatsUtils::StringToMaterialQuality(const F { return EMaterialQualityLevel::Low; } + else if (StrQuality.Equals(TEXT("Epic Quality"))) + { + return EMaterialQualityLevel::Epic; + } return EMaterialQualityLevel::Num; } @@ -328,7 +338,9 @@ FLinearColor FMaterialStatsUtils::QualitySettingColor(const EMaterialQualityLeve case EMaterialQualityLevel::Medium: return YellowColor; break; - + case EMaterialQualityLevel::Epic: + return FLinearColor::Red; + break; default: return FLinearColor::Black; break; diff --git a/Engine/Source/Editor/MaterialEditor/Private/SMaterialLayersFunctionsTree.cpp b/Engine/Source/Editor/MaterialEditor/Private/SMaterialLayersFunctionsTree.cpp index 7700d0835ad7..4b5bbfd2ed15 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/SMaterialLayersFunctionsTree.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/SMaterialLayersFunctionsTree.cpp @@ -560,6 +560,7 @@ void SMaterialLayersFunctionsInstanceTreeItem::Construct(const FArguments& InArg // END ASSET // PROPERTY ---------------------------------------------- + bool bisPaddedProperty = false; if (StackParameterData->StackDataType == EStackDataType::Property) { @@ -810,32 +811,7 @@ void SMaterialLayersFunctionsInstanceTreeItem::Construct(const FArguments& InArg } } } - else if (!CompMaskParam) - { - FNodeWidgets StoredNodeWidgets = Node.CreateNodeWidgets(); - TSharedRef StoredRightSideWidget = StoredNodeWidgets.ValueWidget.ToSharedRef(); - FDetailWidgetRow& CustomWidget = Row.CustomWidget(); - CustomWidget - .FilterString(NameOverride) - .NameContent() - [ - SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(NameOverride) - .ToolTipText(FMaterialPropertyHelpers::GetParameterExpressionDescription(StackParameterData->Parameter, MaterialEditorInstance)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - .ValueContent() - [ - StoredRightSideWidget - ]; - - } - else + else if (CompMaskParam) { TSharedPtr RMaskProperty = StackParameterData->ParameterNode->CreatePropertyHandle()->GetChildHandle("R"); TSharedPtr GMaskProperty = StackParameterData->ParameterNode->CreatePropertyHandle()->GetChildHandle("G"); @@ -918,6 +894,27 @@ void SMaterialLayersFunctionsInstanceTreeItem::Construct(const FArguments& InArg ] ]; } + else + { + FDetailWidgetDecl* CustomNameWidget = Row.CustomNameWidget(); + if (CustomNameWidget) + { + (*CustomNameWidget) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(NameOverride) + .ToolTipText(FMaterialPropertyHelpers::GetParameterExpressionDescription(StackParameterData->Parameter, MaterialEditorInstance)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + } + + bisPaddedProperty = true; + } FNodeWidgets NodeWidgets = Node.CreateNodeWidgets(); LeftSideWidget = NodeWidgets.NameWidget.ToSharedRef(); @@ -945,6 +942,7 @@ void SMaterialLayersFunctionsInstanceTreeItem::Construct(const FArguments& InArg // END PROPERTY CHILD // FINAL WRAPPER + float ValuePadding = bisPaddedProperty ? 20.0f : 0.0f; if (StackParameterData->StackDataType == EStackDataType::Stack) { TSharedPtr FinalStack; @@ -1038,8 +1036,8 @@ void SMaterialLayersFunctionsInstanceTreeItem::Construct(const FArguments& InArg [ SNew(SHorizontalBox) + SHorizontalBox::Slot() - .MaxWidth(350.0f) - .Padding(FMargin(5.0f, 2.0f, 0.0f, 2.0f)) + .MaxWidth(350.0f - ValuePadding) + .Padding(FMargin(5.0f, 2.0f, ValuePadding, 2.0f)) [ RightSideWidget ] @@ -2097,6 +2095,7 @@ void SMaterialLayersFunctionsMaterialTreeItem::Construct(const FArguments& InArg // END ASSET // PROPERTY ---------------------------------------------- + bool bisPaddedProperty = false; if (StackParameterData->StackDataType == EStackDataType::Property) { @@ -2309,32 +2308,7 @@ void SMaterialLayersFunctionsMaterialTreeItem::Construct(const FArguments& InArg } } } - else if (!CompMaskParam) - { - FNodeWidgets StoredNodeWidgets = Node.CreateNodeWidgets(); - TSharedRef StoredRightSideWidget = StoredNodeWidgets.ValueWidget.ToSharedRef(); - FDetailWidgetRow& CustomWidget = Row.CustomWidget(); - CustomWidget - .FilterString(NameOverride) - .NameContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(NameOverride) - .ToolTipText(FMaterialPropertyHelpers::GetParameterExpressionDescription(StackParameterData->Parameter, MaterialEditorInstance)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - .ValueContent() - [ - StoredRightSideWidget - ]; - - } - else + else if (CompMaskParam) { TSharedPtr RMaskProperty = StackParameterData->ParameterNode->CreatePropertyHandle()->GetChildHandle("R"); TSharedPtr GMaskProperty = StackParameterData->ParameterNode->CreatePropertyHandle()->GetChildHandle("G"); @@ -2417,6 +2391,27 @@ void SMaterialLayersFunctionsMaterialTreeItem::Construct(const FArguments& InArg ] ]; } + else + { + FDetailWidgetDecl* CustomNameWidget = Row.CustomNameWidget(); + if (CustomNameWidget) + { + (*CustomNameWidget) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(NameOverride) + .ToolTipText(FMaterialPropertyHelpers::GetParameterExpressionDescription(StackParameterData->Parameter, MaterialEditorInstance)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + } + + bisPaddedProperty = true; + } FNodeWidgets NodeWidgets = Node.CreateNodeWidgets(); LeftSideWidget = NodeWidgets.NameWidget.ToSharedRef(); @@ -2435,6 +2430,7 @@ void SMaterialLayersFunctionsMaterialTreeItem::Construct(const FArguments& InArg // FINAL WRAPPER + float ValuePadding = bisPaddedProperty ? 20.0f : 0.0f; LeftSideWidget->SetEnabled(false); RightSideWidget->SetEnabled(false); if (StackParameterData->StackDataType == EStackDataType::Stack) @@ -2519,8 +2515,8 @@ void SMaterialLayersFunctionsMaterialTreeItem::Construct(const FArguments& InArg [ SNew(SHorizontalBox) + SHorizontalBox::Slot() - .MaxWidth(350.0f) - .Padding(FMargin(5.0f, 2.0f, 0.0f, 2.0f)) + .MaxWidth(350.0f - ValuePadding) + .Padding(FMargin(5.0f, 2.0f, ValuePadding, 2.0f)) [ RightSideWidget ] diff --git a/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.cpp b/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.cpp index 738657c49e48..ce5e794c4515 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.cpp @@ -96,6 +96,7 @@ void SMaterialParametersOverviewTreeItem::Construct(const FArguments& InArgs, co // END GROUP // PROPERTY ---------------------------------------------- + bool bisPaddedProperty = false; if (StackParameterData->StackDataType == EStackDataType::Property) { UDEditorStaticComponentMaskParameterValue* CompMaskParam = Cast(StackParameterData->Parameter); @@ -330,36 +331,7 @@ void SMaterialParametersOverviewTreeItem::Construct(const FArguments& InArgs, co } } } - else if (!CompMaskParam) - { - FNodeWidgets StoredNodeWidgets = Node.CreateNodeWidgets(); - TSharedRef StoredRightSideWidget = StoredNodeWidgets.ValueWidget.ToSharedRef(); - if (TSharedPtr PropertyHandle = StackParameterData->ParameterNode->CreatePropertyHandle()) - { - PropertyHandle->MarkResetToDefaultCustomized(true); - } - FDetailWidgetRow& CustomWidget = Row.CustomWidget(); - CustomWidget - .FilterString(NameOverride) - .NameContent() - [ - SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(NameOverride) - .ToolTipText(FMaterialPropertyHelpers::GetParameterExpressionDescription(StackParameterData->Parameter, MaterialEditorInstance)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - .ValueContent() - [ - StoredRightSideWidget - ]; - - } - else + else if (CompMaskParam) { TSharedPtr RMaskProperty = StackParameterData->ParameterNode->CreatePropertyHandle()->GetChildHandle("R"); TSharedPtr GMaskProperty = StackParameterData->ParameterNode->CreatePropertyHandle()->GetChildHandle("G"); @@ -442,6 +414,32 @@ void SMaterialParametersOverviewTreeItem::Construct(const FArguments& InArgs, co ] ]; } + else + { + if (TSharedPtr PropertyHandle = StackParameterData->ParameterNode->CreatePropertyHandle()) + { + PropertyHandle->MarkResetToDefaultCustomized(true); + } + + FDetailWidgetDecl* CustomNameWidget = Row.CustomNameWidget(); + if (CustomNameWidget) + { + (*CustomNameWidget) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(NameOverride) + .ToolTipText(FMaterialPropertyHelpers::GetParameterExpressionDescription(StackParameterData->Parameter, MaterialEditorInstance)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + } + + bisPaddedProperty = true; + } FNodeWidgets NodeWidgets = Node.CreateNodeWidgets(); LeftSideWidget = NodeWidgets.NameWidget.ToSharedRef(); @@ -460,6 +458,7 @@ void SMaterialParametersOverviewTreeItem::Construct(const FArguments& InArgs, co // FINAL WRAPPER { + float ValuePadding = bisPaddedProperty ? 20.0f : 0.0f; WrapperWidget->AddSlot() .AutoHeight() [ @@ -497,8 +496,8 @@ void SMaterialParametersOverviewTreeItem::Construct(const FArguments& InArgs, co [ SNew(SHorizontalBox) + SHorizontalBox::Slot() - .MaxWidth(350.0f) - .Padding(FMargin(5.0f, 2.0f, 0.0f, 2.0f)) + .MaxWidth(350.0f - ValuePadding) + .Padding(FMargin(5.0f, 2.0f, ValuePadding, 2.0f)) [ RightSideWidget ] diff --git a/Engine/Source/Editor/MaterialEditor/Public/FindInMaterial.h b/Engine/Source/Editor/MaterialEditor/Public/FindInMaterial.h index 4a0f645dc64d..c10ab8176ff9 100644 --- a/Engine/Source/Editor/MaterialEditor/Public/FindInMaterial.h +++ b/Engine/Source/Editor/MaterialEditor/Public/FindInMaterial.h @@ -98,6 +98,9 @@ protected: /** Called when user clicks on a new result */ void OnTreeSelectionChanged(FSearchResult Item, ESelectInfo::Type SelectInfo); + /** Called when user double clicks on a new result */ + void OnTreeSelectionDoubleClick(FSearchResult Item); + /** Called when a new row is being generated */ TSharedRef OnGenerateRow(FSearchResult InItem, const TSharedRef& OwnerTable); diff --git a/Engine/Source/Editor/MaterialEditor/Public/IMaterialEditor.h b/Engine/Source/Editor/MaterialEditor/Public/IMaterialEditor.h index 96f9a6447e85..a0a4b295d7f3 100644 --- a/Engine/Source/Editor/MaterialEditor/Public/IMaterialEditor.h +++ b/Engine/Source/Editor/MaterialEditor/Public/IMaterialEditor.h @@ -50,6 +50,11 @@ public: */ virtual void AddToSelection(UMaterialExpression* Expression) {}; + /** + * Jumps to the node for this expression, if possible + */ + virtual void JumpToExpression(UMaterialExpression* Expression) {}; + /** * Disconnects and removes the selected material graph nodes. */ diff --git a/Engine/Source/Editor/MaterialEditor/Public/MaterialEditorActions.h b/Engine/Source/Editor/MaterialEditor/Public/MaterialEditorActions.h index d74b1e69c399..189aeddf3196 100644 --- a/Engine/Source/Editor/MaterialEditor/Public/MaterialEditorActions.h +++ b/Engine/Source/Editor/MaterialEditor/Public/MaterialEditorActions.h @@ -170,13 +170,13 @@ public: TSharedPtr< FUICommandInfo > PromoteToParameter; TSharedPtr< FUICommandInfo > QualityLevel_All; + TSharedPtr< FUICommandInfo > QualityLevel_Epic; TSharedPtr< FUICommandInfo > QualityLevel_High; TSharedPtr< FUICommandInfo > QualityLevel_Medium; TSharedPtr< FUICommandInfo > QualityLevel_Low; TSharedPtr< FUICommandInfo > FeatureLevel_All; TSharedPtr< FUICommandInfo > FeatureLevel_ES31; - TSharedPtr< FUICommandInfo > FeatureLevel_SM4; TSharedPtr< FUICommandInfo > FeatureLevel_SM5; /** diff --git a/Engine/Source/Editor/MeshPaint/Private/IMeshPaintMode.cpp b/Engine/Source/Editor/MeshPaint/Private/IMeshPaintMode.cpp index 516cccbf3785..9124f3c62051 100644 --- a/Engine/Source/Editor/MeshPaint/Private/IMeshPaintMode.cpp +++ b/Engine/Source/Editor/MeshPaint/Private/IMeshPaintMode.cpp @@ -43,6 +43,10 @@ IMeshPaintEdMode::IMeshPaintEdMode() IMeshPaintEdMode::~IMeshPaintEdMode() { + if (GEditor) + { + GEditor->OnEditorClose().RemoveAll(this); + } } /** FGCObject interface */ diff --git a/Engine/Source/Editor/MovieSceneTools/Private/Channels/BuiltInChannelEditors.cpp b/Engine/Source/Editor/MovieSceneTools/Private/Channels/BuiltInChannelEditors.cpp index 16172d9b0bbc..8a10ff121400 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/Channels/BuiltInChannelEditors.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/Channels/BuiltInChannelEditors.cpp @@ -32,43 +32,134 @@ #include "Framework/Application/SlateApplication.h" #include "Editor/SceneOutliner/Private/SSocketChooser.h" #include "SComponentChooser.h" +#include "EntitySystem/Interrogation/MovieSceneInterrogationLinker.h" +#include "EntitySystem/Interrogation/MovieSceneInterrogatedPropertyInstantiator.h" +#include "Systems/MovieScenePropertyInstantiator.h" +#include "ISequencerModule.h" +#include "MovieSceneTracksComponentTypes.h" #define LOCTEXT_NAMESPACE "BuiltInChannelEditors" FKeyHandle AddOrUpdateKey(FMovieSceneFloatChannel* Channel, UMovieSceneSection* SectionToKey, const TMovieSceneExternalValue& ExternalValue, FFrameNumber InTime, ISequencer& Sequencer, const FGuid& InObjectBindingID, FTrackInstancePropertyBindings* PropertyBindings) { - TOptional Value; - float CurrentValue = 0.0f, CurrentWeight = 1.0f; - if ((ExternalValue.OnGetExternalValue && InObjectBindingID.IsValid())) + using namespace UE::MovieScene; + + const FMovieSceneSequenceID SequenceID = Sequencer.GetFocusedTemplateID(); + + // Find the first bound object so we can get the current property channel value on it. + UObject* FirstBoundObject = nullptr; + TOptional CurrentBoundObjectValue; + if (InObjectBindingID.IsValid()) { - for (TWeakObjectPtr<> WeakObject : Sequencer.FindBoundObjects(InObjectBindingID, Sequencer.GetFocusedTemplateID())) + for (TWeakObjectPtr<> WeakObject : Sequencer.FindBoundObjects(InObjectBindingID, SequenceID)) { if (UObject* Object = WeakObject.Get()) { - Value = ExternalValue.OnGetExternalValue(*Object, PropertyBindings); - if (Value.IsSet() && ExternalValue.OnGetCurrentValueAndWeight && SectionToKey) + FirstBoundObject = Object; + + if (ExternalValue.OnGetExternalValue) { - CurrentValue = Value.Get(0.0f); - ExternalValue.OnGetCurrentValueAndWeight(Object, SectionToKey, InTime, Sequencer.GetFocusedTickResolution(), Sequencer.GetEvaluationTemplate(),CurrentValue,CurrentWeight); + CurrentBoundObjectValue = ExternalValue.OnGetExternalValue(*Object, PropertyBindings); } + break; } } } + // If we got the current property channel value on the object, let's get the current evaluated property channel value at the given time (which is the value that the + // object *would* be at if we scrubbed here and let the sequence evaluation do its thing). This will help us figure out the difference between the current object value + // and the evaluated sequencer value: we will compute a new value for the channel so that a new sequence evaluation would come out at the "desired" value, which is + // what the current object value. float NewValue = Channel->GetDefault().Get(0.f); - bool bWasEvaluated = Channel->Evaluate(InTime, NewValue); - if (Value.IsSet()) //need to get the diff between Value(Global) and CurrentValue and apply that to the local + + if (CurrentBoundObjectValue.IsSet() && SectionToKey) { - if (bWasEvaluated) + if (ExternalValue.OnGetCurrentValueAndWeight) { - float CurrentGlobalValue = Value.GetValue(); - NewValue = (Value.Get(0.0f) - CurrentValue) * CurrentWeight + NewValue; + // We have a custom callback that can provide us with the evaluated value of this channel. + float CurrentValue = CurrentBoundObjectValue.Get(0.0f); + float CurrentWeight = 1.0f; + FMovieSceneRootEvaluationTemplateInstance& EvaluationTemplate = Sequencer.GetEvaluationTemplate(); + ExternalValue.OnGetCurrentValueAndWeight(FirstBoundObject, SectionToKey, InTime, Sequencer.GetFocusedTickResolution(), EvaluationTemplate, CurrentValue, CurrentWeight); + + bool bWasEvaluated = Channel->Evaluate(InTime, NewValue); + if (CurrentBoundObjectValue.IsSet()) //need to get the diff between Value(Global) and CurrentValue and apply that to the local + { + if (bWasEvaluated) + { + float CurrentGlobalValue = CurrentBoundObjectValue.GetValue(); + NewValue = (CurrentBoundObjectValue.Get(0.0f) - CurrentValue) * CurrentWeight + NewValue; + } + else //Nothing set (key or default) on channel so use external value + { + NewValue = CurrentBoundObjectValue.Get(0.0f); + } + } } - else //Nothing set (key or default) on channel so use external value + else { - NewValue = Value.Get(0.0f); + // No custom callback... we need to run the blender system on our property. + FSystemInterrogator Interrogator; + Interrogator.TrackImportedEntities(true); + + TGuardValue DebugVizGuard(GEntityManagerForDebuggingVisualizers, &Interrogator.GetLinker()->EntityManager); + + UMovieSceneTrack* TrackToKey = SectionToKey->GetTypedOuter(); + + Interrogator.ImportTrack(TrackToKey, FInterrogationChannel::Default()); + Interrogator.AddInterrogation(InTime); + Interrogator.Update(); + + const FMovieSceneEntityID EntityID = Interrogator.FindEntityFromOwner(FInterrogationKey::Default(), SectionToKey, 0); + + UMovieSceneInterrogatedPropertyInstantiatorSystem* System = Interrogator.GetLinker()->FindSystem(); + + if (ensure(System) && EntityID) // EntityID can be invalid here if we are keying a section that is currently empty + { + const FMovieSceneChannelProxy& SectionChannelProxy = SectionToKey->GetChannelProxy(); + const FName ChannelTypeName = Channel->StaticStruct()->GetFName(); + int32 ChannelIndex = SectionChannelProxy.FindIndex(ChannelTypeName, Channel); + + FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get(); + + // Find the property definition based on the property tag that our section entity has. + int32 BoundPropertyDefinitionIndex = INDEX_NONE; + TArrayView PropertyDefinitions = BuiltInComponents->PropertyRegistry.GetProperties(); + for (int32 Index = 0; Index < PropertyDefinitions.Num(); ++Index) + { + const FPropertyDefinition& PropertyDefinition = PropertyDefinitions[Index]; + if (Interrogator.GetLinker()->EntityManager.HasComponent(EntityID, PropertyDefinition.PropertyType)) + { + BoundPropertyDefinitionIndex = Index; + break; + } + } + + if (ensure(ChannelIndex != INDEX_NONE && BoundPropertyDefinitionIndex != INDEX_NONE)) + { + const FPropertyDefinition& BoundPropertyDefinition = PropertyDefinitions[BoundPropertyDefinitionIndex]; + + check(FirstBoundObject != nullptr); + if (Interrogator.GetLinker()->EntityManager.HasComponent(EntityID, BuiltInComponents->SceneComponentBinding)) + { + FirstBoundObject = MovieSceneHelpers::SceneComponentFromRuntimeObject(FirstBoundObject); + check(FirstBoundObject != nullptr); + } + + FDecompositionQuery Query; + Query.Entities = MakeArrayView(&EntityID, 1); + Query.bConvertFromSourceEntityIDs = false; + Query.Object = FirstBoundObject; + + FIntermediate3DTransform InTransformData; + + TRecompositionResult RecomposeResult = System->RecomposeBlendFloatChannel(BoundPropertyDefinition, ChannelIndex, Query, CurrentBoundObjectValue.Get(0.f)); + + NewValue = RecomposeResult.Values[0]; + } + } } } diff --git a/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLExport.cpp b/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLExport.cpp index 7ca74f840371..3d56467169e0 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLExport.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLExport.cpp @@ -114,7 +114,12 @@ bool FFCPXMLExportVisitor::ConstructProjectNode(TSharedRef InParent TSharedRef ChildrenNode = ProjectNode->CreateChildNode(TEXT("children")); - if (!ConstructMasterClipNodes(ChildrenNode)) + if (!ConstructMasterVideoClipNodes(ChildrenNode)) + { + return false; + } + + if (!ConstructMasterAudioClipNodes(ChildrenNode)) { return false; } @@ -127,8 +132,7 @@ bool FFCPXMLExportVisitor::ConstructProjectNode(TSharedRef InParent return true; } -/** Creates master clip node. */ -bool FFCPXMLExportVisitor::ConstructMasterClipNodes(TSharedRef InParentNode) +bool FFCPXMLExportVisitor::ConstructMasterVideoClipNodes(TSharedRef InParentNode) { if (!ExportData->IsExportDataValid() || !ExportData->MovieSceneData.IsValid() || !ExportData->MovieSceneData->CinematicMasterTrack.IsValid()) { @@ -161,6 +165,16 @@ bool FFCPXMLExportVisitor::ConstructMasterClipNodes(TSharedRef InPa } } + return true; +} + +bool FFCPXMLExportVisitor::ConstructMasterAudioClipNodes(TSharedRef InParentNode) +{ + if (!ExportData->IsExportDataValid() || !ExportData->MovieSceneData.IsValid() || !ExportData->MovieSceneData->CinematicMasterTrack.IsValid()) + { + return false; + } + for (TSharedPtr AudioMasterTrack : ExportData->MovieSceneData->AudioMasterTracks) { if (!AudioMasterTrack.IsValid()) @@ -236,12 +250,12 @@ bool FFCPXMLExportVisitor::ConstructMasterClipNode(TSharedRef InPar return false; } - if (!ConstructLoggingInfoNode(ClipNode, InCinematicSectionData)) + if (!ConstructLoggingInfoNode(ClipNode, InCinematicSectionData->MovieSceneSection)) { return false; } - if (!ConstructColorInfoNode(ClipNode, InCinematicSectionData)) + if (!ConstructColorInfoNode(ClipNode)) { return false; } @@ -326,15 +340,15 @@ bool FFCPXMLExportVisitor::ConstructMasterClipNode(TSharedRef InPar /** Creates logginginfo node. */ -bool FFCPXMLExportVisitor::ConstructLoggingInfoNode(TSharedRef InParentNode, const TSharedPtr InSectionData) +bool FFCPXMLExportVisitor::ConstructLoggingInfoNode(TSharedRef InParentNode, const UMovieSceneSection* InMovieSceneSection) { - if (!InSectionData.IsValid() || InSectionData->MovieSceneSection == nullptr) + if (!IsValid(InMovieSceneSection)) { return false; } TSharedRef LoggingInfoNode = InParentNode->CreateChildNode(TEXT("logginginfo")); - ConstructLoggingInfoElements(LoggingInfoNode, InSectionData->MovieSceneSection); + ConstructLoggingInfoElements(LoggingInfoNode, InMovieSceneSection); TSharedPtr LogNoteNode = LoggingInfoNode->GetChildNode(TEXT("lognote"), ENodeInherit::NoInherit, ENodeReference::NoReferences); if (!LogNoteNode.IsValid()) @@ -343,7 +357,7 @@ bool FFCPXMLExportVisitor::ConstructLoggingInfoNode(TSharedRef InPa } FString Metadata{ TEXT("") }; - const UMovieSceneCinematicShotSection* ShotSection = Cast(InSectionData->MovieSceneSection); + const UMovieSceneCinematicShotSection* ShotSection = Cast(InMovieSceneSection); if (ShotSection == nullptr) { return false; @@ -462,7 +476,7 @@ void FFCPXMLExportVisitor::SetLoggingInfoElementValue(TSharedPtr In } /** Creates colorinfo node. */ -bool FFCPXMLExportVisitor::ConstructColorInfoNode(TSharedRef InParentNode, const TSharedPtr InSectionData) +bool FFCPXMLExportVisitor::ConstructColorInfoNode(TSharedRef InParentNode) { TSharedPtr ColorInfoNode = InParentNode->CreateChildNode(TEXT("colorinfo")); ColorInfoNode->CreateChildNode(TEXT("lut")); diff --git a/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLExport.h b/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLExport.h index 97bce90857dc..608cea70f99b 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLExport.h +++ b/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLExport.h @@ -50,16 +50,18 @@ public: public: /** Creates project node. */ bool ConstructProjectNode(TSharedRef InParentNode); - /** Creates master clip nodes. */ - bool ConstructMasterClipNodes(TSharedRef InParentNode); - /** Creates master clip node. */ + /** Creates master video clip nodes. */ + virtual bool ConstructMasterVideoClipNodes(TSharedRef InParentNode); + /** Creates master audio clip nodes. */ + bool ConstructMasterAudioClipNodes(TSharedRef InParentNode); + /** Creates master video clip node. */ bool ConstructMasterClipNode(TSharedRef InParentNode, const TSharedPtr InCinematicSectionData, const TSharedPtr InCinematicMasterTrackData); - /** Creates master clip node. */ + /** Creates master audio clip node. */ bool ConstructMasterClipNode(TSharedRef InParentNode, const TSharedPtr InAudioSectionData, const TSharedPtr InCinematicMasterTrackData); /** Creates colorinfo node. */ - bool ConstructColorInfoNode(TSharedRef InParentNode, const TSharedPtr InSectionData); + bool ConstructColorInfoNode(TSharedRef InParentNode); /** Creates logginginfo node. */ - bool ConstructLoggingInfoNode(TSharedRef InParentNode, const TSharedPtr InSectionData); + bool ConstructLoggingInfoNode(TSharedRef InParentNode, const UMovieSceneSection* InMovieSceneSection); /** Creates logginginfo node. */ bool ConstructLoggingInfoNode(TSharedRef InParentNode, const TSharedPtr InSectionData); /** Creates sequence node. */ @@ -69,7 +71,7 @@ public: /** Creates audio node. */ bool ConstructAudioNode(TSharedRef InParentNode); /** Creates video track node. */ - bool ConstructVideoTrackNode(TSharedRef InParentNode, const TSharedPtr InCinematicTrackData, const TSharedPtr InCinematicMasterTrackData); + virtual bool ConstructVideoTrackNode(TSharedRef InParentNode, const TSharedPtr InCinematicTrackData, const TSharedPtr InCinematicMasterTrackData); /** Creates audio track node. */ bool ConstructAudioTrackNode(TSharedRef InParentNode, const TSharedPtr InAudioTrackData, const TSharedPtr InAudioMasterTrackData, uint32 InTrackIndex, uint32 OutNumTracks); /** Creates video clip item node. */ @@ -99,7 +101,7 @@ public: /** Get audio duration, start, end, in, out frames for a given cinematic shot section */ bool GetAudioSectionFrames(const TSharedPtr InAudioSectionData, int32& OutDuration, int32& OutStartFrame, int32& OutEndFrame, int32& OutInFrame, int32& OutOutFrame); -private: +protected: /** Has master clip id for given section */ bool HasMasterClipIdName(const TSharedPtr InSection, FString& OutName, bool& bOutMasterClipExists); @@ -125,7 +127,7 @@ private: /** Get audio section group name */ static FString GetAudioSectionName(const UMovieSceneSection* InAudioSection); -private: +protected: TSharedRef ExportData; TSharedRef ExportContext; diff --git a/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLMetadataExport.cpp b/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLMetadataExport.cpp new file mode 100644 index 000000000000..c31b894dcefb --- /dev/null +++ b/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLMetadataExport.cpp @@ -0,0 +1,331 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "FCPXML/FCPXMLMetadataExport.h" +#include "MovieScene.h" + +DEFINE_LOG_CATEGORY_STATIC(LogFCPXMLMetadataExporter, Log, All); + +FFCPXMLMetadataExportVisitor::FFCPXMLMetadataExportVisitor(FString InSaveFilename, TSharedRef InExportData, TSharedRef InExportContext, const FMovieSceneExportMetadata* InMovieSceneExportMetadata) : + FFCPXMLExportVisitor(InSaveFilename, InExportData, InExportContext), + MovieSceneExportMetadata(InMovieSceneExportMetadata) +{ +} + +FFCPXMLMetadataExportVisitor::~FFCPXMLMetadataExportVisitor() {} + +// Return which of the exported clip formats is preferred for use in the project file +FString GetPreferredFormat(const TArray& Extensions) +{ + // Format extensions in order of preference + const TArray PreferredFormats = { + TEXT("MXF"), + TEXT("MOV"), + TEXT("AVI"), + TEXT("EXR"), + TEXT("PNG"), + TEXT("JPEG"), + TEXT("JPG") + }; + + for (const FString& Format : PreferredFormats) + { + if (Extensions.Contains(Format)) + { + return Format; + } + } + + return Extensions[0]; // If we didn't find a preferred format, use the first one +} + +bool FFCPXMLMetadataExportVisitor::ConstructMasterVideoClipNodes(TSharedRef InParentNode) +{ + if (!ExportData->IsExportDataValid() || !ExportData->MovieSceneData.IsValid() || !ExportData->MovieSceneData->CinematicMasterTrack.IsValid()) + { + return false; + } + + if (!MovieSceneExportMetadata) + { + return false; + } + + + for (const FMovieSceneExportMetadataShot& Shot : MovieSceneExportMetadata->Shots) + { + for (const TPair < FString, TMap >& Clip : Shot.Clips) + { + const TMap& ExtensionList = Clip.Value; + if (ExtensionList.Num() > 0) + { + TArray Extensions; + ExtensionList.GetKeys(Extensions); + FString PreferredFormat = GetPreferredFormat(Extensions); + check(ExtensionList.Contains(PreferredFormat)); + const FMovieSceneExportMetadataClip& ClipMetadata = ExtensionList[PreferredFormat]; + if (ClipMetadata.IsValid()) + { + if (!ConstructMasterClipNode(InParentNode, Clip.Key, Shot, ClipMetadata, Shot.HandleFrames)) + { + return false; + } + } + } + } + } + + return true; +} + + +bool FFCPXMLMetadataExportVisitor::ConstructMasterClipNode(TSharedRef InParentNode, const FString& InClipName, const FMovieSceneExportMetadataShot& InShotMetadata, const FMovieSceneExportMetadataClip& InClipMetadata, const int32 HandleFrames) +{ + int32 Duration = InClipMetadata.GetDuration(); + int32 StartFrame = InClipMetadata.StartFrame; + int32 EndFrame = InClipMetadata.EndFrame; + int32 InFrame = HandleFrames; + int32 OutFrame = InFrame + Duration; + FString SectionName = InClipName; + + /** Construct a master clip id name based on the cinematic section and id */ + FString MasterClipName{ TEXT("") }; + GetMasterClipIdName(InClipName, MasterClipName); + + TSharedRef ClipNode = InParentNode->CreateChildNode(TEXT("clip")); + ClipNode->AddAttribute(TEXT("id"), MasterClipName); + + // @todo add to file's masterclip and refidmap HERE + + ClipNode->CreateChildNode(TEXT("masterclipid"))->SetContent(MasterClipName); + ClipNode->CreateChildNode(TEXT("ismasterclip"))->SetContent(true); + ClipNode->CreateChildNode(TEXT("duration"))->SetContent(Duration); + + if (!ConstructRateNode(ClipNode)) + { + return false; + } + + ClipNode->CreateChildNode(TEXT("in"))->SetContent(InFrame); + ClipNode->CreateChildNode(TEXT("out"))->SetContent(OutFrame); + ClipNode->CreateChildNode(TEXT("name"))->SetContent(SectionName); + TSharedRef MediaNode = ClipNode->CreateChildNode(TEXT("media")); + TSharedRef VideoNode = MediaNode->CreateChildNode(TEXT("video")); + TSharedRef TrackNode = VideoNode->CreateChildNode(TEXT("track")); + + if (!ConstructVideoClipItemNode(TrackNode, SectionName, InClipMetadata, HandleFrames, true)) + { + return false; + } + + UMovieSceneCinematicShotSection* ShotSection = InShotMetadata.MovieSceneShotSection.Get(); + if (ShotSection) + { + if (!ConstructLoggingInfoNode(ClipNode, ShotSection)) + { + return false; + } + } + + if (!ConstructColorInfoNode(ClipNode)) + { + return false; + } + + return true; +} + +bool FFCPXMLMetadataExportVisitor::ConstructVideoClipItemNode(TSharedRef InParentNode, const FString& InClipName, const FMovieSceneExportMetadataClip& InClipMetadata, const int32 HandleFrames, bool bInMasterClip) +{ + + TSharedRef ClipItemNode = InParentNode->CreateChildNode(TEXT("clipitem")); + + int32 Duration = InClipMetadata.GetDuration(); + int32 StartFrame = InClipMetadata.StartFrame; + int32 EndFrame = InClipMetadata.EndFrame; + int32 InFrame = HandleFrames; + int32 OutFrame = InFrame + Duration; + + FString MasterClipIdName = TEXT(""); + GetMasterClipIdName(InClipName, MasterClipIdName); + + FString ClipItemIdName{ TEXT("") }; + GetNextClipItemIdName(ClipItemIdName); + + // attributes + ClipItemNode->AddAttribute(TEXT("id"), ClipItemIdName); + + // elements + ClipItemNode->CreateChildNode(TEXT("masterclipid"))->SetContent(MasterClipIdName); + ClipItemNode->CreateChildNode(TEXT("ismasterclip"))->SetContent(bInMasterClip); + ClipItemNode->CreateChildNode(TEXT("name"))->SetContent(InClipName); + ClipItemNode->CreateChildNode(TEXT("enabled"))->SetContent(true); + ClipItemNode->CreateChildNode(TEXT("duration"))->SetContent(Duration); + + if (!ConstructRateNode(ClipItemNode)) + { + return false; + } + + if (!bInMasterClip) + { + ClipItemNode->CreateChildNode(TEXT("start"))->SetContent(StartFrame); + ClipItemNode->CreateChildNode(TEXT("end"))->SetContent(EndFrame); + } + + ClipItemNode->CreateChildNode(TEXT("in"))->SetContent(InFrame); + ClipItemNode->CreateChildNode(TEXT("out"))->SetContent(OutFrame); + + if (bInMasterClip) + { + ClipItemNode->CreateChildNode(TEXT("anamorphic"))->SetContent(false); + ClipItemNode->CreateChildNode(TEXT("pixelaspectratio"))->SetContent(FString(TEXT("square"))); + ClipItemNode->CreateChildNode(TEXT("fielddominance"))->SetContent(FString(TEXT("lower"))); + } + + if (!ConstructVideoFileNode(ClipItemNode, InClipMetadata.FileName, Duration, bInMasterClip)) + { + return false; + } + + return true; +} + +bool FFCPXMLMetadataExportVisitor::GetMasterClipIdName(const FString& InClipName, FString& OutName) +{ + if (MasterClipIdMap.Num() > 0) + { + uint32* FoundId = MasterClipIdMap.Find(InClipName); + if (FoundId != nullptr) + { + OutName = FString::Printf(TEXT("masterclip-%d"), *FoundId); + return true; + } + } + + ++MasterClipId; + MasterClipIdMap.Add(InClipName, MasterClipId); + OutName = FString::Printf(TEXT("masterclip-%d"), MasterClipId); + + return true; +} + +bool FFCPXMLMetadataExportVisitor::GetFileIdName(const FString& InFileName, FString& OutFileIdName, bool& OutFileExists) +{ + if (FileIdMap.Num() > 0) + { + uint32* FoundFileId = FileIdMap.Find(InFileName); + if (FoundFileId != nullptr) + { + OutFileIdName = FString::Printf(TEXT("file-%d"), *FoundFileId); + OutFileExists = true; + return true; + } + } + + ++FileId; + FileIdMap.Add(InFileName, FileId); + OutFileIdName = FString::Printf(TEXT("file-%d"), FileId); + OutFileExists = false; + return true; +} + +bool FFCPXMLMetadataExportVisitor::ConstructVideoTrackNode(TSharedRef InParentNode, const TSharedPtr InCinematicTrackData, const TSharedPtr InCinematicMasterTrackData) +{ + if (!ExportData->IsExportDataValid() || !InCinematicTrackData.IsValid()) + { + return false; + } + + TSharedRef TrackNode = InParentNode->CreateChildNode(TEXT("track")); + + for (TSharedPtr CinematicSection : InCinematicTrackData->CinematicSections) + { + for (const FMovieSceneExportMetadataShot& Shot : MovieSceneExportMetadata->Shots) + { + UMovieSceneCinematicShotSection* ShotSection = Shot.MovieSceneShotSection.Get(); + if (ShotSection && ShotSection == CinematicSection->MovieSceneSection) + { + for (const TPair < FString, TMap >& Clip : Shot.Clips) + { + const TMap& ExtensionList = Clip.Value; + if (ExtensionList.Num() > 0) + { + TArray Extensions; + ExtensionList.GetKeys(Extensions); + FString PreferredFormat = GetPreferredFormat(Extensions); + check(ExtensionList.Contains(PreferredFormat)); + const FMovieSceneExportMetadataClip& ClipMetadata = ExtensionList[PreferredFormat]; + if (ClipMetadata.IsValid()) + { + if (!ConstructVideoClipItemNode(TrackNode, Clip.Key, ClipMetadata, Shot.HandleFrames, false)) + { + return false; + } + } + } + } + break; + } + } + } + + TSharedRef EnabledNode = TrackNode->CreateChildNode(TEXT("enabled")); + EnabledNode->SetContent(true); + + TSharedRef LockedNode = TrackNode->CreateChildNode(TEXT("locked")); + LockedNode->SetContent(false); + + return true; +} + +bool FFCPXMLMetadataExportVisitor::ConstructVideoFileNode(TSharedRef InParentNode, const FString& InFileName, int32 Duration, bool bInMasterClip) +{ + if (!ExportData->IsExportDataValid()) + { + return false; + } + + FString FileIdName{ TEXT("") }; + bool bFileExists = false; + GetFileIdName(InFileName, FileIdName, bFileExists); + + // attributes + TSharedRef FileNode = InParentNode->CreateChildNode(TEXT("file")); + FileNode->AddAttribute(TEXT("id"), FileIdName); + + if (!bFileExists) + { + FString FilePathName = SaveFilePath + TEXT("/") + InFileName; + FString FilePathUrl = FString(TEXT("file://localhost/")) + FilePathName.Replace(TEXT(" "), TEXT("%20")).Replace(TEXT(":"), TEXT("%3a")); + + // required elements + TSharedRef NameNode = FileNode->CreateChildNode(TEXT("name")); + NameNode->SetContent(InFileName); + + TSharedRef PathUrlNode = FileNode->CreateChildNode(TEXT("pathurl")); + PathUrlNode->SetContent(FilePathUrl); + + if (!ConstructRateNode(FileNode)) + { + return false; + } + + TSharedRef DurationNode = FileNode->CreateChildNode(TEXT("duration")); + DurationNode->SetContent(static_cast(Duration)); + + if (!ConstructTimecodeNode(FileNode)) + { + return false; + } + + TSharedRef MediaNode = FileNode->CreateChildNode(TEXT("media")); + TSharedRef VideoNode = MediaNode->CreateChildNode(TEXT("video")); + + if (!ConstructVideoSampleCharacteristicsNode(VideoNode, ExportData->GetResX(), ExportData->GetResY())) + { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLMetadataExport.h b/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLMetadataExport.h new file mode 100644 index 000000000000..2c08ab431224 --- /dev/null +++ b/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLMetadataExport.h @@ -0,0 +1,32 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "FCPXML/FCPXMLExport.h" +#include "MovieSceneExportMetadata.h" + +/** The FFCPXMLMetadataExportVisitor class exports FCP 7 XML structure based on metadata from a sequencer movie export + */ + +class FFCPXMLMetadataExportVisitor : public FFCPXMLExportVisitor +{ +public: + /** Constructor */ + FFCPXMLMetadataExportVisitor(FString InSaveFilename, TSharedRef InExportData, TSharedRef InExportContext, const FMovieSceneExportMetadata* InMovieSceneExportMetadata); + /** Destructor */ + virtual ~FFCPXMLMetadataExportVisitor(); + + /** Creates master video clip nodes. */ + virtual bool ConstructMasterVideoClipNodes(TSharedRef InParentNode) override; + virtual bool ConstructVideoTrackNode(TSharedRef InParentNode, const TSharedPtr InCinematicTrackData, const TSharedPtr InCinematicMasterTrackData) override; + + bool ConstructMasterClipNode(TSharedRef InParentNode, const FString& InClipName, const FMovieSceneExportMetadataShot& InShotMetadata, const FMovieSceneExportMetadataClip& InClipMetadata, const int32 HandleFrames); + bool ConstructVideoClipItemNode(TSharedRef InParentNode, const FString& InClipName, const FMovieSceneExportMetadataClip& InClipMetadata, const int32 HandleFrames, bool bInMasterClip); + bool GetMasterClipIdName(const FString& InClipName, FString& OutName); + bool GetFileIdName(const FString& InFileName, FString& OutFileIdName, bool& OutFileExists); + bool ConstructVideoFileNode(TSharedRef InParentNode, const FString& InClipName, int32 Duration, bool bInMasterClip); + +protected: + const FMovieSceneExportMetadata* MovieSceneExportMetadata; + +}; diff --git a/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLMovieSceneTranslator.cpp b/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLMovieSceneTranslator.cpp index fe1fbc5fc507..dd6e3f327910 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLMovieSceneTranslator.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/FCPXML/FCPXMLMovieSceneTranslator.cpp @@ -4,6 +4,7 @@ #include "FCPXML/FCPXMLFile.h" #include "FCPXML/FCPXMLImport.h" #include "FCPXML/FCPXMLExport.h" +#include "FCPXML/FCPXMLMetadataExport.h" #include "MovieScene.h" #include "MovieSceneTranslator.h" #include "Misc/FileHelper.h" @@ -176,7 +177,7 @@ FText FFCPXMLExporter::GetMessageLogLabel() const return LOCTEXT("FCPXMLExportLogLabel", "FCP 7 XML Export Log"); } -bool FFCPXMLExporter::Export(const UMovieScene* InMovieScene, FString InFilenameFormat, FFrameRate InFrameRate, uint32 InResX, uint32 InResY, int32 InHandleFrames, FString InSaveFilename, TSharedRef InContext, FString InMovieExtension) +bool FFCPXMLExporter::Export(const UMovieScene* InMovieScene, FString InFilenameFormat, FFrameRate InFrameRate, uint32 InResX, uint32 InResY, int32 InHandleFrames, FString InSaveFilename, TSharedRef InContext, FString InMovieExtension, const FMovieSceneExportMetadata* InMetadata) { // add warning message if filename format is not "{shot}" FString AcceptedFormat = TEXT("{shot}"); @@ -195,8 +196,18 @@ bool FFCPXMLExporter::Export(const UMovieScene* InMovieScene, FString InFilename TSharedRef ExportData = MakeShared(InMovieScene, InFrameRate, InResX, InResY, InHandleFrames, InSaveFilename, InContext, InMovieExtension); // Export sequencer movie scene, merging with existing Xml structure. - FFCPXMLExportVisitor ExportVisitor(InSaveFilename, ExportData, InContext); - bool bSuccess = FCPXMLFile->Accept(ExportVisitor); + FFCPXMLExportVisitor* ExportVisitor; + + if (InMetadata) + { + ExportVisitor = new FFCPXMLMetadataExportVisitor(InSaveFilename, ExportData, InContext, InMetadata); + } + else + { + ExportVisitor = new FFCPXMLExportVisitor(InSaveFilename, ExportData, InContext); + } + + bool bSuccess = FCPXMLFile->Accept(*ExportVisitor); if (bSuccess && FCPXMLFile->IsValidFile()) { // Save the Xml structure to a file @@ -215,6 +226,8 @@ bool FFCPXMLExporter::Export(const UMovieScene* InMovieScene, FString InFilename InContext->AddMessage(EMessageSeverity::Error, Message); } + delete ExportVisitor; + return bSuccess; } diff --git a/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneEventUtils.cpp b/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneEventUtils.cpp index 8c0fac038ad4..d4c2f2672e45 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneEventUtils.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneEventUtils.cpp @@ -163,46 +163,35 @@ UK2Node* FMovieSceneEventUtils::FindEndpoint(FMovieSceneEvent* EntryPoint, UMovi check(OwnerBlueprint); check(EntryPoint); - if (EntryPoint->WeakCachedEndpoint.IsStale()) + if (EntryPoint->WeakEndpoint.IsStale()) + { + return nullptr; + } + if (UK2Node* Node = Cast(EntryPoint->WeakEndpoint.Get())) + { + return Node; + } + + if (!EntryPoint->GraphGuid_DEPRECATED.IsValid()) { return nullptr; } - UK2Node* CachedEndpoint = CastChecked(EntryPoint->WeakCachedEndpoint.Get(), ECastCheckedType::NullAllowed); - if (CachedEndpoint) - { - if (FBlueprintEditorUtils::FindBlueprintForNode(CachedEndpoint) == OwnerBlueprint) - { - return CachedEndpoint; - } - - CachedEndpoint->OnUserDefinedPinRenamed().RemoveAll(EventSection); - EntryPoint->WeakCachedEndpoint = nullptr; - return nullptr; - } - - // The cached entry point is either null or stale - EntryPoint->WeakCachedEndpoint = nullptr; - if (!EntryPoint->GraphGuid.IsValid()) - { - return nullptr; - } - - if (EntryPoint->NodeGuid.IsValid()) + if (EntryPoint->NodeGuid_DEPRECATED.IsValid()) { for (UEdGraph* Graph : OwnerBlueprint->UbergraphPages) { - if (Graph->GraphGuid == EntryPoint->GraphGuid) + if (Graph->GraphGuid == EntryPoint->GraphGuid_DEPRECATED) { for (UEdGraphNode* Node : Graph->Nodes) { - if (Node->NodeGuid == EntryPoint->NodeGuid) + if (Node->NodeGuid == EntryPoint->NodeGuid_DEPRECATED) { UK2Node_CustomEvent* CustomEvent = Cast(Node); if (ensureMsgf(CustomEvent, TEXT("Encountered an event entry point node that is bound to something other than a custom event"))) { CustomEvent->OnUserDefinedPinRenamed().AddUObject(EventSection, &UMovieSceneEventSectionBase::OnUserDefinedPinRenamed); - EntryPoint->WeakCachedEndpoint = CustomEvent; + EntryPoint->WeakEndpoint = CustomEvent; return CustomEvent; } } @@ -213,14 +202,14 @@ UK2Node* FMovieSceneEventUtils::FindEndpoint(FMovieSceneEvent* EntryPoint, UMovi // If the node guid is invalid, this must be a function graph on the BP else for (UEdGraph* Graph : OwnerBlueprint->FunctionGraphs) { - if (Graph->GraphGuid == EntryPoint->GraphGuid) + if (Graph->GraphGuid == EntryPoint->GraphGuid_DEPRECATED) { for (UEdGraphNode* Node : Graph->Nodes) { if (UK2Node_FunctionEntry* FunctionEntry = Cast(Node)) { FunctionEntry->OnUserDefinedPinRenamed().AddUObject(EventSection, &UMovieSceneEventSectionBase::OnUserDefinedPinRenamed); - EntryPoint->WeakCachedEndpoint = FunctionEntry; + EntryPoint->WeakEndpoint = FunctionEntry; return FunctionEntry; } } @@ -252,7 +241,7 @@ void FMovieSceneEventUtils::SetEndpoint(FMovieSceneEvent* EntryPoint, UMovieScen { check(EntryPoint); - UK2Node* ExistingEndpoint = CastChecked(EntryPoint->WeakCachedEndpoint.Get(), ECastCheckedType::NullAllowed); + UK2Node* ExistingEndpoint = CastChecked(EntryPoint->WeakEndpoint.Get(), ECastCheckedType::NullAllowed); if (ExistingEndpoint) { ExistingEndpoint->OnUserDefinedPinRenamed().RemoveAll(EventSection); @@ -265,16 +254,6 @@ void FMovieSceneEventUtils::SetEndpoint(FMovieSceneEvent* EntryPoint, UMovieScen checkf(bIsFunction || bIsCustomEvent, TEXT("Only functions and custom events are supported as event endpoints")); - EntryPoint->GraphGuid = InNewEndpoint->GetGraph()->GraphGuid; - - if (bIsCustomEvent) - { - EntryPoint->NodeGuid = InNewEndpoint->NodeGuid; - } - else - { - EntryPoint->NodeGuid = FGuid(); - } if (BoundObjectPin) { @@ -286,15 +265,12 @@ void FMovieSceneEventUtils::SetEndpoint(FMovieSceneEvent* EntryPoint, UMovieScen } InNewEndpoint->OnUserDefinedPinRenamed().AddUObject(EventSection, &UMovieSceneEventSectionBase::OnUserDefinedPinRenamed); - EntryPoint->WeakCachedEndpoint = InNewEndpoint; + EntryPoint->WeakEndpoint = InNewEndpoint; } else { - EntryPoint->NodeGuid = FGuid(); - EntryPoint->GraphGuid = FGuid(); + EntryPoint->WeakEndpoint = nullptr; EntryPoint->BoundObjectPinName = NAME_None; - - EntryPoint->WeakCachedEndpoint = nullptr; } } diff --git a/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneToolHelpers.cpp b/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneToolHelpers.cpp index d083ac16bc33..13f8e081f946 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneToolHelpers.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneToolHelpers.cpp @@ -782,24 +782,26 @@ void MovieSceneToolHelpers::MovieSceneTranslatorLogOutput(FMovieSceneTranslator } } -static FGuid GetHandleToObject(UObject* InObject, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID) +static FGuid GetHandleToObject(UObject* InObject, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID) { + UMovieScene* MovieScene = InSequence->GetMovieScene(); + // Attempt to resolve the object through the movie scene instance first, FGuid PropertyOwnerGuid = FGuid(); - if (InObject != nullptr && !InMovieScene->IsReadOnly()) + if (InObject != nullptr && !MovieScene->IsReadOnly()) { FGuid ObjectGuid = Player->FindObjectId(*InObject, TemplateID); if (ObjectGuid.IsValid()) { // Check here for spawnable otherwise spawnables get recreated as possessables, which doesn't make sense - FMovieSceneSpawnable* Spawnable = InMovieScene->FindSpawnable(ObjectGuid); + FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectGuid); if (Spawnable) { PropertyOwnerGuid = ObjectGuid; } else { - FMovieScenePossessable* Possessable = InMovieScene->FindPossessable(ObjectGuid); + FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectGuid); if (Possessable) { PropertyOwnerGuid = ObjectGuid; @@ -807,11 +809,22 @@ static FGuid GetHandleToObject(UObject* InObject, UMovieScene* InMovieScene, IMo } } } + + if (PropertyOwnerGuid.IsValid()) + { + return PropertyOwnerGuid; + } + + // Otherwise, create a possessable for this object. Note this will handle creating the parent possessables if this is a component. + PropertyOwnerGuid = InSequence->CreatePossessable(InObject); + return PropertyOwnerGuid; } -bool ImportFBXProperty(FString NodeName, FString AnimatedPropertyName, FGuid ObjectBinding, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID) +bool ImportFBXProperty(FString NodeName, FString AnimatedPropertyName, FGuid ObjectBinding, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID) { + UMovieScene* MovieScene = InSequence->GetMovieScene(); + const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault(); const UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetDefault(); @@ -844,7 +857,7 @@ bool ImportFBXProperty(FString NodeName, FString AnimatedPropertyName, FGuid Obj continue; } - FGuid PropertyOwnerGuid = GetHandleToObject(PropertyOwner,InMovieScene, Player, TemplateID); + FGuid PropertyOwnerGuid = GetHandleToObject(PropertyOwner, InSequence, Player, TemplateID); if (!PropertyOwnerGuid.IsValid()) { continue; @@ -855,11 +868,11 @@ bool ImportFBXProperty(FString NodeName, FString AnimatedPropertyName, FGuid Obj continue; } - UMovieSceneFloatTrack* FloatTrack = InMovieScene->FindTrack(PropertyOwnerGuid, *FbxSetting.PropertyPath.PropertyName); + UMovieSceneFloatTrack* FloatTrack = MovieScene->FindTrack(PropertyOwnerGuid, *FbxSetting.PropertyPath.PropertyName); if (!FloatTrack) { - InMovieScene->Modify(); - FloatTrack = InMovieScene->AddTrack(PropertyOwnerGuid); + MovieScene->Modify(); + FloatTrack = MovieScene->AddTrack(PropertyOwnerGuid); FloatTrack->SetPropertyNameAndPath(*FbxSetting.PropertyPath.PropertyName, *FbxSetting.PropertyPath.PropertyName); } @@ -1039,8 +1052,10 @@ void ImportTransformChannel(const FRichCurve& Source, FMovieSceneFloatChannel* D } } -bool ImportFBXTransform(FString NodeName, FGuid ObjectBinding, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieScene* InMovieScene) +bool ImportFBXTransform(FString NodeName, FGuid ObjectBinding, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieSceneSequence* InSequence) { + UMovieScene* MovieScene = InSequence->GetMovieScene(); + const UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetDefault(); // Look for transforms explicitly @@ -1051,11 +1066,11 @@ bool ImportFBXTransform(FString NodeName, FGuid ObjectBinding, UnFbx::FFbxCurves const bool bUseSequencerCurve = true; CurveAPI.GetConvertedTransformCurveData(NodeName, Translation[0], Translation[1], Translation[2], EulerRotation[0], EulerRotation[1], EulerRotation[2], Scale[0], Scale[1], Scale[2], DefaultTransform, bUseSequencerCurve); - UMovieScene3DTransformTrack* TransformTrack = InMovieScene->FindTrack(ObjectBinding); + UMovieScene3DTransformTrack* TransformTrack = MovieScene->FindTrack(ObjectBinding); if (!TransformTrack) { - InMovieScene->Modify(); - TransformTrack = InMovieScene->AddTrack(ObjectBinding); + MovieScene->Modify(); + TransformTrack = MovieScene->AddTrack(ObjectBinding); } TransformTrack->Modify(); @@ -1106,7 +1121,7 @@ bool ImportFBXTransform(FString NodeName, FGuid ObjectBinding, UnFbx::FFbxCurves return true; } -bool MovieSceneToolHelpers::ImportFBXNode(FString NodeName, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, FGuid ObjectBinding) +bool MovieSceneToolHelpers::ImportFBXNode(FString NodeName, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, FGuid ObjectBinding) { // Look for animated float properties TArray AnimatedPropertyNames; @@ -1114,10 +1129,10 @@ bool MovieSceneToolHelpers::ImportFBXNode(FString NodeName, UnFbx::FFbxCurvesAPI for (auto AnimatedPropertyName : AnimatedPropertyNames) { - ImportFBXProperty(NodeName, AnimatedPropertyName, ObjectBinding, CurveAPI, InMovieScene, Player, TemplateID); + ImportFBXProperty(NodeName, AnimatedPropertyName, ObjectBinding, CurveAPI, InSequence, Player, TemplateID); } - ImportFBXTransform(NodeName, ObjectBinding, CurveAPI, InMovieScene); + ImportFBXTransform(NodeName, ObjectBinding, CurveAPI, InSequence); return true; } @@ -1252,8 +1267,10 @@ FString MovieSceneToolHelpers::GetCameraName(FbxCamera* InCamera) } -void MovieSceneToolHelpers::ImportFBXCameraToExisting(UnFbx::FFbxImporter* FbxImporter, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap& InObjectBindingMap, bool bMatchByNameOnly, bool bNotifySlate) +void MovieSceneToolHelpers::ImportFBXCameraToExisting(UnFbx::FFbxImporter* FbxImporter, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap& InObjectBindingMap, bool bMatchByNameOnly, bool bNotifySlate) { + UMovieScene* MovieScene = InSequence->GetMovieScene(); + for (auto InObjectBinding : InObjectBindingMap) { TArrayView> BoundObjects = Player->FindBoundObjects(InObjectBinding.Key,TemplateID); @@ -1336,14 +1353,14 @@ void MovieSceneToolHelpers::ImportFBXCameraToExisting(UnFbx::FFbxImporter* FbxIm // Set the default value of the current focal length or field of view section //FGuid PropertyOwnerGuid = Player->GetHandleToObject(CameraComponent); - FGuid PropertyOwnerGuid = GetHandleToObject(CameraComponent, InMovieScene, Player, TemplateID); + FGuid PropertyOwnerGuid = GetHandleToObject(CameraComponent, InSequence, Player, TemplateID); if (!PropertyOwnerGuid.IsValid()) { continue; } - UMovieSceneFloatTrack* FloatTrack = InMovieScene->FindTrack(PropertyOwnerGuid, TrackName); + UMovieSceneFloatTrack* FloatTrack = MovieScene->FindTrack(PropertyOwnerGuid, TrackName); if (FloatTrack) { FloatTrack->Modify(); @@ -1370,8 +1387,10 @@ void MovieSceneToolHelpers::ImportFBXCameraToExisting(UnFbx::FFbxImporter* FbxIm } } -void ImportFBXCamera(UnFbx::FFbxImporter* FbxImporter, UMovieScene* InMovieScene, ISequencer& InSequencer, TMap& InObjectBindingMap, bool bMatchByNameOnly, bool bCreateCameras) +void ImportFBXCamera(UnFbx::FFbxImporter* FbxImporter, UMovieSceneSequence* InSequence, ISequencer& InSequencer, TMap& InObjectBindingMap, bool bMatchByNameOnly, bool bCreateCameras) { + UMovieScene* MovieScene = InSequence->GetMovieScene(); + if (bCreateCameras) { TArray AllCameras; @@ -1459,7 +1478,7 @@ void ImportFBXCamera(UnFbx::FFbxImporter* FbxImporter, UMovieScene* InMovieScene } } - MovieSceneToolHelpers::ImportFBXCameraToExisting(FbxImporter, InMovieScene, &InSequencer, InSequencer.GetFocusedTemplateID(), InObjectBindingMap, bMatchByNameOnly, true); + MovieSceneToolHelpers::ImportFBXCameraToExisting(FbxImporter, InSequence, &InSequencer, InSequencer.GetFocusedTemplateID(), InObjectBindingMap, bMatchByNameOnly, true); } FGuid FindCameraGuid(FbxCamera* Camera, TMap& InObjectBindingMap) @@ -1538,7 +1557,7 @@ class SMovieSceneImportFBXSettings : public SCompoundWidget, public FGCObject { SLATE_BEGIN_ARGS(SMovieSceneImportFBXSettings) {} SLATE_ARGUMENT(FString, ImportFilename) - SLATE_ARGUMENT(UMovieScene*, MovieScene) + SLATE_ARGUMENT(UMovieSceneSequence*, Sequence) SLATE_ARGUMENT(ISequencer*, Sequencer) SLATE_END_ARGS() @@ -1580,7 +1599,7 @@ class SMovieSceneImportFBXSettings : public SCompoundWidget, public FGCObject ]; ImportFilename = InArgs._ImportFilename; - MovieScene = InArgs._MovieScene; + Sequence = InArgs._Sequence; Sequencer = InArgs._Sequencer; UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetMutableDefault(); @@ -1589,7 +1608,7 @@ class SMovieSceneImportFBXSettings : public SCompoundWidget, public FGCObject virtual void AddReferencedObjects( FReferenceCollector& Collector ) override { - Collector.AddReferencedObject(MovieScene); + Collector.AddReferencedObject(Sequence); } void SetObjectBindingMap(const TMap& InObjectBindingMap) @@ -1610,7 +1629,7 @@ private: UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetMutableDefault(); FEditorDirectories::Get().SetLastDirectory( ELastDirectory::FBX, FPaths::GetPath( ImportFilename ) ); // Save path as default for next time. - if (!MovieScene || MovieScene->IsReadOnly()) + if (!Sequence || !Sequence->GetMovieScene() || Sequence->GetMovieScene()->IsReadOnly()) { return FReply::Unhandled(); } @@ -1626,10 +1645,10 @@ private: const bool bMatchByNameOnly = ImportFBXSettings->bMatchByNameOnly; // Import static cameras first - ImportFBXCamera(FbxImporter, MovieScene, *Sequencer, ObjectBindingMap, bMatchByNameOnly, bCreateCameras.IsSet() ? bCreateCameras.GetValue() : ImportFBXSettings->bCreateCameras); + ImportFBXCamera(FbxImporter, Sequence, *Sequencer, ObjectBindingMap, bMatchByNameOnly, bCreateCameras.IsSet() ? bCreateCameras.GetValue() : ImportFBXSettings->bCreateCameras); UWorld* World = Cast(Sequencer->GetPlaybackContext()); - bool bValid = MovieSceneToolHelpers::ImportFBXIfReady(World, MovieScene, Sequencer, Sequencer->GetFocusedTemplateID(), ObjectBindingMap, ImportFBXSettings, InOutParams); + bool bValid = MovieSceneToolHelpers::ImportFBXIfReady(World, Sequence, Sequencer, Sequencer->GetFocusedTemplateID(), ObjectBindingMap, ImportFBXSettings, InOutParams); Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); @@ -1645,7 +1664,7 @@ private: TSharedPtr DetailView; FString ImportFilename; - UMovieScene* MovieScene; + UMovieSceneSequence* Sequence; ISequencer* Sequencer; TMap ObjectBindingMap; TOptional bCreateCameras; @@ -1679,9 +1698,11 @@ bool MovieSceneToolHelpers::ReadyFBXForImport(const FString& ImportFilename, UM } -bool MovieSceneToolHelpers::ImportFBXIfReady(UWorld* World, UMovieScene* MovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap& ObjectBindingMap, UMovieSceneUserImportFBXSettings* ImportFBXSettings, +bool MovieSceneToolHelpers::ImportFBXIfReady(UWorld* World, UMovieSceneSequence* Sequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap& ObjectBindingMap, UMovieSceneUserImportFBXSettings* ImportFBXSettings, const FFBXInOutParameters& InParams) { + UMovieScene* MovieScene = Sequence->GetMovieScene(); + UMovieSceneUserImportFBXSettings* CurrentImportFBXSettings = GetMutableDefault(); TArray OriginalSettings; FObjectWriter(CurrentImportFBXSettings, OriginalSettings); @@ -1719,7 +1740,7 @@ bool MovieSceneToolHelpers::ImportFBXIfReady(UWorld* World, UMovieScene* MovieSc { if (FCString::Strcmp(*It.Value().ToUpper(), *NodeName.ToUpper()) == 0) { - MovieSceneToolHelpers::ImportFBXNode(NodeName, CurveAPI, MovieScene, Player, TemplateID, It.Key()); + MovieSceneToolHelpers::ImportFBXNode(NodeName, CurveAPI, Sequence, Player, TemplateID, It.Key()); ObjectBindingMap.Remove(It.Key()); AllNodeNames.RemoveAt(NodeIndex); @@ -1752,7 +1773,7 @@ bool MovieSceneToolHelpers::ImportFBXIfReady(UWorld* World, UMovieScene* MovieSc auto It = ObjectBindingMap.CreateConstIterator(); if (It) { - MovieSceneToolHelpers::ImportFBXNode(NodeName, CurveAPI, MovieScene, Player, TemplateID, It.Key()); + MovieSceneToolHelpers::ImportFBXNode(NodeName, CurveAPI, Sequence, Player, TemplateID, It.Key()); UE_LOG(LogMovieScene, Warning, TEXT("Fbx Import: Failed to find any matching node for (%s). Defaulting to first available (%s)."), *NodeName, *It.Value()); ObjectBindingMap.Remove(It.Key()); @@ -1780,7 +1801,7 @@ bool MovieSceneToolHelpers::ImportFBXIfReady(UWorld* World, UMovieScene* MovieSc return true; } -bool MovieSceneToolHelpers::ImportFBXWithDialog(UMovieScene* InMovieScene, ISequencer& InSequencer, const TMap& InObjectBindingMap, TOptional bCreateCameras) +bool MovieSceneToolHelpers::ImportFBXWithDialog(UMovieSceneSequence* InSequence, ISequencer& InSequencer, const TMap& InObjectBindingMap, TOptional bCreateCameras) { TArray OpenFilenames; IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); @@ -1823,7 +1844,7 @@ bool MovieSceneToolHelpers::ImportFBXWithDialog(UMovieScene* InMovieScene, ISequ TSharedRef DialogWidget = SNew(SMovieSceneImportFBXSettings) .ImportFilename(OpenFilenames[0]) - .MovieScene(InMovieScene) + .Sequence(InSequence) .Sequencer(&InSequencer); DialogWidget->SetObjectBindingMap(InObjectBindingMap); DialogWidget->SetCreateCameras(bCreateCameras); diff --git a/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneToolsModule.cpp b/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneToolsModule.cpp index 1a893470e267..4192debfa90f 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneToolsModule.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneToolsModule.cpp @@ -72,9 +72,14 @@ #include "MovieSceneEventUtils.h" +#include "EntitySystem/MovieSceneEntityManager.h" #define LOCTEXT_NAMESPACE "FMovieSceneToolsModule" +#if !IS_MONOLITHIC + UE::MovieScene::FEntityManager*& GEntityManagerForDebugging = UE::MovieScene::GEntityManagerForDebuggingVisualizers; +#endif + void FMovieSceneToolsModule::StartupModule() { if (GIsEditor) @@ -162,6 +167,7 @@ void FMovieSceneToolsModule::StartupModule() FixupPayloadParameterNameHandle = UMovieSceneEventSectionBase::FixupPayloadParameterNameEvent.AddStatic(FixupPayloadParameterNameForSection); UMovieSceneEventSectionBase::UpgradeLegacyEventEndpoint.BindStatic(UpgradeLegacyEventEndpointForSection); + UMovieSceneEventSectionBase::PostDuplicateSectionEvent.BindStatic(PostDuplicateEventSection); auto OnObjectsReplaced = [](const TMap& ReplacedObjects) { @@ -199,6 +205,7 @@ void FMovieSceneToolsModule::ShutdownModule() { UMovieSceneEventSectionBase::FixupPayloadParameterNameEvent.Remove(FixupPayloadParameterNameHandle); UMovieSceneEventSectionBase::UpgradeLegacyEventEndpoint = UMovieSceneEventSectionBase::FUpgradeLegacyEventEndpoint(); + UMovieSceneEventSectionBase::PostDuplicateSectionEvent = UMovieSceneEventSectionBase::FPostDuplicateEvent(); if (ICurveEditorModule* CurveEditorModule = FModuleManager::GetModulePtr("CurveEditor")) { @@ -269,13 +276,35 @@ void FMovieSceneToolsModule::ShutdownModule() } } -bool FMovieSceneToolsModule::UpgradeLegacyEventEndpointForSection(UMovieSceneEventSectionBase* Section, UBlueprint* Blueprint) +void FMovieSceneToolsModule::PostDuplicateEventSection(UMovieSceneEventSectionBase* Section) { + UMovieSceneSequence* Sequence = Section->GetTypedOuter(); + FMovieSceneSequenceEditor* SequenceEditor = FMovieSceneSequenceEditor::Find(Sequence); + UBlueprint* SequenceDirectorBP = SequenceEditor ? SequenceEditor->FindDirectorBlueprint(Sequence) : nullptr; + + if (SequenceDirectorBP) + { + // Always bind the event section onto the blueprint to ensure that we get another chance to upgrade when the BP compiles if this try wasn't successful + FMovieSceneEventUtils::BindEventSectionToBlueprint(Section, SequenceDirectorBP); + } +} + +bool FMovieSceneToolsModule::UpgradeLegacyEventEndpointForSection(UMovieSceneEventSectionBase* Section) +{ + UMovieSceneSequence* Sequence = Section->GetTypedOuter(); + FMovieSceneSequenceEditor* SequenceEditor = FMovieSceneSequenceEditor::Find(Sequence); + UBlueprint* SequenceDirectorBP = SequenceEditor ? SequenceEditor->FindDirectorBlueprint(Sequence) : nullptr; + + if (!SequenceDirectorBP) + { + return true; + } + // Always bind the event section onto the blueprint to ensure that we get another chance to upgrade when the BP compiles if this try wasn't successful - FMovieSceneEventUtils::BindEventSectionToBlueprint(Section, Blueprint); + FMovieSceneEventUtils::BindEventSectionToBlueprint(Section, SequenceDirectorBP); // We can't do this upgrade if we any of the function graphs are RF_NeedLoad - for (UEdGraph* EdGraph : Blueprint->FunctionGraphs) + for (UEdGraph* EdGraph : SequenceDirectorBP->FunctionGraphs) { if (EdGraph->HasAnyFlags(RF_NeedLoad)) { @@ -286,29 +315,72 @@ bool FMovieSceneToolsModule::UpgradeLegacyEventEndpointForSection(UMovieSceneEve // All the function graphs have been loaded, which means this is a good time to perform legacy data upgrade for (FMovieSceneEvent& EntryPoint : Section->GetAllEntryPoints()) { - if (UK2Node_FunctionEntry* LegacyFunctionEntry = Cast(EntryPoint.FunctionEntry_DEPRECATED.Get())) - { - EntryPoint.GraphGuid = LegacyFunctionEntry->GetGraph()->GraphGuid; - } - - UK2Node* Endpoint = FMovieSceneEventUtils::FindEndpoint(&EntryPoint, Section, Blueprint); + UK2Node* Endpoint = CastChecked(EntryPoint.WeakEndpoint.Get(), ECastCheckedType::NullAllowed); if (!Endpoint) { - continue; - } - - // Discover its bound object pin name from the node - for (UEdGraphPin* Pin : Endpoint->Pins) - { - if (Pin->Direction == EGPD_Output && (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Interface) ) + if (UK2Node_FunctionEntry* LegacyFunctionEntry = Cast(EntryPoint.FunctionEntry_DEPRECATED.Get())) { - EntryPoint.BoundObjectPinName = Pin->PinName; - break; + EntryPoint.WeakEndpoint = Endpoint = LegacyFunctionEntry; + } + + // If we don't have an endpoint but do have legacy graph or node guids, we do the manual upgrade + if (!Endpoint && EntryPoint.GraphGuid_DEPRECATED.IsValid()) + { + if (EntryPoint.NodeGuid_DEPRECATED.IsValid()) + { + if (UEdGraph* const* GraphPtr = Algo::FindBy(SequenceDirectorBP->UbergraphPages, EntryPoint.GraphGuid_DEPRECATED, &UEdGraph::GraphGuid)) + { + UEdGraphNode* const* NodePtr = Algo::FindBy((*GraphPtr)->Nodes, EntryPoint.NodeGuid_DEPRECATED, &UEdGraphNode::NodeGuid); + if (NodePtr) + { + UK2Node_CustomEvent* CustomEvent = Cast(*NodePtr); + if (ensureMsgf(CustomEvent, TEXT("Encountered an event entry point node that is bound to something other than a custom event"))) + { + CustomEvent->OnUserDefinedPinRenamed().AddUObject(Section, &UMovieSceneEventSectionBase::OnUserDefinedPinRenamed); + EntryPoint.WeakEndpoint = Endpoint = CustomEvent; + } + } + } + } + // If the node guid is invalid, this must be a function graph on the BP + else if (UEdGraph* const* GraphPtr = Algo::FindBy(SequenceDirectorBP->FunctionGraphs, EntryPoint.GraphGuid_DEPRECATED, &UEdGraph::GraphGuid)) + { + UEdGraphNode* const* NodePtr = Algo::FindByPredicate((*GraphPtr)->Nodes, [](UEdGraphNode* InNode){ return InNode && InNode->IsA(); }); + if (NodePtr) + { + UK2Node_FunctionEntry* FunctionEntry = CastChecked(*NodePtr); + FunctionEntry->OnUserDefinedPinRenamed().AddUObject(Section, &UMovieSceneEventSectionBase::OnUserDefinedPinRenamed); + EntryPoint.WeakEndpoint = Endpoint = FunctionEntry; + } + } + + if (Endpoint) + { + // Discover its bound object pin name from the node + for (UEdGraphPin* Pin : Endpoint->Pins) + { + if (Pin->Direction == EGPD_Output && (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Interface) ) + { + EntryPoint.BoundObjectPinName = Pin->PinName; + break; + } + } + } } } // Set the compiled function name so that any immediate PostCompile steps find the correct function name - EntryPoint.CompiledFunctionName = Endpoint->GetGraph()->GetFName(); + if (Endpoint) + { + EntryPoint.CompiledFunctionName = Endpoint->GetGraph()->GetFName(); + } + } + + // If the BP has already been compiled (eg regenerate on load) we must perform PostCompile fixup immediately since + // We will not have had a chance to generate function entries. In this case we just bind directly to the already compiled functions. + if (SequenceDirectorBP->bHasBeenRegenerated) + { + Section->OnPostCompile(SequenceDirectorBP); } return true; @@ -318,18 +390,9 @@ void FMovieSceneToolsModule::FixupPayloadParameterNameForSection(UMovieSceneEven { check(Section && InNode); - UEdGraph* Graph = InNode->GetGraph(); - - const bool bCheckNodeGuid = InNode->IsA(); - for (FMovieSceneEvent& EntryPoint : Section->GetAllEntryPoints()) { - if (EntryPoint.GraphGuid != Graph->GraphGuid) - { - continue; - } - - if (bCheckNodeGuid && EntryPoint.NodeGuid != InNode->NodeGuid) + if (EntryPoint.WeakEndpoint.Get() != InNode) { continue; } diff --git a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/AudioTrackEditor.cpp b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/AudioTrackEditor.cpp index 833f37a9e753..9cf7fdb7dbd7 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/AudioTrackEditor.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/AudioTrackEditor.cpp @@ -84,8 +84,13 @@ USoundWave* DeriveSoundWave(UMovieSceneAudioSection* AudioSection) float DeriveUnloopedDuration(UMovieSceneAudioSection* AudioSection) { - USoundWave* SoundWave = DeriveSoundWave(AudioSection); + USoundBase* Sound = AudioSection->GetSound(); + if (Sound && Sound->GetDuration() != INDEFINITELY_LOOPING_DURATION) + { + return Sound->GetDuration(); + } + USoundWave* SoundWave = DeriveSoundWave(AudioSection); const float Duration = (SoundWave ? SoundWave->GetDuration() : 0.f); return Duration == INDEFINITELY_LOOPING_DURATION ? SoundWave->Duration : Duration; } diff --git a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/MaterialTrackEditor.cpp b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/MaterialTrackEditor.cpp index 9e6ef5521c5d..68d24faef686 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/MaterialTrackEditor.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/MaterialTrackEditor.cpp @@ -2,6 +2,7 @@ #include "TrackEditors/MaterialTrackEditor.h" #include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Components/DecalComponent.h" #include "Components/PrimitiveComponent.h" #include "Materials/Material.h" #include "Materials/MaterialInstance.h" @@ -212,18 +213,33 @@ UMaterialInterface* FComponentMaterialTrackEditor::GetMaterialInterfaceForTrack( return nullptr; } - UPrimitiveComponent* Component = Cast(SequencerPtr->FindSpawnedObjectOrTemplate( ObjectBinding )); UMovieSceneComponentMaterialTrack* ComponentMaterialTrack = Cast( MaterialTrack ); - if ( Component != nullptr && ComponentMaterialTrack != nullptr ) + if (!ComponentMaterialTrack) + { + return nullptr; + } + + UObject* Object = GetSequencer()->FindSpawnedObjectOrTemplate(ObjectBinding); + if (!Object) + { + return nullptr; + } + + if (UPrimitiveComponent* Component = Cast(Object)) { return Component->GetMaterial( ComponentMaterialTrack->GetMaterialIndex() ); } + else if (UDecalComponent* DecalComponent = Cast(Object)) + { + return DecalComponent->GetDecalMaterial(); + } + return nullptr; } void FComponentMaterialTrackEditor::ExtendObjectBindingTrackMenu(TSharedRef Extender, const TArray& ObjectBindings, const UClass* ObjectClass) { - if (ObjectClass->IsChildOf(UPrimitiveComponent::StaticClass())) + if (ObjectClass->IsChildOf(UPrimitiveComponent::StaticClass()) || ObjectClass->IsChildOf(UDecalComponent::StaticClass())) { Extender->AddMenuExtension(SequencerMenuExtensionPoints::AddTrackMenu_PropertiesSection, EExtensionHook::Before, nullptr, FMenuExtensionDelegate::CreateSP(this, &FComponentMaterialTrackEditor::ConstructObjectBindingTrackMenu, ObjectBindings)); } @@ -237,7 +253,13 @@ void FComponentMaterialTrackEditor::ConstructObjectBindingTrackMenu(FMenuBuilder return; } - if (UPrimitiveComponent* PrimitiveComponent = Cast(Object)) + USceneComponent* SceneComponent = Cast(Object); + if (!SceneComponent) + { + return; + } + + if (UPrimitiveComponent* PrimitiveComponent = Cast(SceneComponent)) { int32 NumMaterials = PrimitiveComponent->GetNumMaterials(); if (NumMaterials > 0) @@ -246,7 +268,7 @@ void FComponentMaterialTrackEditor::ConstructObjectBindingTrackMenu(FMenuBuilder { for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; MaterialIndex++) { - FUIAction AddComponentMaterialAction(FExecuteAction::CreateRaw(this, &FComponentMaterialTrackEditor::HandleAddComponentMaterialActionExecute, PrimitiveComponent, MaterialIndex)); + FUIAction AddComponentMaterialAction(FExecuteAction::CreateRaw(this, &FComponentMaterialTrackEditor::HandleAddComponentMaterialActionExecute, SceneComponent, MaterialIndex)); FText AddComponentMaterialLabel = FText::Format(LOCTEXT("ComponentMaterialIndexLabelFormat", "Element {0}"), FText::AsNumber(MaterialIndex)); FText AddComponentMaterialToolTip = FText::Format(LOCTEXT("ComponentMaterialIndexToolTipFormat", "Add material element {0}"), FText::AsNumber(MaterialIndex)); MenuBuilder.AddMenuEntry(AddComponentMaterialLabel, AddComponentMaterialToolTip, FSlateIcon(), AddComponentMaterialAction); @@ -255,9 +277,22 @@ void FComponentMaterialTrackEditor::ConstructObjectBindingTrackMenu(FMenuBuilder MenuBuilder.EndSection(); } } + else if (UDecalComponent* DecalComponent = Cast(SceneComponent)) + { + if (UMaterialInterface* DecalMaterial = DecalComponent->GetDecalMaterial()) + { + MenuBuilder.BeginSection("Materials", LOCTEXT("MaterialSection", "Material Parameters")); + { + FUIAction AddComponentMaterialAction(FExecuteAction::CreateRaw(this, &FComponentMaterialTrackEditor::HandleAddComponentMaterialActionExecute, SceneComponent, 0)); + FText AddDecalMaterialToolTip = FText::Format(LOCTEXT("AddDecalMaterialToolTipFormat", "Add decal material {0}"), FText::FromString(DecalMaterial->GetName())); + MenuBuilder.AddMenuEntry(FText::FromString(DecalMaterial->GetName()), AddDecalMaterialToolTip, FSlateIcon(), AddComponentMaterialAction); + } + MenuBuilder.EndSection(); + } + } } -void FComponentMaterialTrackEditor::HandleAddComponentMaterialActionExecute(UPrimitiveComponent* Component, int32 MaterialIndex) +void FComponentMaterialTrackEditor::HandleAddComponentMaterialActionExecute(USceneComponent* Component, int32 MaterialIndex) { TSharedPtr SequencerPtr = GetSequencer(); UMovieScene* MovieScene = SequencerPtr->GetFocusedMovieSceneSequence()->GetMovieScene(); diff --git a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/PropertyTrackEditors/EulerTransformPropertyTrackEditor.cpp b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/PropertyTrackEditors/EulerTransformPropertyTrackEditor.cpp index 6beb702b887a..db72531bffe0 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/PropertyTrackEditors/EulerTransformPropertyTrackEditor.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/PropertyTrackEditors/EulerTransformPropertyTrackEditor.cpp @@ -111,7 +111,7 @@ void FEulerTransformPropertyTrackEditor::ProcessKeyOperation(UObject* ObjectToKe } UMovieSceneEntitySystemLinker* EntityLinker = EvaluationTemplate.GetEntitySystemLinker(); - UMovieScenePropertyInstantiatorSystem* System = EntityLinker ? EntityLinker->SystemGraph.FindSystemOfType() : nullptr; + UMovieScenePropertyInstantiatorSystem* System = EntityLinker ? EntityLinker->FindSystem() : nullptr; if (System && ValidEntities.Num() != 0) { @@ -119,6 +119,8 @@ void FEulerTransformPropertyTrackEditor::ProcessKeyOperation(UObject* ObjectToKe Query.Entities = ValidEntities; Query.Object = ObjectToKey; + TGuardValue DebugVizGuard(GEntityManagerForDebuggingVisualizers, &EntityLinker->EntityManager); + FIntermediate3DTransform CurrentValue; ConvertOperationalProperty(Track->GetCurrentValue(ObjectToKey).Get(FEulerTransform::Identity), CurrentValue); TRecompositionResult TransformData = System->RecomposeBlendOperational(FMovieSceneTracksComponentTypes::Get()->EulerTransform, Query, CurrentValue); @@ -229,7 +231,9 @@ FEulerTransform FEulerTransformPropertyTrackEditor::RecomposeTransform(const FEu if (EntityLinker && EntityID) { - UMovieScenePropertyInstantiatorSystem* System = EntityLinker->SystemGraph.FindSystemOfType(); + TGuardValue DebugVizGuard(GEntityManagerForDebuggingVisualizers, &EntityLinker->EntityManager); + + UMovieScenePropertyInstantiatorSystem* System = EntityLinker->FindSystem(); if (System) { FDecompositionQuery Query; diff --git a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/PropertyTrackEditors/FloatPropertyTrackEditor.cpp b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/PropertyTrackEditors/FloatPropertyTrackEditor.cpp index a053132eb106..33a03a5b9c61 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/PropertyTrackEditors/FloatPropertyTrackEditor.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/PropertyTrackEditors/FloatPropertyTrackEditor.cpp @@ -71,7 +71,9 @@ float FFloatPropertyTrackEditor::RecomposeFloat(float InCurrentValue, UObject* A if (EntityLinker && EntityID) { - UMovieScenePropertyInstantiatorSystem* System = EntityLinker->SystemGraph.FindSystemOfType(); + TGuardValue DebugVizGuard(GEntityManagerForDebuggingVisualizers, &EntityLinker->EntityManager); + + UMovieScenePropertyInstantiatorSystem* System = EntityLinker->FindSystem(); if (System) { FDecompositionQuery Query; diff --git a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/PropertyTrackEditors/TransformPropertyTrackEditor.cpp b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/PropertyTrackEditors/TransformPropertyTrackEditor.cpp index f018302d476a..48cf9a879489 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/PropertyTrackEditors/TransformPropertyTrackEditor.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/PropertyTrackEditors/TransformPropertyTrackEditor.cpp @@ -82,7 +82,7 @@ UE::MovieScene::FIntermediate3DTransform FTransformPropertyTrackEditor::Recompos if (EntityLinker && EntityID) { - UMovieScenePropertyInstantiatorSystem* System = EntityLinker->SystemGraph.FindSystemOfType(); + UMovieScenePropertyInstantiatorSystem* System = EntityLinker->FindSystem(); if (System) { FDecompositionQuery Query; diff --git a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/TransformTrackEditor.cpp b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/TransformTrackEditor.cpp index 5f8a3e222a3b..0cc6b1782b0a 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/TransformTrackEditor.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/TransformTrackEditor.cpp @@ -28,6 +28,8 @@ #include "SequencerUtilities.h" #include "MovieSceneToolHelpers.h" +#include "EntitySystem/Interrogation/MovieSceneInterrogationLinker.h" +#include "EntitySystem/Interrogation/MovieSceneInterrogatedPropertyInstantiator.h" #include "Systems/MovieScenePropertyInstantiator.h" #include "MovieSceneTracksComponentTypes.h" @@ -814,7 +816,7 @@ FTransformData F3DTransformTrackEditor::RecomposeTransform(const FTransformData& if (EntityID) { - UMovieScenePropertyInstantiatorSystem* System = EntityLinker->SystemGraph.FindSystemOfType(); + UMovieScenePropertyInstantiatorSystem* System = EntityLinker->FindSystem(); if (System) { FDecompositionQuery Query; @@ -862,13 +864,30 @@ void F3DTransformTrackEditor::ProcessKeyOperation(UObject* ObjectToKey, TArrayVi using namespace UE::MovieScene; using namespace UE::Sequencer; - FMovieSceneSequenceID SequenceID = GetSequencer()->GetFocusedTemplateID(); - const FMovieSceneRootEvaluationTemplateInstance& EvaluationTemplate = GetSequencer()->GetEvaluationTemplate(); + FSystemInterrogator Interrogator; + Interrogator.TrackImportedEntities(true); + + TGuardValue DebugVizGuard(GEntityManagerForDebuggingVisualizers, &Interrogator.GetLinker()->EntityManager); + + TSet TracksToInterrogate; + for (const FKeySectionOperation& Operation : SectionsToKey) + { + if (UMovieSceneTrack* Track = Operation.Section->GetSectionObject()->GetTypedOuter()) + { + TracksToInterrogate.Add(Track); + } + } + + // Don't care about the object binding ID for now + Interrogator.ImportTracks(TracksToInterrogate.Array(), FGuid(), FInterrogationChannel::Default()); + Interrogator.AddInterrogation(KeyTime); + + Interrogator.Update(); TArray EntitiesPerSection, ValidEntities; for (const FKeySectionOperation& Operation : SectionsToKey) { - FMovieSceneEntityID EntityID = EvaluationTemplate.FindEntityFromOwner(Operation.Section->GetSectionObject(), 0, SequenceID); + FMovieSceneEntityID EntityID = Interrogator.FindEntityFromOwner(FInterrogationKey::Default(), Operation.Section->GetSectionObject(), 0); EntitiesPerSection.Add(EntityID); if (EntityID) @@ -877,17 +896,15 @@ void F3DTransformTrackEditor::ProcessKeyOperation(UObject* ObjectToKey, TArrayVi } } - UMovieSceneEntitySystemLinker* EntityLinker = EvaluationTemplate.GetEntitySystemLinker(); - UMovieScenePropertyInstantiatorSystem* System = EntityLinker ? EntityLinker->SystemGraph.FindSystemOfType() : nullptr; + UMovieSceneInterrogatedPropertyInstantiatorSystem* System = Interrogator.GetLinker()->FindSystem(); - if (System && ValidEntities.Num() != 0) + if (ensure(System && ValidEntities.Num() != 0)) { - TGuardValue DebugVizGuard(GEntityManagerForDebuggingVisualizers, &EntityLinker->EntityManager); - USceneComponent* Component = MovieSceneHelpers::SceneComponentFromRuntimeObject(ObjectToKey); FDecompositionQuery Query; Query.Entities = ValidEntities; + Query.bConvertFromSourceEntityIDs = false; Query.Object = Component; FIntermediate3DTransform CurrentValue(Component->GetRelativeLocation(), Component->GetRelativeRotation(), Component->GetRelativeScale3D()); diff --git a/Engine/Source/Editor/MovieSceneTools/Public/FCPXML/FCPXMLMovieSceneTranslator.h b/Engine/Source/Editor/MovieSceneTools/Public/FCPXML/FCPXMLMovieSceneTranslator.h index 8f347c90b817..53ecd02e65f4 100644 --- a/Engine/Source/Editor/MovieSceneTools/Public/FCPXML/FCPXMLMovieSceneTranslator.h +++ b/Engine/Source/Editor/MovieSceneTools/Public/FCPXML/FCPXMLMovieSceneTranslator.h @@ -83,7 +83,9 @@ public: * @param InSaveFilename The file path to save to. * @param OutError The return error message * @param MovieExtension The movie extension for the shot filenames (ie. .avi, .mov, .mp4) + * @param InMetadata (optional) Metadata from export to override movie output file list * @return Whether the export was successful */ - virtual bool Export(const UMovieScene* InMovieScene, FString InFilenameFormat, FFrameRate InFrameRate, uint32 InResX, uint32 InResY, int32 InHandleFrames, FString InSaveFilename, TSharedRef InContext, FString InMovieExtension); + + virtual bool Export(const UMovieScene* InMovieScene, FString InFilenameFormat, FFrameRate InFrameRate, uint32 InResX, uint32 InResY, int32 InHandleFrames, FString InSaveFilename, TSharedRef InContext, FString InMovieExtension, const FMovieSceneExportMetadata* InMetadata=nullptr); }; diff --git a/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneExportMetadata.h b/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneExportMetadata.h new file mode 100644 index 000000000000..a1c4adbd413c --- /dev/null +++ b/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneExportMetadata.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Sections/MovieSceneCinematicShotSection.h" + +struct FMovieSceneExportMetadataClip +{ + FMovieSceneExportMetadataClip() + : StartFrame(INT32_MAX) + , EndFrame(INT32_MIN) + , bHasAlpha(false) + {} + + bool IsValid() const { return EndFrame >= StartFrame; } + int32 GetDuration() const { return IsValid() ? EndFrame - StartFrame + 1 : 0; } + + int32 StartFrame; + int32 EndFrame; + bool bHasAlpha; + + FString FileName; +}; + +struct FMovieSceneExportMetadataShot +{ + TWeakObjectPtr MovieSceneShotSection; + int32 HandleFrames; + + // All of the clips for this shot, stored by ClipName + // Multiple formats may be exported, so each ClipName has a list metadata stored by extension + TMap< FString, TMap > Clips; +}; + +struct FMovieSceneExportMetadata +{ + TArray Shots; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneToolHelpers.h b/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneToolHelpers.h index 403b0fbaee8e..707bc5959b71 100644 --- a/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneToolHelpers.h +++ b/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneToolHelpers.h @@ -22,6 +22,7 @@ class ISequencer; class UMovieScene; class UMovieSceneSection; +class UMovieSceneSequence; class UInterpTrackMoveAxis; struct FMovieSceneObjectBindingID; class UMovieSceneTrack; @@ -246,7 +247,7 @@ public: * @param bCreateCameras Whether to allow creation of cameras if found in the fbx file. * @return Whether the import was successful */ - static bool ImportFBXWithDialog(UMovieScene* InMovieScene, ISequencer& InSequencer, const TMap& InObjectBindingNameMap, TOptional bCreateCameras); + static bool ImportFBXWithDialog(UMovieSceneSequence* InSequence, ISequencer& InSequencer, const TMap& InObjectBindingNameMap, TOptional bCreateCameras); /** * Get FBX Ready for Import. This make sure the passed in file make be imported. After calling this call ImportReadiedFbx. It returns out some parameters that we forcably change so we reset them later. @@ -271,7 +272,7 @@ public: * @return Whether the fbx file was ready and is ready to be import. */ - static bool ImportFBXIfReady(UWorld* World, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap& ObjectBindingMap, UMovieSceneUserImportFBXSettings* ImportFBXSettings, + static bool ImportFBXIfReady(UWorld* World, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap& ObjectBindingMap, UMovieSceneUserImportFBXSettings* ImportFBXSettings, const FFBXInOutParameters& InFBXParams); /** @@ -287,7 +288,7 @@ public: * @return Whether the import was successful */ - static void ImportFBXCameraToExisting(UnFbx::FFbxImporter* FbxImporter, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap& InObjectBindingMap, bool bMatchByNameOnly, bool bNotifySlate); + static void ImportFBXCameraToExisting(UnFbx::FFbxImporter* FbxImporter, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap& InObjectBindingMap, bool bMatchByNameOnly, bool bNotifySlate); /** * Import FBX Node to existing actor/node @@ -300,7 +301,7 @@ public: * @param ObjectBinding Guid of the object we are importing onto. * @return Whether the import was successful */ - static bool ImportFBXNode(FString NodeName, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, FGuid ObjectBinding); + static bool ImportFBXNode(FString NodeName, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, FGuid ObjectBinding); /** diff --git a/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneToolsModule.h b/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneToolsModule.h index 9da1cb47b813..f195c4a9c3ad 100644 --- a/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneToolsModule.h +++ b/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneToolsModule.h @@ -51,7 +51,8 @@ private: void RegisterClipboardConversions(); static void FixupPayloadParameterNameForSection(UMovieSceneEventSectionBase* Section, UK2Node* InNode, FName OldPinName, FName NewPinName); - static bool UpgradeLegacyEventEndpointForSection(UMovieSceneEventSectionBase* Section, UBlueprint* Blueprint); + static bool UpgradeLegacyEventEndpointForSection(UMovieSceneEventSectionBase* Section); + static void PostDuplicateEventSection(UMovieSceneEventSectionBase* Section); private: diff --git a/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneTranslator.h b/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneTranslator.h index a67415d448aa..2926865af64d 100644 --- a/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneTranslator.h +++ b/Engine/Source/Editor/MovieSceneTools/Public/MovieSceneTranslator.h @@ -13,6 +13,7 @@ #include "Logging/TokenizedMessage.h" #include "UObject/MetaData.h" #include "Sound/SoundBase.h" +#include "MovieSceneExportMetadata.h" /** * MovieSceneTranslator context class. @@ -413,8 +414,9 @@ public: * @param InSaveFilename The file path to save to. * @param OutError The return error message * @param MovieExtension The movie extension for the shot filenames (ie. .avi, .mov, .mp4) + * @param InMetadata (optional) Metadata from export to override movie output file list * @return Whether the export was successful */ - virtual bool Export(const UMovieScene* InMovieScene, FString InFilenameFormat, FFrameRate InFrameRate, uint32 InResX, uint32 InResY, int32 InHandleFrames, FString InSaveFilename, TSharedRef InContext, FString InMovieExtension) = 0; + virtual bool Export(const UMovieScene* InMovieScene, FString InFilenameFormat, FFrameRate InFrameRate, uint32 InResX, uint32 InResY, int32 InHandleFrames, FString InSaveFilename, TSharedRef InContext, FString InMovieExtension, const FMovieSceneExportMetadata* InMetadata=nullptr) = 0; }; diff --git a/Engine/Source/Editor/MovieSceneTools/Public/TrackEditors/MaterialTrackEditor.h b/Engine/Source/Editor/MovieSceneTools/Public/TrackEditors/MaterialTrackEditor.h index 169bcb49bbe3..a21e1b6477cd 100644 --- a/Engine/Source/Editor/MovieSceneTools/Public/TrackEditors/MaterialTrackEditor.h +++ b/Engine/Source/Editor/MovieSceneTools/Public/TrackEditors/MaterialTrackEditor.h @@ -15,6 +15,7 @@ class UMaterial; class UMaterialInterface; class UMovieSceneMaterialTrack; +class USceneComponent; /** * Track editor for material parameters. @@ -95,6 +96,6 @@ private: void ConstructObjectBindingTrackMenu(FMenuBuilder& MenuBuilder, TArray ObjectBindings); /** Callback for executing the add component material track. */ - void HandleAddComponentMaterialActionExecute(UPrimitiveComponent* Component, int32 MaterialIndex); + void HandleAddComponentMaterialActionExecute(USceneComponent* Component, int32 MaterialIndex); }; diff --git a/Engine/Source/Editor/Persona/Persona.Build.cs b/Engine/Source/Editor/Persona/Persona.Build.cs index 45460c6177e8..49903acbaeb0 100644 --- a/Engine/Source/Editor/Persona/Persona.Build.cs +++ b/Engine/Source/Editor/Persona/Persona.Build.cs @@ -33,7 +33,7 @@ public class Persona : ModuleRules "MeshReductionInterface", "SequenceRecorder", "AnimationBlueprintEditor", - } + } ); PrivateDependencyModuleNames.AddRange( @@ -73,7 +73,7 @@ public class Persona : ModuleRules "SequencerWidgets", "TimeManagement", "Sequencer", - } + } ); DynamicallyLoadedModuleNames.AddRange( @@ -87,7 +87,7 @@ public class Persona : ModuleRules "AnimationEditor", "MeshReductionInterface", "SequenceRecorder", - } + } ); } } diff --git a/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.cpp b/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.cpp index 54f0b5d3846c..7df855d66be5 100644 --- a/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.cpp +++ b/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.cpp @@ -47,6 +47,11 @@ #include "LODInfoUILayout.h" #include "IPersonaToolkit.h" #include "Interfaces/Interface_BoneReferenceSkeletonProvider.h" +#include "IPropertyAccessEditor.h" +#include "Algo/Accumulate.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "ScopedTransaction.h" #define LOCTEXT_NAMESPACE "KismetNodeWithOptionalPinsDetails" @@ -67,6 +72,8 @@ void FAnimGraphNodeDetails::CustomizeDetails(class IDetailLayoutBuilder& DetailB IDetailCategoryBuilder& PinOptionsCategory = DetailBuilder.EditCategory("PinOptions"); TSharedRef AvailablePins = DetailBuilder.GetProperty("ShowPinForProperties"); DetailBuilder.HideProperty(AvailablePins); + TSharedRef PropertyBindings = DetailBuilder.GetProperty("PropertyBindings"); + DetailBuilder.HideProperty(PropertyBindings); // get first animgraph nodes UAnimGraphNode_Base* AnimGraphNode = Cast(SelectedObjectsList[0].Get()); @@ -645,10 +652,10 @@ TSharedRef FBoneSocketTargetCustomization::MakeInsta void FBoneSocketTargetCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { // set property handle - SetPropertyHandle(StructPropertyHandle); - // set editable skeleton info from struct - SetEditableSkeleton(StructPropertyHandle); - Build(StructPropertyHandle, ChildBuilder); + SetPropertyHandle(StructPropertyHandle); + // set editable skeleton info from struct + SetEditableSkeleton(StructPropertyHandle); + Build(StructPropertyHandle, ChildBuilder); } void FBoneSocketTargetCustomization::SetPropertyHandle(TSharedRef StructPropertyHandle) @@ -1159,7 +1166,7 @@ void FPlayerTreeViewEntry::GenerateNameWidget(TSharedPtr Box) ]; } -void FAnimGraphNodeShowAsPinExtension::GetOptionalPinData(const IPropertyHandle& PropertyHandle, int32& OutOptionalPinIndex, UAnimGraphNode_Base*& OutAnimGraphNode) const +void FAnimGraphNodeBindingExtension::GetOptionalPinData(const IPropertyHandle& PropertyHandle, int32& OutOptionalPinIndex, UAnimGraphNode_Base*& OutAnimGraphNode) const { OutOptionalPinIndex = INDEX_NONE; @@ -1180,7 +1187,7 @@ void FAnimGraphNodeShowAsPinExtension::GetOptionalPinData(const IPropertyHandle& } } -bool FAnimGraphNodeShowAsPinExtension::IsPropertyExtendable(const UClass* InObjectClass, const IPropertyHandle& PropertyHandle) const +bool FAnimGraphNodeBindingExtension::IsPropertyExtendable(const UClass* InObjectClass, const IPropertyHandle& PropertyHandle) const { int32 OptionalPinIndex; UAnimGraphNode_Base* AnimGraphNode; @@ -1207,6 +1214,7 @@ bool FAnimGraphNodeShowAsPinExtension::IsPropertyExtendable(const UClass* InObje return false; } +// Legacy binding widget class SShowAsWidget : public SCompoundWidget { SLATE_BEGIN_ARGS(SShowAsWidget) {} @@ -1274,19 +1282,397 @@ class SShowAsWidget : public SCompoundWidget TSharedPtr PropertyHandle; }; -TSharedRef FAnimGraphNodeShowAsPinExtension::GenerateExtensionWidget(const IDetailLayoutBuilder& InDetailBuilder, const UClass* InObjectClass, TSharedPtr PropertyHandle) +static FText MakeTextPath(const TArray& InPath) +{ + return FText::FromString(Algo::Accumulate(InPath, FString(), [](const FString& InResult, const FString& InSegment) + { + return InResult.IsEmpty() ? InSegment : (InResult + TEXT(".") + InSegment); + })); +} + +TSharedRef FAnimGraphNodeBindingExtension::GenerateExtensionWidget(const IDetailLayoutBuilder& InDetailBuilder, const UClass* InObjectClass, TSharedPtr InPropertyHandle) { int32 OptionalPinIndex; UAnimGraphNode_Base* AnimGraphNode; - GetOptionalPinData(*PropertyHandle.Get(), OptionalPinIndex, AnimGraphNode); + GetOptionalPinData(*InPropertyHandle.Get(), OptionalPinIndex, AnimGraphNode); check(OptionalPinIndex != INDEX_NONE); + TArray OuterObjects; + InPropertyHandle->GetOuterObjects(OuterObjects); + + FProperty* AnimNodeProperty = InPropertyHandle->GetProperty(); + const FName PropertyName = AnimNodeProperty->GetFName(); + const FName OptionalPinArrayEntryName(*FString::Printf(TEXT("ShowPinForProperties[%d].bShowPin"), OptionalPinIndex)); - TSharedRef ShowHidePropertyHandle = InDetailBuilder.GetProperty(OptionalPinArrayEntryName, UAnimGraphNode_Base::StaticClass()); + TSharedRef ShowPinPropertyHandle = InDetailBuilder.GetProperty(OptionalPinArrayEntryName, UAnimGraphNode_Base::StaticClass()); + ShowPinPropertyHandle->MarkHiddenByCustomization(); - ShowHidePropertyHandle->MarkHiddenByCustomization(); + UBlueprint* Blueprint = AnimGraphNode->GetAnimBlueprint(); - return SNew(SShowAsWidget, ShowHidePropertyHandle); + if(IModularFeatures::Get().IsModularFeatureAvailable("PropertyAccessEditor")) + { + FPropertyBindingWidgetArgs Args; + + Args.Property = InPropertyHandle->GetProperty(); + + Args.OnCanBindProperty = FOnCanBindProperty::CreateLambda([AnimNodeProperty](FProperty* InProperty) + { + // Note: We support type promotion here + IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); + return PropertyAccessEditor.GetPropertyCompatibility(InProperty, AnimNodeProperty) != EPropertyAccessCompatibility::Incompatible; + }); + + Args.OnCanBindFunction = FOnCanBindFunction::CreateLambda([AnimNodeProperty](UFunction* InFunction) + { + IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); + + // Note: We support type promotion here + return InFunction->NumParms == 1 + && PropertyAccessEditor.GetPropertyCompatibility(InFunction->GetReturnProperty(), AnimNodeProperty) != EPropertyAccessCompatibility::Incompatible + && InFunction->HasAnyFunctionFlags(FUNC_BlueprintPure); + }); + + Args.OnCanBindToClass = FOnCanBindToClass::CreateLambda([](UClass* InClass) + { + return true; + }); + + Args.OnAddBinding = FOnAddBinding::CreateLambda([OuterObjects, Blueprint, ShowPinPropertyHandle, AnimNodeProperty](FName InPropertyName, const TArray& InBindingChain) + { + const UEdGraphSchema_K2* Schema = GetDefault(); + IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); + + for(UObject* OuterObject : OuterObjects) + { + if(UAnimGraphNode_Base* AnimGraphNode = Cast(OuterObject)) + { + AnimGraphNode->Modify(); + + const FFieldVariant& LeafField = InBindingChain.Last().Field; + + FAnimGraphNodePropertyBinding Binding; + Binding.PropertyName = InPropertyName; + PropertyAccessEditor.MakeStringPath(InBindingChain, Binding.PropertyPath); + Binding.PathAsText = MakeTextPath(Binding.PropertyPath); + Binding.Type = LeafField.IsA() ? EAnimGraphNodePropertyBindingType::Function : EAnimGraphNodePropertyBindingType::Property; + Binding.bIsBound = true; + if(LeafField.IsA()) + { + const FProperty* LeafProperty = LeafField.Get(); + if(LeafProperty) + { + if(PropertyAccessEditor.GetPropertyCompatibility(LeafProperty, AnimNodeProperty) == EPropertyAccessCompatibility::Promotable) + { + Binding.bIsPromotion = true; + Schema->ConvertPropertyToPinType(LeafProperty, Binding.PromotedPinType); + } + + Schema->ConvertPropertyToPinType(LeafProperty, Binding.PinType); + } + } + else if(LeafField.IsA()) + { + const UFunction* LeafFunction = LeafField.Get(); + if(LeafFunction) + { + if(FProperty* ReturnProperty = LeafFunction->GetReturnProperty()) + { + if(PropertyAccessEditor.GetPropertyCompatibility(ReturnProperty, AnimNodeProperty) == EPropertyAccessCompatibility::Promotable) + { + Binding.bIsPromotion = true; + Schema->ConvertPropertyToPinType(ReturnProperty, Binding.PromotedPinType); + } + + Schema->ConvertPropertyToPinType(ReturnProperty, Binding.PinType); + } + } + } + AnimGraphNode->PropertyBindings.Add(InPropertyName, Binding); + } + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); + } + + ShowPinPropertyHandle->SetValue(false); + }); + + Args.OnRemoveBinding = FOnRemoveBinding::CreateLambda([OuterObjects, Blueprint](FName InPropertyName) + { + for(UObject* OuterObject : OuterObjects) + { + if(UAnimGraphNode_Base* AnimGraphNode = Cast(OuterObject)) + { + AnimGraphNode->Modify(); + + AnimGraphNode->PropertyBindings.Remove(InPropertyName); + } + } + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); + }); + + Args.OnCanRemoveBinding = FOnCanRemoveBinding::CreateLambda([OuterObjects](FName InPropertyName) + { + for(UObject* OuterObject : OuterObjects) + { + if(UAnimGraphNode_Base* AnimGraphNode = Cast(OuterObject)) + { + if(AnimGraphNode->PropertyBindings.Contains(InPropertyName)) + { + return true; + } + } + } + + return false; + }); + + enum class ECurrentValueType : int32 + { + None, + Pin, + Binding, + MultipleValues, + }; + + Args.CurrentBindingText = MakeAttributeLambda([OuterObjects, PropertyName, ShowPinPropertyHandle]() + { + ECurrentValueType CurrentValueType = ECurrentValueType::None; + + const FText MultipleValues = LOCTEXT("MultipleValues", "Multiple Values"); + const FText Bind = LOCTEXT("Bind", "Bind"); + const FText ExposedAsPin = LOCTEXT("ExposedAsPin", "Exposed As Pin"); + FText CurrentValue = Bind; + + auto SetAssignValue = [&CurrentValueType, &CurrentValue, &MultipleValues](const FText& InValue, ECurrentValueType InType) + { + if(CurrentValueType != ECurrentValueType::MultipleValues) + { + if(CurrentValueType == ECurrentValueType::None) + { + CurrentValueType = InType; + CurrentValue = InValue; + } + else if(CurrentValueType == InType) + { + if(!CurrentValue.EqualTo(InValue)) + { + CurrentValueType = ECurrentValueType::MultipleValues; + CurrentValue = MultipleValues; + } + } + else + { + CurrentValueType = ECurrentValueType::MultipleValues; + CurrentValue = MultipleValues; + } + } + }; + + for(UObject* OuterObject : OuterObjects) + { + if(UAnimGraphNode_Base* AnimGraphNode = Cast(OuterObject)) + { + if(FAnimGraphNodePropertyBinding* BindingPtr = AnimGraphNode->PropertyBindings.Find(PropertyName)) + { + SetAssignValue(BindingPtr->PathAsText, ECurrentValueType::Binding); + } + else + { + bool bAsPin = false; + FPropertyAccess::Result Result = ShowPinPropertyHandle->GetValue(bAsPin); + if(Result == FPropertyAccess::MultipleValues) + { + SetAssignValue(MultipleValues, ECurrentValueType::MultipleValues); + } + else if(bAsPin) + { + SetAssignValue(ExposedAsPin, ECurrentValueType::Pin); + } + else + { + SetAssignValue(Bind, ECurrentValueType::None); + } + } + } + } + + return CurrentValue; + }); + + Args.CurrentBindingImage = MakeAttributeLambda([OuterObjects, PropertyName, OptionalPinIndex]() -> const FSlateBrush* + { + static FName PropertyIcon(TEXT("Kismet.Tabs.Variables")); + static FName FunctionIcon(TEXT("GraphEditor.Function_16x")); + + EAnimGraphNodePropertyBindingType BindingType = EAnimGraphNodePropertyBindingType::None; + for(UObject* OuterObject : OuterObjects) + { + if(UAnimGraphNode_Base* AnimGraphNode = Cast(OuterObject)) + { + if(AnimGraphNode->ShowPinForProperties[OptionalPinIndex].bShowPin) + { + BindingType = EAnimGraphNodePropertyBindingType::None; + break; + } + else if(FAnimGraphNodePropertyBinding* BindingPtr = AnimGraphNode->PropertyBindings.Find(PropertyName)) + { + if(BindingType == EAnimGraphNodePropertyBindingType::None) + { + BindingType = BindingPtr->Type; + } + else if(BindingType != BindingPtr->Type) + { + BindingType = EAnimGraphNodePropertyBindingType::None; + break; + } + } + else if(BindingType != EAnimGraphNodePropertyBindingType::None) + { + BindingType = EAnimGraphNodePropertyBindingType::None; + break; + } + } + } + + if (BindingType == EAnimGraphNodePropertyBindingType::Function) + { + return FEditorStyle::GetBrush(FunctionIcon); + } + else + { + return FEditorStyle::GetBrush(PropertyIcon); + } + }); + + Args.CurrentBindingColor = MakeAttributeLambda([OuterObjects, InPropertyHandle, OptionalPinIndex, PropertyName]() -> FLinearColor + { + const UEdGraphSchema_K2* Schema = GetDefault(); + + FEdGraphPinType PinType; + Schema->ConvertPropertyToPinType(InPropertyHandle->GetProperty(), PinType); + FLinearColor BindingColor = Schema->GetPinTypeColor(PinType); + + enum class EPromotionState + { + NotChecked, + NotPromoted, + Promoted, + } Promotion = EPromotionState::NotChecked; + + for(UObject* OuterObject : OuterObjects) + { + if(UAnimGraphNode_Base* AnimGraphNode = Cast(OuterObject)) + { + if(AnimGraphNode->ShowPinForProperties[OptionalPinIndex].bShowPin) + { + if(Promotion == EPromotionState::NotChecked) + { + Promotion = EPromotionState::NotPromoted; + } + else if(Promotion == EPromotionState::Promoted) + { + BindingColor = FLinearColor::Gray; + break; + } + } + else if(FAnimGraphNodePropertyBinding* BindingPtr = AnimGraphNode->PropertyBindings.Find(PropertyName)) + { + if(Promotion == EPromotionState::NotChecked) + { + if(BindingPtr->bIsPromotion) + { + Promotion = EPromotionState::Promoted; + BindingColor = Schema->GetPinTypeColor(BindingPtr->PromotedPinType); + } + else + { + Promotion = EPromotionState::NotPromoted; + } + } + else + { + EPromotionState NewPromotion = BindingPtr->bIsPromotion ? EPromotionState::Promoted : EPromotionState::NotPromoted; + if(Promotion != NewPromotion) + { + BindingColor = FLinearColor::Gray; + break; + } + } + } + } + } + + return BindingColor; + }); + + Args.MenuExtender = MakeShared(); + Args.MenuExtender->AddMenuExtension("BindingActions", EExtensionHook::Before, nullptr, FMenuExtensionDelegate::CreateLambda([ShowPinPropertyHandle, OuterObjects, PropertyName](FMenuBuilder& InMenuBuilder) + { + InMenuBuilder.BeginSection("Pins", LOCTEXT("Pin", "Pin")); + { + InMenuBuilder.AddMenuEntry( + LOCTEXT("ExposeAsPin", "Expose As Pin"), + LOCTEXT("ExposeAsPinTooltip", "Show/hide this property as a pin on the node"), + FSlateIcon("EditorStyle", "GraphEditor.PinIcon"), + FUIAction( + FExecuteAction::CreateLambda([ShowPinPropertyHandle, OuterObjects, PropertyName]() + { + bool bValue = false; + ShowPinPropertyHandle->GetValue(bValue); + + { + FScopedTransaction Transaction(bValue ? LOCTEXT("RemoveExposeAsPin", "Remove Expose As Pin") : LOCTEXT("ExposeAsPin", "Expose As Pin")); + + ShowPinPropertyHandle->SetValue(!bValue); + + // Switching from non-pin to pin, remove any bindings + for(UObject* OuterObject : OuterObjects) + { + if(UAnimGraphNode_Base* AnimGraphNode = Cast(OuterObject)) + { + AnimGraphNode->Modify(); + + AnimGraphNode->PropertyBindings.Remove(PropertyName); + } + } + } + }), + FCanExecuteAction(), + FGetActionCheckState::CreateLambda([ShowPinPropertyHandle]() + { + bool bValue; + FPropertyAccess::Result Result = ShowPinPropertyHandle->GetValue(bValue); + if(Result == FPropertyAccess::MultipleValues) + { + return ECheckBoxState::Undetermined; + } + else + { + return bValue ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + } + + return ECheckBoxState::Unchecked; + }) + ), + NAME_None, + EUserInterfaceActionType::Check + ); + } + InMenuBuilder.EndSection(); + })); + + Args.bAllowNewBindings = false; + Args.bAllowArrayElementBindings = true; + Args.bAllowUObjectFunctions = true; + + IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); + return PropertyAccessEditor.MakePropertyBindingWidget(AnimGraphNode->GetAnimBlueprint(), Args); + } + else + { + return SNew(SShowAsWidget, ShowPinPropertyHandle); + } } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.h b/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.h index 0b0bfbcbab08..37825f1be5ed 100644 --- a/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.h +++ b/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.h @@ -24,7 +24,7 @@ class USkeleton; class IEditableSkeleton; struct FAnimParentNodeAssetOverride; -class FAnimGraphNodeShowAsPinExtension : public IDetailPropertyExtensionHandler +class FAnimGraphNodeBindingExtension : public IDetailPropertyExtensionHandler { public: // IDetailPropertyExtensionHandler interface diff --git a/Engine/Source/Editor/Persona/Private/PersonaModule.cpp b/Engine/Source/Editor/Persona/Private/PersonaModule.cpp index f2b20af3efb8..7cf01848aa79 100644 --- a/Engine/Source/Editor/Persona/Private/PersonaModule.cpp +++ b/Engine/Source/Editor/Persona/Private/PersonaModule.cpp @@ -845,7 +845,7 @@ void FPersonaModule::CustomizeBlueprintEditorDetails(const TSharedRefRegisterInstancedCustomPropertyLayout(UAnimGraphNode_Slot::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FAnimGraphNodeSlotDetails::MakeInstance, InOnInvokeTab)); - InDetailsView->SetExtensionHandler(MakeShared()); + InDetailsView->SetExtensionHandler(MakeShared()); } IPersonaEditorModeManager* FPersonaModule::CreatePersonaEditorModeManager() diff --git a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditor.cpp b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditor.cpp index 2e0f8d002e3f..3d51e581c657 100644 --- a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditor.cpp +++ b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditor.cpp @@ -112,6 +112,7 @@ FPhysicsAssetEditor::~FPhysicsAssetEditor() } GEditor->UnregisterForUndo(this); + GEditor->GetEditorSubsystem()->OnAssetReimport.Remove(OnAssetReimportDelegateHandle); } void FPhysicsAssetEditor::InitPhysicsAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UPhysicsAsset* ObjectToEdit) @@ -162,6 +163,9 @@ void FPhysicsAssetEditor::InitPhysicsAssetEditor(const EToolkitMode::Type Mode, GEditor->RegisterForUndo(this); + // If any assets we care about get reimported, we need to rebuild some stuff + OnAssetReimportDelegateHandle = GEditor->GetEditorSubsystem()->OnAssetReimport.AddSP(this, &FPhysicsAssetEditor::OnAssetReimport); + // Register our commands. This will only register them if not previously registered FPhysicsAssetEditorCommands::Register(); @@ -391,6 +395,21 @@ void FPhysicsAssetEditor::PostRedo( bool bSuccess ) PostUndo(bSuccess); } +void FPhysicsAssetEditor::OnAssetReimport(UObject* Object) +{ + RecreatePhysicsState(); + RefreshHierachyTree(); + RefreshPreviewViewport(); + + if (SharedData->EditorSkelComp && SharedData->EditorSkelComp->SkeletalMesh) + { + IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked("MeshUtilities"); + // Update various infos based on the mesh + MeshUtilities.CalcBoneVertInfos(SharedData->EditorSkelComp->SkeletalMesh, SharedData->DominantWeightBoneInfos, true); + MeshUtilities.CalcBoneVertInfos(SharedData->EditorSkelComp->SkeletalMesh, SharedData->AnyWeightBoneInfos, false); + } +} + void FPhysicsAssetEditor::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) { FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None; diff --git a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditor.h b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditor.h index 3eabfa6dffba..9b3e4e3018de 100644 --- a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditor.h +++ b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditor.h @@ -157,6 +157,9 @@ private: virtual void PostRedo(bool bSuccess) override; // End of FEditorUndoClient + /** Called when an asset has just been imported */ + void OnAssetReimport(UObject* Object); + /** Builds the menu for the PhysicsAsset editor */ void ExtendMenu(); @@ -357,6 +360,9 @@ private: /** Command list for viewport operations */ TSharedPtr ViewportCommandList; + /** To unregister reimport handler */ + FDelegateHandle OnAssetReimportDelegateHandle; + void FixPhysicsState(); void ImpToggleSimulation(); diff --git a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorEditMode.cpp b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorEditMode.cpp index dc20669f1cfc..268aaed4f54f 100644 --- a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorEditMode.cpp +++ b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorEditMode.cpp @@ -31,7 +31,7 @@ FPhysicsAssetEditorEditMode::FPhysicsAssetEditorEditMode() , PhysicsAssetEditor_TranslateSpeed(0.25f) , PhysicsAssetEditor_RotateSpeed(1.0f * (PI / 180.0f)) , PhysicsAssetEditor_LightRotSpeed(0.22f) - , SimGrabCheckDistance(5000.0f) + , SimGrabCheckDistance(500.0f) , SimHoldDistanceChangeDelta(20.0f) , SimMinHoldDistance(10.0f) , SimGrabMoveSpeed(1.0f) @@ -802,23 +802,28 @@ bool FPhysicsAssetEditorEditMode::SimMousePress(FEditorViewportClient* InViewpor FSceneView* View = InViewportClient->CalcSceneView(&ViewFamily); const FViewportClick Click(View, InViewportClient, EKeys::Invalid, IE_Released, Viewport->GetMouseX(), Viewport->GetMouseY()); -#if DEBUG_CLICK_VIEWPORT + FHitResult Result(1.f); + bool bHit = SharedData->EditorSkelComp->LineTraceComponent(Result, Click.GetOrigin(), Click.GetOrigin() + Click.GetDirection() * SimGrabCheckDistance, FCollisionQueryParams(NAME_None, true)); + + SharedData->LastClickPos = Click.GetClickPos(); SharedData->LastClickOrigin = Click.GetOrigin(); SharedData->LastClickDirection = Click.GetDirection(); -#endif - SharedData->LastClickPos = Click.GetClickPos(); - FHitResult Result(1.f); - bool bHit = SharedData->EditorSkelComp->LineTraceComponent(Result, Click.GetOrigin() - Click.GetDirection() * SimGrabCheckDistance, Click.GetOrigin() + Click.GetDirection() * SimGrabCheckDistance, FCollisionQueryParams(NAME_None, true)); + SharedData->bLastClickHit = bHit; + if (bHit) + { + SharedData->LastClickHitPos = Result.Location; + SharedData->LastClickHitNormal = Result.Normal; + } if (bHit) { + check(Result.Item != INDEX_NONE); + FName BoneName = SharedData->PhysicsAsset->SkeletalBodySetups[Result.Item]->BoneName; + + UE_LOG(LogPhysics, Log, TEXT("Physics Asset Editor Click Hit Bone (%s)"), *BoneName.ToString()); + if(bCtrlDown || bShiftDown) { - check(Result.Item != INDEX_NONE); - FName BoneName = SharedData->PhysicsAsset->SkeletalBodySetups[Result.Item]->BoneName; - - //UE_LOG(LogPhysics, Warning, TEXT("Hit Bone Name (%s)"), *BoneName.ToString()); - // Right mouse is for dragging things around if (Key == EKeys::RightMouseButton) { @@ -852,11 +857,12 @@ bool FPhysicsAssetEditorEditMode::SimMousePress(FEditorViewportClient* InViewpor SharedData->EditorSkelComp->AddImpulseAtLocation(Click.GetDirection() * SharedData->EditorOptions->PokeStrength, Result.Location, BoneName); } } - - return true; } - return false; + // @todo(ccaulfield): really this should return false if we don't have Ctrl or Shift help down. This would allow the mouse-fly + // behaviour to work even when clicking on a space occupied by a body. However we don't want to change this until we have a way + // to enable/disable the fly and orbit camera behaviours. + return bHit; } void FPhysicsAssetEditorEditMode::SimMouseMove(FEditorViewportClient* InViewportClient, float DeltaX, float DeltaY) diff --git a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSharedData.cpp b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSharedData.cpp index a346c28ed5d0..bdc7b662315e 100644 --- a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSharedData.cpp +++ b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSharedData.cpp @@ -68,6 +68,13 @@ FPhysicsAssetEditorSharedData::FPhysicsAssetEditorSharedData() bNoGravitySimulation = false; bManipulating = false; + + LastClickPos = FIntPoint::ZeroValue; + LastClickOrigin = FVector::ZeroVector; + LastClickDirection = FVector::UpVector; + LastClickHitPos = FVector::ZeroVector; + LastClickHitNormal = FVector::UpVector; + bLastClickHit = false; // Construct mouse handle MouseHandle = NewObject(); diff --git a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSharedData.h b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSharedData.h index 0ece565180f3..8e97fd4ba4df 100644 --- a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSharedData.h +++ b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSharedData.h @@ -19,8 +19,6 @@ struct FBoneVertInfo; class IPersonaPreviewScene; class FPhysicsAssetEditorSharedData; -#define DEBUG_CLICK_VIEWPORT 0 - /** Scoped object that blocks selection broadcasts until it leaves scope */ struct FScopedBulkSelection { @@ -317,9 +315,10 @@ public: FTransform ResetTM; -#if DEBUG_CLICK_VIEWPORT + FIntPoint LastClickPos; FVector LastClickOrigin; FVector LastClickDirection; -#endif - FIntPoint LastClickPos; + FVector LastClickHitPos; + FVector LastClickHitNormal; + bool bLastClickHit; }; diff --git a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSkeletalMeshComponent.cpp b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSkeletalMeshComponent.cpp index 896eb2c7c02c..d06d21643db4 100644 --- a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSkeletalMeshComponent.cpp +++ b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSkeletalMeshComponent.cpp @@ -24,6 +24,10 @@ namespace // How large to make the constraint arrows. // The factor of 60 was found experimentally, to look reasonable in comparison with the rest of the constraint visuals. constexpr float ConstraintArrowScale = 60.0f; + + bool bDebugViewportClicks = false; + FAutoConsoleVariableRef CVarChaosImmPhysStepTime(TEXT("p.PhAT.DebugViewportClicks"), bDebugViewportClicks, TEXT("Set to 1 to show mouse click results in PhAT")); + } UPhysicsAssetEditorSkeletalMeshComponent::UPhysicsAssetEditorSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer) @@ -87,10 +91,13 @@ void UPhysicsAssetEditorSkeletalMeshComponent::RenderAssetTools(const FSceneView EPhysicsAssetEditorRenderMode CollisionViewMode = SharedData->GetCurrentCollisionViewMode(SharedData->bRunningSimulation); -#if DEBUG_CLICK_VIEWPORT - PDI->DrawLine(SharedData->LastClickOrigin, SharedData->LastClickOrigin + SharedData->LastClickDirection * 5000.0f, FLinearColor(1, 1, 0, 1), SDPG_Foreground); - PDI->DrawPoint(SharedData->LastClickOrigin, FLinearColor(1, 0, 0), 5, SDPG_Foreground); -#endif + if (bDebugViewportClicks) + { + PDI->DrawLine(SharedData->LastClickOrigin, SharedData->LastClickOrigin + SharedData->LastClickDirection * 5000.0f, FLinearColor(1, 1, 0, 1), SDPG_Foreground); + PDI->DrawPoint(SharedData->LastClickOrigin, FLinearColor(1, 1, 0), 5, SDPG_Foreground); + PDI->DrawLine(SharedData->LastClickHitPos, SharedData->LastClickHitPos + SharedData->LastClickHitNormal * 10.0f, FLinearColor(1, 0, 0, 1), SDPG_Foreground); + PDI->DrawPoint(SharedData->LastClickHitPos, FLinearColor(1, 0, 0), 5, SDPG_Foreground); + } // set opacity of our materials static FName OpacityName(TEXT("Opacity")); @@ -515,6 +522,18 @@ void UPhysicsAssetEditorSkeletalMeshComponent::AddImpulseAtLocation(FVector Impu #endif } +bool UPhysicsAssetEditorSkeletalMeshComponent::ShouldCreatePhysicsState() const +{ +#if !WITH_CHAOS + return Super::ShouldCreatePhysicsState(); +#else + // Chaos uses a RigidBody AnimNode to run the simulation in the PhysicsAsset Editor. + // See FPhysicsAssetEditorAnimInstanceProxy + return false; +#endif +} + + void UPhysicsAssetEditorSkeletalMeshComponent::Grab(FName InBoneName, const FVector& Location, const FRotator& Rotation, bool bRotationConstrained) { UPhysicsAssetEditorAnimInstance* PhatPreviewInstance = Cast(PreviewInstance); diff --git a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSkeletalMeshComponent.h b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSkeletalMeshComponent.h index 42403e2f840a..9927608aeca4 100644 --- a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSkeletalMeshComponent.h +++ b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSkeletalMeshComponent.h @@ -53,6 +53,7 @@ class UPhysicsAssetEditorSkeletalMeshComponent : public UDebugSkelMeshComponent /** UPrimitiveComponent interface */ virtual FPrimitiveSceneProxy* CreateSceneProxy() override; virtual void AddImpulseAtLocation(FVector Impulse, FVector Location, FName BoneName = NAME_None) override; + virtual bool ShouldCreatePhysicsState() const override; /** USkinnedMeshComponent interface */ virtual void RefreshBoneTransforms(FActorComponentTickFunction* TickFunction = nullptr) override; diff --git a/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.cpp b/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.cpp index ed6abdda7499..56c4a0ab2a58 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.cpp @@ -182,6 +182,16 @@ FDetailWidgetRow& FDetailPropertyRow::CustomWidget( bool bShowChildren ) return *CustomPropertyWidget; } +FDetailWidgetDecl* FDetailPropertyRow::CustomNameWidget() +{ + return CustomPropertyWidget.IsValid() ? &CustomPropertyWidget->NameContent() : nullptr; +} + +FDetailWidgetDecl* FDetailPropertyRow::CustomValueWidget() +{ + return CustomPropertyWidget.IsValid() ? &CustomPropertyWidget->ValueContent() : nullptr; +} + TSharedPtr FDetailPropertyRow::GetThumbnailPool() const { TSharedPtr ParentCategoryPinned = ParentCategory.Pin(); diff --git a/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.h b/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.h index 162eefb1083d..76d166fb6d96 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.h +++ b/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.h @@ -36,6 +36,8 @@ public: virtual IDetailPropertyRow& Visibility( TAttribute Visibility ) override; virtual IDetailPropertyRow& OverrideResetToDefault(const FResetToDefaultOverride& ResetToDefault) override; virtual FDetailWidgetRow& CustomWidget( bool bShowChildren = false ) override; + virtual FDetailWidgetDecl* CustomNameWidget() override; + virtual FDetailWidgetDecl* CustomValueWidget() override; virtual void GetDefaultWidgets( TSharedPtr& OutNameWidget, TSharedPtr& OutValueWidget, bool bAddWidgetDecoration = false) override; virtual void GetDefaultWidgets( TSharedPtr& OutNameWidget, TSharedPtr& OutValueWidget, FDetailWidgetRow& Row, bool bAddWidgetDecoration = false) override; diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.cpp b/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.cpp index 020d59d9667a..4622e84b4c4b 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.cpp @@ -11,12 +11,6 @@ DEFINE_LOG_CATEGORY_STATIC(LogEditCondition, Log, All); FEditConditionContext::FEditConditionContext(FPropertyNode& InPropertyNode) { PropertyNode = InPropertyNode.AsShared(); - - FComplexPropertyNode* ComplexParentNode = InPropertyNode.FindComplexParent(); - check(ComplexParentNode); - - const FProperty* Property = InPropertyNode.GetProperty(); - check(Property); } const FBoolProperty* FEditConditionContext::GetSingleBoolProperty(const TSharedPtr& Expression) const @@ -137,6 +131,10 @@ TOptional FEditConditionContext::GetBoolValue(const FString& PropertyName) } FComplexPropertyNode* ComplexParentNode = PinnedNode->FindComplexParent(); + if (ComplexParentNode == nullptr) + { + return TOptional(); + } TOptional Result; for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) @@ -188,6 +186,10 @@ TOptional FEditConditionContext::GetIntegerValue(const FString& PropertyN } FComplexPropertyNode* ComplexParentNode = PinnedNode->FindComplexParent(); + if (ComplexParentNode == nullptr) + { + return TOptional(); + } TOptional Result; for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) @@ -234,6 +236,10 @@ TOptional FEditConditionContext::GetNumericValue(const FString& Property } FComplexPropertyNode* ComplexParentNode = PinnedNode->FindComplexParent(); + if (ComplexParentNode == nullptr) + { + return TOptional(); + } TOptional Result; for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) @@ -312,6 +318,10 @@ TOptional FEditConditionContext::GetEnumValue(const FString& PropertyNa } FComplexPropertyNode* ComplexParentNode = PinnedNode->FindComplexParent(); + if (ComplexParentNode == nullptr) + { + return TOptional(); + } TOptional Result; for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) @@ -369,6 +379,10 @@ TOptional FEditConditionContext::GetPointerValue(const FString& Proper } FComplexPropertyNode* ComplexParentNode = PinnedNode->FindComplexParent(); + if (ComplexParentNode == nullptr) + { + return TOptional(); + } TOptional Result; for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp b/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp index fae54aa89865..f16154375027 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp @@ -1134,14 +1134,12 @@ public: void InnerInitialize() { - { - PropertyValueRoot.OwnerObject = NULL; - PropertyDefaultValueRoot.OwnerObject = NULL; - PropertyValueAddress = NULL; - PropertyValueBaseAddress = NULL; - PropertyDefaultBaseAddress = NULL; - PropertyDefaultAddress = NULL; - } + PropertyValueRoot.OwnerObject = NULL; + PropertyDefaultValueRoot.OwnerObject = NULL; + PropertyValueAddress = NULL; + PropertyValueBaseAddress = NULL; + PropertyDefaultBaseAddress = NULL; + PropertyDefaultAddress = NULL; PropertyValueRoot.OwnerObject = OwnerObject.Get(); check(PropertyNode); @@ -1149,7 +1147,7 @@ public: check(Property); check(PropertyValueRoot.OwnerObject); - FPropertyNode* ParentNode = PropertyNode->GetParentNode(); + FPropertyNode* ParentNode = PropertyNode->GetParentNode(); // if the object specified is a class object, transfer to the CDO instead if ( Cast(PropertyValueRoot.OwnerObject) != NULL ) @@ -1790,11 +1788,7 @@ bool FPropertyNode::GetDiffersFromDefaultForObject( FPropertyItemValueDataTracke uint32 PortFlags = 0; if (InProperty->ContainsInstancedObjectProperty()) { - // Use PPF_DeepCompareInstances for component objects - if (CastField(InProperty)) - { - PortFlags |= PPF_DeepCompareInstances; - } + PortFlags |= PPF_DeepCompareInstances; } if ( ValueTracker.GetPropertyValueAddress() == NULL || ValueTracker.GetPropertyDefaultAddress() == NULL ) @@ -1894,11 +1888,7 @@ FString FPropertyNode::GetDefaultValueAsStringForObject( FPropertyItemValueDataT if (InProperty->ContainsInstancedObjectProperty()) { - // Use PPF_DeepCompareInstances for component objects - if (CastField(InProperty)) - { - PortFlags |= PPF_DeepCompareInstances; - } + PortFlags |= PPF_DeepCompareInstances; } if ( ValueTracker.GetPropertyDefaultAddress() == NULL ) @@ -2062,13 +2052,31 @@ void FPropertyNode::FilterNodes( const TArray& InFilterStrings, const b //if filtering, default to NOT-seen bool bPassedFilter = false; //assuming that we aren't filtered - //see if this is a filter-able primitive + // Populate name aliases acceptable for searching / filtering FText DisplayName = GetDisplayName(); const FString& DisplayNameStr = DisplayName.ToString(); TArray AcceptableNames; AcceptableNames.Add(DisplayNameStr); - //get the basic name as well of the property + // For containers, check if base class metadata in parent includes 'TitleProperty', add corresponding value to filter names if so. + static const FName TitlePropertyFName = FName(TEXT("TitleProperty")); + if (ParentNode && ParentNode->GetProperty()) + { + const FString& TitleProperty = ParentNode->GetProperty()->GetMetaData(TitlePropertyFName); + if (!TitleProperty.IsEmpty()) + { + if (TSharedPtr TitlePropertyNode = FindChildPropertyNode(*TitleProperty, true)) + { + FString TitlePropertyValue; + if (TitlePropertyNode->GetPropertyValueString(TitlePropertyValue, true /*bAllowAlternateDisplayValue*/) != FPropertyAccess::Result::Fail) + { + AcceptableNames.Add(TitlePropertyValue); + } + } + } + } + + // Get the basic name as well of the property FProperty* TheProperty = GetProperty(); if (TheProperty && (TheProperty->GetName() != DisplayNameStr)) { diff --git a/Engine/Source/Editor/PropertyEditor/Public/IDetailPropertyRow.h b/Engine/Source/Editor/PropertyEditor/Public/IDetailPropertyRow.h index d2dd885c5990..c625c5111291 100644 --- a/Engine/Source/Editor/PropertyEditor/Public/IDetailPropertyRow.h +++ b/Engine/Source/Editor/PropertyEditor/Public/IDetailPropertyRow.h @@ -9,6 +9,8 @@ #include "PropertyHandle.h" class FDetailWidgetRow; +class FDetailWidgetDecl; + DECLARE_DELEGATE_RetVal_OneParam(bool, FIsResetToDefaultVisible, TSharedPtr /* PropertyHandle */); DECLARE_DELEGATE_OneParam(FResetToDefaultHandler, TSharedPtr /* PropertyHandle*/); @@ -184,10 +186,24 @@ public: virtual void GetDefaultWidgets( TSharedPtr& OutNameWidget, TSharedPtr& OutValueWidget, FDetailWidgetRow& Row, bool bAddWidgetDecoration = false ) = 0; /** - * Overrides the property widget + * Overrides the property widget. Destroys any existing custom property widgets. * * @param bShowChildren Whether or not to still show any children of this property * @return a row for the property that custom widgets can be added to */ virtual FDetailWidgetRow& CustomWidget( bool bShowChildren = false ) = 0; + + /** + * Gives a non-owning pointer to name widget on existing custom property widget if it exists. + * + * @return Pointer to name widget if custom property widget already exists, nullptr otherwise + */ + virtual FDetailWidgetDecl* CustomNameWidget() = 0; + + /** + * Gives a non-owning pointer to value widget on existing custom property widget if it exists. + * + * @return Pointer to value widget if custom property widget already exists, nullptr otherwise + */ + virtual FDetailWidgetDecl* CustomValueWidget() = 0; }; diff --git a/Engine/Source/Editor/SequenceRecorder/Private/AnimationRecorder.cpp b/Engine/Source/Editor/SequenceRecorder/Private/AnimationRecorder.cpp index b48d7a1bf0d8..abaee783dfa8 100644 --- a/Engine/Source/Editor/SequenceRecorder/Private/AnimationRecorder.cpp +++ b/Engine/Source/Editor/SequenceRecorder/Private/AnimationRecorder.cpp @@ -322,9 +322,9 @@ UAnimSequence* FAnimationRecorder::StopRecord(bool bShowMessage) for (int32 FrameIndex = 0; FrameIndex < NumFrames; ++FrameIndex) { const float TimeToRecord = FrameIndex*IntervalTime; - if(RecordedCurves[FrameIndex].IsValidIndex(CurveIndex)) + if(RecordedCurves[FrameIndex].ValidCurveWeights[CurveIndex]) { - FCurveElement& CurCurve = RecordedCurves[FrameIndex][CurveIndex]; + float CurCurveValue = RecordedCurves[FrameIndex].CurveWeights[CurveIndex]; if (!bSeenThisCurve) { bSeenThisCurve = true; @@ -334,7 +334,7 @@ UAnimSequence* FAnimationRecorder::StopRecord(bool bShowMessage) if (SkeletonObj->GetSmartNameByUID(USkeleton::AnimCurveMappingName, CurveUID, CurveName)) { // give default curve flag for recording - AnimationObject->RawCurveData.AddFloatCurveKey(CurveName, AACF_DefaultCurve, TimeToRecord, CurCurve.Value); + AnimationObject->RawCurveData.AddFloatCurveKey(CurveName, AACF_DefaultCurve, TimeToRecord, CurCurveValue); FloatCurveData = static_cast(AnimationObject->RawCurveData.GetCurveData(CurveUID, ERawCurveTrackTypes::RCT_Float)); } } @@ -342,7 +342,7 @@ UAnimSequence* FAnimationRecorder::StopRecord(bool bShowMessage) if (FloatCurveData) { TimesToRecord[FrameIndex] = TimeToRecord; - ValuesToRecord[FrameIndex] = CurCurve.Value; + ValuesToRecord[FrameIndex] = CurCurveValue; } } } @@ -516,7 +516,7 @@ void FAnimationRecorder::UpdateRecord(USkeletalMeshComponent* Component, float D BlendedComponentToWorld.Blend(PreviousComponentToWorld, Component->GetComponentTransform(), BlendAlpha); FBlendedHeapCurve BlendedCurve; - if (AnimCurves.Elements.Num() > 0 && PreviousAnimCurves.Elements.Num() == AnimCurves.Elements.Num() && PreviousAnimCurves.IsValid() && AnimCurves.IsValid()) + if (AnimCurves.CurveWeights.Num() > 0 && PreviousAnimCurves.CurveWeights.Num() == AnimCurves.CurveWeights.Num() && PreviousAnimCurves.IsValid() && AnimCurves.IsValid()) { BlendedCurve.Lerp(PreviousAnimCurves, AnimCurves, BlendAlpha); } @@ -640,9 +640,9 @@ bool FAnimationRecorder::Record(USkeletalMeshComponent* Component, FTransform co AnimationSerializer->WriteFrameData(AnimationSerializer->FramesWritten, SerializedAnimation); } // each RecordedCurves contains all elements - if (AnimationCurves.Elements.Num() > 0) + if (AnimationCurves.CurveWeights.Num() > 0) { - RecordedCurves.Add(AnimationCurves.Elements); + RecordedCurves.Emplace(AnimationCurves.CurveWeights, AnimationCurves.ValidCurveWeights); if (UIDToArrayIndexLUT == nullptr) { UIDToArrayIndexLUT = AnimationCurves.UIDToArrayIndexLUT; diff --git a/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorder.cpp b/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorder.cpp index ea9d2c47468e..b7b52f540273 100644 --- a/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorder.cpp +++ b/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorder.cpp @@ -365,9 +365,9 @@ void FSequenceRecorder::Tick(float DeltaSeconds) } // if a replay recording is in progress and channels are paused, wait until we have data again before recording - if(CurrentReplayWorld.IsValid() && CurrentReplayWorld->DemoNetDriver != nullptr) + if (UDemoNetDriver* DemoNetDriver = CurrentReplayWorld.IsValid() ? CurrentReplayWorld->GetDemoNetDriver() : nullptr) { - if(CurrentReplayWorld->DemoNetDriver->bChannelsArePaused) + if (DemoNetDriver->GetChannelsArePaused()) { return; } diff --git a/Engine/Source/Editor/SequenceRecorder/Public/AnimationRecorder.h b/Engine/Source/Editor/SequenceRecorder/Public/AnimationRecorder.h index d8199aac418a..7b047be5627b 100644 --- a/Engine/Source/Editor/SequenceRecorder/Public/AnimationRecorder.h +++ b/Engine/Source/Editor/SequenceRecorder/Public/AnimationRecorder.h @@ -103,7 +103,20 @@ private: void FixupNotifies(); // recording curve data - TArray< TArray > RecordedCurves; + struct FBlendedCurve + { + template + FBlendedCurve(TArray CW, TBitArray VCW) + { + CurveWeights = CW; + ValidCurveWeights = VCW; + } + + TArray CurveWeights; + TBitArray<> ValidCurveWeights; + }; + + TArray RecordedCurves; TArray const * UIDToArrayIndexLUT; }; diff --git a/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerRootNode.h b/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerRootNode.h index 2abc089397ae..39034d2ba294 100644 --- a/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerRootNode.h +++ b/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerRootNode.h @@ -20,7 +20,9 @@ public: */ explicit FSequencerRootNode(FSequencerNodeTree& InParentTree) : FSequencerDisplayNode(NAME_None, InParentTree) - {} + { + bExpanded = true; + } public: diff --git a/Engine/Source/Editor/Sequencer/Private/LevelEditorSequencerIntegration.cpp b/Engine/Source/Editor/Sequencer/Private/LevelEditorSequencerIntegration.cpp index 62ee9f0b0c3e..63e6af235754 100644 --- a/Engine/Source/Editor/Sequencer/Private/LevelEditorSequencerIntegration.cpp +++ b/Engine/Source/Editor/Sequencer/Private/LevelEditorSequencerIntegration.cpp @@ -1217,6 +1217,7 @@ void FLevelEditorSequencerIntegration::AddSequencer(TSharedRef InSeq ActivateRealtimeViewports(); AttachOutlinerColumn(); + OnSequencersChanged.Broadcast(); } void FLevelEditorSequencerIntegration::OnSequencerReceivedFocus(TSharedRef InSequencer) @@ -1251,6 +1252,20 @@ void FLevelEditorSequencerIntegration::RemoveSequencer(TSharedRef In { AcquiredResources.Release(); } + + OnSequencersChanged.Broadcast(); +} + +TArray> FLevelEditorSequencerIntegration::GetSequencers() +{ + TArray> SequencerPtrs; + SequencerPtrs.Reserve(BoundSequencers.Num()); + for (FSequencerAndOptions& SequencerAndOption : BoundSequencers) + { + SequencerPtrs.Add(SequencerAndOption.Sequencer); + } + + return SequencerPtrs; } void AddActorsToBindingsMapRecursive(FSequencer& Sequencer, UMovieSceneSequence* Sequence, FMovieSceneSequenceIDRef SequenceID, const FMovieSceneSequenceHierarchy* Hierarchy, TMap& ActorBindingsMap) diff --git a/Engine/Source/Editor/Sequencer/Private/SSequencer.cpp b/Engine/Source/Editor/Sequencer/Private/SSequencer.cpp index e8f56dd70f7c..2beee53e6b87 100644 --- a/Engine/Source/Editor/Sequencer/Private/SSequencer.cpp +++ b/Engine/Source/Editor/Sequencer/Private/SSequencer.cpp @@ -2434,32 +2434,6 @@ TSharedRef SSequencer::MakePlaybackMenu() } MenuBuilder.AddMenuEntry(FSequencerCommands::Get().ToggleLinkCurveEditorTimeRange); - - // Menu entry for zero padding - auto OnZeroPadChanged = [=](uint8 NewValue){ - GetSequencerSettings()->SetZeroPadFrames(NewValue); - }; - - MenuBuilder.AddWidget( - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(SSpacer) - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SSpinBox) - .Style(&FEditorStyle::GetWidgetStyle("Sequencer.HyperlinkSpinBox")) - .OnValueCommitted_Lambda([=](uint8 Value, ETextCommit::Type){ OnZeroPadChanged(Value); }) - .OnValueChanged_Lambda(OnZeroPadChanged) - .MinValue(0) - .MaxValue(8) - .Value_Lambda([=]() -> uint8 { - return GetSequencerSettings()->GetZeroPadFrames(); - }) - ], - LOCTEXT("ZeroPaddingText", "Zero Pad Frame Numbers")); } MenuBuilder.EndSection(); diff --git a/Engine/Source/Editor/Sequencer/Private/SSequencerSection.cpp b/Engine/Source/Editor/Sequencer/Private/SSequencerSection.cpp index 88f9579c3ee1..d70b5c330ddc 100644 --- a/Engine/Source/Editor/Sequencer/Private/SSequencerSection.cpp +++ b/Engine/Source/Editor/Sequencer/Private/SSequencerSection.cpp @@ -6,6 +6,7 @@ #include "SequencerSelectionPreview.h" #include "SequencerSettings.h" #include "Editor.h" +#include "ScopedTransaction.h" #include "Sequencer.h" #include "SequencerSectionPainter.h" #include "MovieSceneSequence.h" @@ -15,6 +16,7 @@ #include "ISequencerHotspot.h" #include "SequencerHotspots.h" #include "Widgets/SOverlay.h" +#include "SequencerAddKeyOperation.h" #include "SequencerObjectBindingNode.h" #include "SequencerSectionCategoryNode.h" #include "SequencerSectionKeyAreaNode.h" @@ -808,6 +810,14 @@ void SSequencerSection::CreateKeysUnderMouse( const FVector2D& MousePosition, co FGeometry SectionGeometry = MakeSectionGeometryWithoutHandles( AllottedGeometry, SectionInterface ); + FTimeToPixel TimeToPixelConverter = ConstructTimeConverterForSection(SectionGeometry, Section, GetSequencer()); + FVector2D LocalSpaceMousePosition = SectionGeometry.AbsoluteToLocal( MousePosition ); + const FFrameTime CurrentTime = TimeToPixelConverter.PixelToFrame(LocalSpaceMousePosition.X); + + ISequencerTrackEditor& TrackEditor = ParentSectionArea->GetTrackEditor(); + + TArray> ValidKeyAreasUnderCursor; + // Search every key area until we find the one under the mouse for (const FSectionLayoutElement& Element : Layout->GetElements()) { @@ -820,22 +830,19 @@ void SSequencerSection::CreateKeysUnderMouse( const FVector2D& MousePosition, co continue; } - FTimeToPixel TimeToPixelConverter = ConstructTimeConverterForSection(SectionGeometry, Section, GetSequencer()); - - FVector2D LocalSpaceMousePosition = SectionGeometry.AbsoluteToLocal( MousePosition ); - const FFrameTime KeyTime = TimeToPixelConverter.PixelToFrame(LocalSpaceMousePosition.X); - - Section.Modify(); - for (TSharedPtr KeyArea : Element.GetKeyAreas()) { if (KeyArea.IsValid()) { - FKeyHandle NewHandle = KeyArea->AddOrUpdateKey(KeyTime.FrameNumber, ObjectBinding, GetSequencer()); - OutKeys.Add(FSequencerSelectedKey(Section, KeyArea, NewHandle)); + ValidKeyAreasUnderCursor.Add(KeyArea.ToSharedRef()); } } } + + using namespace UE::Sequencer; + + FScopedTransaction Transaction(NSLOCTEXT("Sequencer", "CreateKeysUnderMouse", "Create keys under mouse")); + FAddKeyOperation::FromKeyAreas(&TrackEditor, ValidKeyAreasUnderCursor).Commit(CurrentTime.FrameNumber, GetSequencer()); } if (OutKeys.Num()) diff --git a/Engine/Source/Editor/Sequencer/Private/Sequencer.cpp b/Engine/Source/Editor/Sequencer/Private/Sequencer.cpp index 73fea704ca47..26cbffb058dc 100644 --- a/Engine/Source/Editor/Sequencer/Private/Sequencer.cpp +++ b/Engine/Source/Editor/Sequencer/Private/Sequencer.cpp @@ -539,6 +539,7 @@ FSequencer::FSequencer() , bUpdatingSequencerSelection( false ) , bUpdatingExternalSelection( false ) , bNeedsEvaluate(false) + , bNeedsInvalidateCachedData(false) , bHasPreAnimatedInfo(false) { Selection.GetOnOutlinerNodeSelectionChanged().AddRaw(this, &FSequencer::OnSelectedOutlinerNodesChanged); @@ -615,6 +616,12 @@ void FSequencer::Tick(float InDeltaTime) ensureAlwaysMsgf(SequencerRefCount == 1, TEXT("Multiple persistent shared references detected for Sequencer. There should only be one persistent authoritative reference. Found %d additional references which will result in FSequencer not being released correctly."), SequencerRefCount - 1); } + if (bNeedsInvalidateCachedData) + { + InvalidateCachedData(); + bNeedsInvalidateCachedData = false; + } + // Ensure the time bases for our playback position are kept up to date with the root sequence UpdateTimeBases(); @@ -2108,7 +2115,7 @@ void FSequencer::BakeTransform() CameraComponent->GetAdditiveOffset(AdditiveOffset, AdditiveFOVOffset); FTransform Transform(Actor->GetActorRotation(), Actor->GetActorLocation()); - FTransform TransformWithAdditiveOffset = Transform * AdditiveOffset; + FTransform TransformWithAdditiveOffset = AdditiveOffset * Transform; FVector LocalTranslation = TransformWithAdditiveOffset.GetTranslation(); FRotator LocalRotation = TransformWithAdditiveOffset.GetRotation().Rotator(); @@ -3752,26 +3759,28 @@ void FSequencer::UpdateCameraCut(UObject* CameraObject, const EMovieSceneCameraC continue; } - if ((CameraObject != nullptr) || LevelVC->IsLockedToActor(UnlockIfCameraActor)) + if (CameraObject == nullptr && UnlockIfCameraActor != nullptr && !LevelVC->IsLockedToActor(UnlockIfCameraActor)) { - if (bShouldCachePreAnimatedViewportInfo) - { - PreAnimatedViewportLocation = LevelVC->GetViewLocation(); - PreAnimatedViewportRotation = LevelVC->GetViewRotation(); - PreAnimatedViewportFOV = LevelVC->ViewFOV; - bHasPreAnimatedInfo = true; - - // We end-up only caching the first cinematic viewport's info, which means that - // if we are previewing the sequence on 2 different viewports, the second viewport - // will blend back to the same camera position as the first viewport, even if they - // started at different positions (which is very likely). It's a small downside to - // pay for a much simpler piece of code, and for a use-case that is frankly - // probably very uncommon. - bShouldCachePreAnimatedViewportInfo = false; - } - - UpdatePreviewLevelViewportClientFromCameraCut(*LevelVC, CameraObject, CameraCutParams); + continue; } + + if (bShouldCachePreAnimatedViewportInfo) + { + PreAnimatedViewportLocation = LevelVC->GetViewLocation(); + PreAnimatedViewportRotation = LevelVC->GetViewRotation(); + PreAnimatedViewportFOV = LevelVC->ViewFOV; + bHasPreAnimatedInfo = true; + + // We end-up only caching the first cinematic viewport's info, which means that + // if we are previewing the sequence on 2 different viewports, the second viewport + // will blend back to the same camera position as the first viewport, even if they + // started at different positions (which is very likely). It's a small downside to + // pay for a much simpler piece of code, and for a use-case that is frankly + // probably very uncommon. + bShouldCachePreAnimatedViewportInfo = false; + } + + UpdatePreviewLevelViewportClientFromCameraCut(*LevelVC, CameraObject, CameraCutParams); } // Clear pre-animated info when we exit any sequencer camera. @@ -5164,21 +5173,9 @@ FGuid FSequencer::AddSpawnable(UObject& Object, UActorFactory* ActorFactory) } FNewSpawnable& NewSpawnable = Result.GetValue(); - - auto DuplName = [&](FMovieSceneSpawnable& InSpawnable) - { - return InSpawnable.GetName() == NewSpawnable.Name; - }; - - int32 Index = 2; - FString UniqueString; - while (OwnerMovieScene->FindSpawnable(DuplName)) - { - NewSpawnable.Name.RemoveFromEnd(UniqueString); - UniqueString = FString::Printf(TEXT(" (%d)"), Index++); - NewSpawnable.Name += UniqueString; - } - + + NewSpawnable.Name = MovieSceneHelpers::MakeUniqueSpawnableName(OwnerMovieScene, NewSpawnable.Name); + FGuid NewGuid = OwnerMovieScene->AddSpawnable(NewSpawnable.Name, *NewSpawnable.ObjectTemplate); ForceEvaluate(); @@ -5607,7 +5604,10 @@ void FSequencer::OnNewActorsDropped(const TArray& DroppedObjects, cons if (bAddSpawnable) { + FString NewCameraName = MovieSceneHelpers::MakeUniqueSpawnableName(OwnerMovieScene, FName::NameToDisplayString(ACineCameraActor::StaticClass()->GetFName().ToString(), false)); + FMovieSceneSpawnable* Spawnable = ConvertToSpawnableInternal(NewCameraGuid)[0]; + Spawnable->SetName(NewCameraName); for (TWeakObjectPtr<> WeakObject : FindBoundObjects(Spawnable->GetGuid(), ActiveTemplateIDs.Top())) { @@ -5618,6 +5618,8 @@ void FSequencer::OnNewActorsDropped(const TArray& DroppedObjects, cons } } + NewCamera->SetActorLabel(NewCameraName, false); + NewCameraGuid = Spawnable->GetGuid(); // Create an attach track @@ -10451,17 +10453,14 @@ void FSequencer::AddSelectedActors() void FSequencer::SetKey() { - using namespace UE::Sequencer; - - FScopedTransaction SetKeyTransaction( NSLOCTEXT("Sequencer", "SetKey_Transaction", "Set Key") ); - - const FFrameNumber KeyTime = GetLocalTime().Time.FrameNumber; - if (Selection.GetSelectedOutlinerNodes().Num() == 0) - { - FAddKeyOperation::FromNode(NodeTree->GetRootNode()).Commit(KeyTime, *this); - } - else + if (Selection.GetSelectedOutlinerNodes().Num() > 0) { + using namespace UE::Sequencer; + + FScopedTransaction SetKeyTransaction( NSLOCTEXT("Sequencer", "SetKey_Transaction", "Set Key") ); + + const FFrameNumber KeyTime = GetLocalTime().Time.FrameNumber; + FAddKeyOperation::FromNodes(Selection.GetSelectedOutlinerNodes()).Commit(KeyTime, *this); } } @@ -11155,14 +11154,17 @@ void FSequencer::CreateCamera() if (bCreateAsSpawnable) { + FString NewName = MovieSceneHelpers::MakeUniqueSpawnableName(FocusedMovieScene, FName::NameToDisplayString(ACineCameraActor::StaticClass()->GetFName().ToString(), false)); + CameraGuid = MakeNewSpawnable(*NewCamera); - Spawnable = GetFocusedMovieSceneSequence()->GetMovieScene()->FindSpawnable(CameraGuid); + Spawnable = FocusedMovieScene->FindSpawnable(CameraGuid); if (ensure(Spawnable)) { // Override spawn ownership during this process to ensure it never gets destroyed SavedOwnership = Spawnable->GetSpawnOwnership(); Spawnable->SetSpawnOwnership(ESpawnOwnership::External); + Spawnable->SetName(NewName); } // Destroy the old actor @@ -11177,6 +11179,8 @@ void FSequencer::CreateCamera() } } ensure(NewCamera); + + NewCamera->SetActorLabel(NewName, false); } else { @@ -11352,8 +11356,6 @@ void FSequencer::RebindPossessableReferences() void FSequencer::ImportFBX() { - UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene(); - TMap ObjectBindingNameMap; TArray> RootObjectBindingNodes; @@ -11366,13 +11368,11 @@ void FSequencer::ImportFBX() ObjectBindingNameMap.Add(ObjectBinding, RootObjectBindingNode.Get().GetDisplayName().ToString()); } - MovieSceneToolHelpers::ImportFBXWithDialog(MovieScene, *this, ObjectBindingNameMap, TOptional()); + MovieSceneToolHelpers::ImportFBXWithDialog(GetFocusedMovieSceneSequence(), *this, ObjectBindingNameMap, TOptional()); } void FSequencer::ImportFBXOntoSelectedNodes() { - UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene(); - // The object binding and names to match when importing from fbx TMap ObjectBindingNameMap; @@ -11388,7 +11388,7 @@ void FSequencer::ImportFBXOntoSelectedNodes() } } - MovieSceneToolHelpers::ImportFBXWithDialog(MovieScene, *this, ObjectBindingNameMap, TOptional(false)); + MovieSceneToolHelpers::ImportFBXWithDialog(GetFocusedMovieSceneSequence(), *this, ObjectBindingNameMap, TOptional(false)); } void FSequencer::ExportFBX() diff --git a/Engine/Source/Editor/Sequencer/Private/Sequencer.h b/Engine/Source/Editor/Sequencer/Private/Sequencer.h index 731cf1d01555..1208cd939a45 100644 --- a/Engine/Source/Editor/Sequencer/Private/Sequencer.h +++ b/Engine/Source/Editor/Sequencer/Private/Sequencer.h @@ -728,6 +728,8 @@ public: virtual void SetLocalTime(FFrameTime Time, ESnapTimeMode SnapTimeMode = ESnapTimeMode::STM_None) override; virtual void SetLocalTimeDirectly(FFrameTime NewTime) override; virtual void SetGlobalTime(FFrameTime Time) override; + virtual void RequestInvalidateCachedData() override { bNeedsInvalidateCachedData = true; } + virtual void RequestEvaluate() override { bNeedsEvaluate = true; } virtual void ForceEvaluate() override; virtual void SetPerspectiveViewportPossessionEnabled(bool bEnabled) override; virtual void SetPerspectiveViewportCameraCutEnabled(bool bEnabled) override; @@ -1348,8 +1350,12 @@ private: /** Event contexts retrieved from the above attribute once per frame */ TArray> CachedEventContexts; + /** When true, sequence will be forcefully evaluated on the next tick */ bool bNeedsEvaluate; + /** When true, cached data will be invalidated on the next tick */ + bool bNeedsInvalidateCachedData; + FAcquiredResources AcquiredResources; bool bGlobalMarkedFramesCached; diff --git a/Engine/Source/Editor/Sequencer/Private/SequencerAddKeyOperation.cpp b/Engine/Source/Editor/Sequencer/Private/SequencerAddKeyOperation.cpp index 9ad617c99fe1..2267fd633e76 100644 --- a/Engine/Source/Editor/Sequencer/Private/SequencerAddKeyOperation.cpp +++ b/Engine/Source/Editor/Sequencer/Private/SequencerAddKeyOperation.cpp @@ -53,6 +53,19 @@ FAddKeyOperation FAddKeyOperation::FromNode(TSharedRef In return Operation; } +FAddKeyOperation FAddKeyOperation::FromKeyAreas(ISequencerTrackEditor* TrackEditor, const TArrayView> InKeyAreas) +{ + FAddKeyOperation Operation; + if (ensure(TrackEditor)) + { + for (const TSharedRef KeyArea : InKeyAreas) + { + Operation.ProcessKeyArea(TrackEditor, KeyArea); + } + } + return Operation; +} + void FAddKeyOperation::AddPreFilteredNodes(TArrayView> FilteredNodes) { auto KeyChildTrackArea = [this](FSequencerDisplayNode& InNode) @@ -121,16 +134,21 @@ bool FAddKeyOperation::ProcessKeyAreaNode(FSequencerTrackNode* InTrackNode, cons return bKeyedAnything; } -bool FAddKeyOperation::ProcessKeyArea(FSequencerTrackNode* InTrackNode, TSharedPtr KeyArea) +bool FAddKeyOperation::ProcessKeyArea(FSequencerTrackNode* InTrackNode, TSharedPtr InKeyArea) { - TSharedPtr Section = KeyArea->GetSectionInterface(); + ISequencerTrackEditor* TrackEditor = &InTrackNode->GetTrackEditor(); + return ProcessKeyArea(TrackEditor, InKeyArea); +} + +bool FAddKeyOperation::ProcessKeyArea(ISequencerTrackEditor* InTrackEditor, TSharedPtr InKeyArea) +{ + TSharedPtr Section = InKeyArea->GetSectionInterface(); UMovieSceneSection* SectionObject = Section ? Section->GetSectionObject() : nullptr; UMovieSceneTrack* TrackObject = SectionObject ? SectionObject->GetTypedOuter() : nullptr; if (TrackObject) { - ISequencerTrackEditor* TrackEditor = &InTrackNode->GetTrackEditor(); - GetTrackOperation(TrackEditor).Populate(TrackObject, Section, KeyArea); + GetTrackOperation(InTrackEditor).Populate(TrackObject, Section, InKeyArea); return true; } diff --git a/Engine/Source/Editor/Sequencer/Private/SequencerContextMenus.cpp b/Engine/Source/Editor/Sequencer/Private/SequencerContextMenus.cpp index 3d43d120016e..b08d8a1cf9df 100644 --- a/Engine/Source/Editor/Sequencer/Private/SequencerContextMenus.cpp +++ b/Engine/Source/Editor/Sequencer/Private/SequencerContextMenus.cpp @@ -32,6 +32,7 @@ #include "Widgets/Input/SCheckBox.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Layout/SBox.h" +#include "Widgets/Layout/SSpacer.h" #include "ISequencerChannelInterface.h" #include "Channels/MovieSceneChannelProxy.h" #include "SKeyEditInterface.h" @@ -513,7 +514,82 @@ void FSectionContextMenu::AddEditMenu(FMenuBuilder& MenuBuilder) FExecuteAction::CreateLambda([=]{ Shared->AutoSizeSection(); }), FCanExecuteAction::CreateLambda([=]{ return Shared->CanAutoSize(); })) ); - + + MenuBuilder.AddMenuEntry( + LOCTEXT("SyncSectionsUsingSourceTimecode", "Synchronize using Source Timecode"), + LOCTEXT("SyncSectionsUsingSourceTimecodeTooltip", "Sync selected sections using the source timecode. The first selected section will be unchanged and subsequent sections will be adjusted according to their source timecode as relative to the first section's."), + FSlateIcon(), + FUIAction( + FExecuteAction::CreateLambda([=] { return Shared->Sequencer->SyncSectionsUsingSourceTimecode(); }), + FCanExecuteAction::CreateLambda([=]{ return (Shared->Sequencer->GetSelection().GetSelectedSections().Num() > 1); })) + ); + + MenuBuilder.BeginSection("SequencerInterpolation", LOCTEXT("KeyInterpolationMenu", "Key Interpolation")); + + MenuBuilder.AddMenuEntry( + LOCTEXT("SetKeyInterpolationAuto", "Cubic (Auto)"), + LOCTEXT("SetKeyInterpolationAutoTooltip", "Set key interpolation to auto"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Sequencer.IconKeyAuto"), + FUIAction( + FExecuteAction::CreateLambda([=]{ Shared->SetInterpTangentMode(RCIM_Cubic, RCTM_Auto); }), + FCanExecuteAction::CreateLambda([=]{ return Shared->CanSetInterpTangentMode(); }), + FIsActionChecked::CreateLambda([=]{ return Shared->IsInterpTangentModeSelected(RCIM_Cubic, RCTM_Auto); }) ), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + + MenuBuilder.AddMenuEntry( + LOCTEXT("SetKeyInterpolationUser", "Cubic (User)"), + LOCTEXT("SetKeyInterpolationUserTooltip", "Set key interpolation to user"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Sequencer.IconKeyUser"), + FUIAction( + FExecuteAction::CreateLambda([=]{ Shared->SetInterpTangentMode(RCIM_Cubic, RCTM_User); }), + FCanExecuteAction::CreateLambda([=]{ return Shared->CanSetInterpTangentMode(); }), + FIsActionChecked::CreateLambda([=]{ return Shared->IsInterpTangentModeSelected(RCIM_Cubic, RCTM_User); }) ), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + + MenuBuilder.AddMenuEntry( + LOCTEXT("SetKeyInterpolationBreak", "Cubic (Break)"), + LOCTEXT("SetKeyInterpolationBreakTooltip", "Set key interpolation to break"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Sequencer.IconKeyBreak"), + FUIAction( + FExecuteAction::CreateLambda([=]{ Shared->SetInterpTangentMode(RCIM_Cubic, RCTM_Break); }), + FCanExecuteAction::CreateLambda([=]{ return Shared->CanSetInterpTangentMode(); }), + FIsActionChecked::CreateLambda([=]{ return Shared->IsInterpTangentModeSelected(RCIM_Cubic, RCTM_Break); }) ), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + + MenuBuilder.AddMenuEntry( + LOCTEXT("SetKeyInterpolationLinear", "Linear"), + LOCTEXT("SetKeyInterpolationLinearTooltip", "Set key interpolation to linear"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Sequencer.IconKeyLinear"), + FUIAction( + FExecuteAction::CreateLambda([=]{ Shared->SetInterpTangentMode(RCIM_Linear, RCTM_Auto); }), + FCanExecuteAction::CreateLambda([=]{ return Shared->CanSetInterpTangentMode(); }), + FIsActionChecked::CreateLambda([=]{ return Shared->IsInterpTangentModeSelected(RCIM_Linear, RCTM_Auto); }) ), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + + MenuBuilder.AddMenuEntry( + LOCTEXT("SetKeyInterpolationConstant", "Constant"), + LOCTEXT("SetKeyInterpolationConstantTooltip", "Set key interpolation to constant"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Sequencer.IconKeyConstant"), + FUIAction( + FExecuteAction::CreateLambda([=]{ Shared->SetInterpTangentMode(RCIM_Constant, RCTM_Auto); }), + FCanExecuteAction::CreateLambda([=]{ return Shared->CanSetInterpTangentMode(); }), + FIsActionChecked::CreateLambda([=]{ return Shared->IsInterpTangentModeSelected(RCIM_Constant, RCTM_Auto); }) ), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + + MenuBuilder.EndSection(); // SequencerInterpolation + + MenuBuilder.BeginSection("Key Editing", LOCTEXT("KeyEditingSectionMenus", "Key Editing")); + MenuBuilder.AddMenuEntry( LOCTEXT("ReduceKeysSection", "Reduce Keys"), LOCTEXT("ReduceKeysTooltip", "Reduce keys in this section"), @@ -523,14 +599,32 @@ void FSectionContextMenu::AddEditMenu(FMenuBuilder& MenuBuilder) FCanExecuteAction::CreateLambda([=]{ return Shared->CanReduceKeys(); })) ); - MenuBuilder.AddMenuEntry( - LOCTEXT("SyncSectionsUsingSourceTimecode", "Synchronize Selected Sections using Source Timecode"), - LOCTEXT("SyncSectionsUsingSourceTimecodeTooltip", "Sync selected sections using the source timecode. The first selected section will be unchanged and subsequent sections will be adjusted according to their source timecode as relative to the first section's."), - FSlateIcon(), - FUIAction( - FExecuteAction::CreateLambda([=] { return Shared->Sequencer->SyncSectionsUsingSourceTimecode(); }), - FCanExecuteAction::CreateLambda([=]{ return (Shared->Sequencer->GetSelection().GetSelectedSections().Num() > 1); })) - ); + auto OnReduceKeysToleranceChanged = [=](float NewValue) { + Sequencer->GetSequencerSettings()->SetReduceKeysTolerance(NewValue); + }; + + MenuBuilder.AddWidget( + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SSpacer) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SSpinBox) + .Style(&FEditorStyle::GetWidgetStyle("Sequencer.HyperlinkSpinBox")) + .OnValueCommitted_Lambda([=](float Value, ETextCommit::Type) { OnReduceKeysToleranceChanged(Value); }) + .OnValueChanged_Lambda(OnReduceKeysToleranceChanged) + .MinValue(0) + .MaxValue(TOptional()) + .Value_Lambda([=]() -> float { + return Sequencer->GetSequencerSettings()->GetReduceKeysTolerance(); + }) + ], + LOCTEXT("ReduceKeysTolerance", "Tolerance")); + + MenuBuilder.EndSection(); } FMovieSceneBlendTypeField FSectionContextMenu::GetSupportedBlendTypes() const @@ -770,15 +864,15 @@ void FSectionContextMenu::ReduceKeys() FScopedTransaction ReduceKeysTransaction(LOCTEXT("ReduceKeys_Transaction", "Reduce Keys")); TSet > KeyAreas; - for (auto DisplayNode : Sequencer->GetSelection().GetSelectedOutlinerNodes()) + for (const TSharedRef& DisplayNode : Sequencer->GetSelection().GetSelectedOutlinerNodes()) { SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas); } if (KeyAreas.Num() == 0) { - TSet> SelectedNodes = Sequencer->GetSelection().GetNodesWithSelectedKeysOrSections(); - for (auto DisplayNode : SelectedNodes) + const TSet>& SelectedNodes = Sequencer->GetSelection().GetNodesWithSelectedKeysOrSections(); + for (const TSharedRef& DisplayNode : SelectedNodes) { SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas); } @@ -787,8 +881,9 @@ void FSectionContextMenu::ReduceKeys() FKeyDataOptimizationParams Params; Params.bAutoSetInterpolation = true; + Params.Tolerance = Sequencer->GetSequencerSettings()->GetReduceKeysTolerance(); - for (auto KeyArea : KeyAreas) + for (TSharedPtr KeyArea : KeyAreas) { if (KeyArea.IsValid()) { @@ -838,15 +933,15 @@ bool FSectionContextMenu::CanAutoSize() const bool FSectionContextMenu::CanReduceKeys() const { TSet > KeyAreas; - for (auto DisplayNode : Sequencer->GetSelection().GetSelectedOutlinerNodes()) + for (const TSharedRef& DisplayNode : Sequencer->GetSelection().GetSelectedOutlinerNodes()) { SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas); } if (KeyAreas.Num() == 0) { - TSet> SelectedNodes = Sequencer->GetSelection().GetNodesWithSelectedKeysOrSections(); - for (auto DisplayNode : SelectedNodes) + const TSet>& SelectedNodes = Sequencer->GetSelection().GetNodesWithSelectedKeysOrSections(); + for (const TSharedRef& DisplayNode : SelectedNodes) { SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas); } @@ -855,6 +950,139 @@ bool FSectionContextMenu::CanReduceKeys() const return KeyAreas.Num() != 0; } +void FSectionContextMenu::SetInterpTangentMode(ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode) +{ + FScopedTransaction SetInterpTangentModeTransaction(LOCTEXT("SetInterpTangentMode_Transaction", "Set Interpolation and Tangent Mode")); + + TSet > KeyAreas; + for (const TSharedRef& DisplayNode : Sequencer->GetSelection().GetSelectedOutlinerNodes()) + { + SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas); + } + + if (KeyAreas.Num() == 0) + { + const TSet>& SelectedNodes = Sequencer->GetSelection().GetNodesWithSelectedKeysOrSections(); + for (const TSharedRef& DisplayNode : SelectedNodes) + { + SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas); + } + } + + bool bAnythingChanged = false; + + for (TSharedPtr KeyArea : KeyAreas) + { + if (KeyArea.IsValid()) + { + UMovieSceneSection* Section = KeyArea->GetOwningSection(); + if (Section) + { + Section->Modify(); + + for (FMovieSceneFloatChannel* FloatChannel : Section->GetChannelProxy().GetChannels()) + { + TMovieSceneChannelData ChannelData = FloatChannel->GetData(); + TArrayView Values = ChannelData.GetValues(); + + for (int32 KeyIndex = 0; KeyIndex < FloatChannel->GetNumKeys(); ++KeyIndex) + { + Values[KeyIndex].InterpMode = InterpMode; + Values[KeyIndex].TangentMode = TangentMode; + bAnythingChanged = true; + } + + FloatChannel->AutoSetTangents(); + } + } + } + } + + if (bAnythingChanged) + { + Sequencer->NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::TrackValueChanged ); + } +} + +bool FSectionContextMenu::CanSetInterpTangentMode() const +{ + TSet > KeyAreas; + for (const TSharedRef& DisplayNode : Sequencer->GetSelection().GetSelectedOutlinerNodes()) + { + SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas); + } + + if (KeyAreas.Num() == 0) + { + const TSet>& SelectedNodes = Sequencer->GetSelection().GetNodesWithSelectedKeysOrSections(); + for (const TSharedRef& DisplayNode : SelectedNodes) + { + SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas); + } + } + + for (TSharedPtr KeyArea : KeyAreas) + { + if (KeyArea.IsValid()) + { + UMovieSceneSection* Section = KeyArea->GetOwningSection(); + if (Section) + { + return Section->GetChannelProxy().GetChannels().Num() != 0; + } + } + } + + return false; +} + + +bool FSectionContextMenu::IsInterpTangentModeSelected(ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode) const +{ + TSet > KeyAreas; + for (const TSharedRef& DisplayNode : Sequencer->GetSelection().GetSelectedOutlinerNodes()) + { + SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas); + } + + if (KeyAreas.Num() == 0) + { + const TSet>& SelectedNodes = Sequencer->GetSelection().GetNodesWithSelectedKeysOrSections(); + for (const TSharedRef& DisplayNode : SelectedNodes) + { + SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas); + } + } + + int32 NumKeys = 0; + for (TSharedPtr KeyArea : KeyAreas) + { + if (KeyArea.IsValid()) + { + UMovieSceneSection* Section = KeyArea->GetOwningSection(); + if (Section) + { + for (FMovieSceneFloatChannel* FloatChannel : Section->GetChannelProxy().GetChannels()) + { + TMovieSceneChannelData ChannelData = FloatChannel->GetData(); + TArrayView Values = ChannelData.GetValues(); + + NumKeys += FloatChannel->GetNumKeys(); + for (int32 KeyIndex = 0; KeyIndex < FloatChannel->GetNumKeys(); ++KeyIndex) + { + if (Values[KeyIndex].InterpMode != InterpMode || Values[KeyIndex].TangentMode != TangentMode) + { + return false; + } + } + } + } + } + } + + return NumKeys != 0; +} + void FSectionContextMenu::ToggleSectionActive() { diff --git a/Engine/Source/Editor/Sequencer/Private/SequencerContextMenus.h b/Engine/Source/Editor/Sequencer/Private/SequencerContextMenus.h index 7e81625a2c83..2e6d2d064aae 100644 --- a/Engine/Source/Editor/Sequencer/Private/SequencerContextMenus.h +++ b/Engine/Source/Editor/Sequencer/Private/SequencerContextMenus.h @@ -8,6 +8,7 @@ #include "SequencerClipboardReconciler.h" #include "ScopedTransaction.h" #include "Channels/MovieSceneChannelHandle.h" +#include "Curves/RealCurve.h" struct FEasingAreaHandle; class FMenuBuilder; @@ -64,12 +65,18 @@ private: void ReduceKeys(); + void SetInterpTangentMode(ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode); + bool IsTrimmable() const; bool CanAutoSize() const; bool CanReduceKeys() const; + bool CanSetInterpTangentMode() const; + + bool IsInterpTangentModeSelected(ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode) const; + void ToggleSectionActive(); bool IsSectionActive() const; diff --git a/Engine/Source/Editor/Sequencer/Private/SequencerModule.cpp b/Engine/Source/Editor/Sequencer/Private/SequencerModule.cpp index 8fbaa5da8074..90aa58aa627c 100644 --- a/Engine/Source/Editor/Sequencer/Private/SequencerModule.cpp +++ b/Engine/Source/Editor/Sequencer/Private/SequencerModule.cpp @@ -23,6 +23,11 @@ #include "LevelSequence.h" #include "AssetRegistryModule.h" +#if !IS_MONOLITHIC + UE::MovieScene::FEntityManager*& GEntityManagerForDebugging = UE::MovieScene::GEntityManagerForDebuggingVisualizers; +#endif + + #define LOCTEXT_NAMESPACE "SequencerEditor" diff --git a/Engine/Source/Editor/Sequencer/Private/SequencerNodeTree.cpp b/Engine/Source/Editor/Sequencer/Private/SequencerNodeTree.cpp index 7bab31cad88f..8a87209f8669 100644 --- a/Engine/Source/Editor/Sequencer/Private/SequencerNodeTree.cpp +++ b/Engine/Source/Editor/Sequencer/Private/SequencerNodeTree.cpp @@ -327,7 +327,14 @@ void FSequencerNodeTree::Update() SectionToHandle.Empty(); HoveredNode = nullptr; - UMovieScene* MovieScene = Sequencer.GetFocusedMovieSceneSequence()->GetMovieScene(); + UMovieSceneSequence* CurrentSequence = Sequencer.GetFocusedMovieSceneSequence(); + if (WeakCurrentSequence != CurrentSequence) + { + DestroyAllNodes(); + WeakCurrentSequence = CurrentSequence; + } + + UMovieScene* MovieScene = CurrentSequence->GetMovieScene(); RefreshNodes(MovieScene); // Re-filter the tree after updating @@ -1355,13 +1362,54 @@ TArray< TSharedRef > FSequencerNodeTree::GetAllNodes() co void FSequencerNodeTree::UpdateCurveEditorTree() { - FCurveEditor* CurveEditor = Sequencer.GetCurveEditor().Get(); + FCurveEditor* CurveEditor = Sequencer.GetCurveEditor().Get(); + FCurveEditorTree* CurveEditorTree = CurveEditor->GetTree(); // Guard against multiple broadcasts here and defer them until the end of this function - FScopedCurveEditorTreeEventGuard ScopedEventGuard = CurveEditor->GetTree()->ScopedEventGuard(); + FScopedCurveEditorTreeEventGuard ScopedEventGuard = CurveEditorTree->ScopedEventGuard(); + + // Remove any curve editor tree items which are now parented incorrectly - we remove invalid entries before adding new ones below + // to ensure that we do not add new tree items as children of stale tree items + for (auto It = CurveEditorTreeItemIDs.CreateIterator(); It; ++It) + { + TSharedPtr Node = It->Key.Pin(); + + bool bIsStillRelevant = Node.IsValid() && Node->TreeSerialNumber == SerialNumber && Node->IsVisible(); + if (bIsStillRelevant) + { + // It's possible that the item was removed by a previous iteration of this loop, so we have to handle that case here + TSharedPtr Parent = Node->GetParent(); + FCurveEditorTreeItemID CachedParentID = Parent ? CurveEditorTreeItemIDs.FindRef(Parent) : FCurveEditorTreeItemID::Invalid(); + + const FCurveEditorTreeItem* TreeItem = CurveEditorTree->FindItem(It->Value); + + bIsStillRelevant = TreeItem && TreeItem->GetParentID() == CachedParentID; + } + + // Remove this item and all its children if it is no longer relevant, or it needs reparenting + if (!bIsStillRelevant) + { + CurveEditor->RemoveTreeItem(It->Value); + It.RemoveCurrent(); + } + } + + // Do a second pass to remove any items that were removed recursively above + for (auto It = CurveEditorTreeItemIDs.CreateIterator(); It; ++It) + { + if (CurveEditorTree->FindItem(It->Value) == nullptr) + { + It.RemoveCurrent(); + } + } auto Traverse_AddToCurveEditor = [this, CurveEditor](FSequencerDisplayNode& InNode) { + if (!InNode.IsVisible()) + { + return true; + } + if (InNode.GetType() == ESequencerNode::Track) { // Track nodes with top level key area's must be added @@ -1382,16 +1430,7 @@ void FSequencerNodeTree::UpdateCurveEditorTree() static const bool bIncludeThisNode = false; RootNode->Traverse_ChildFirst(Traverse_AddToCurveEditor, bIncludeThisNode); - // Remove no longer valid elements from the curve editor tree - for (auto It = CurveEditorTreeItemIDs.CreateIterator(); It; ++It) - { - TSharedPtr Node = It->Key.Pin(); - if (!Node.IsValid() || Node->TreeSerialNumber != SerialNumber || !Node->IsVisible()) - { - CurveEditor->RemoveTreeItem(It->Value); - It.RemoveCurrent(); - } - } + CurveEditorTreeItemIDs.Compact(); } bool FSequencerNodeTree::KeyAreaHasCurves(const FSequencerSectionKeyAreaNode& KeyAreaNode) const @@ -1447,3 +1486,29 @@ void FSequencerNodeTree::UnpinAllNodes() return true; }, bIncludeRootNode); } + +void FSequencerNodeTree::DestroyAllNodes() +{ + for (TSharedRef RootChild : CopyTemp(RootNode->GetChildNodes())) + { + RootChild->SetParent(nullptr); + } + + ObjectBindingToNode.Empty(); + TrackToNode.Empty(); + FolderToNode.Empty(); + SectionToHandle.Empty(); + FilteredNodes.Empty(); + HoveredNode = nullptr; + + if (FCurveEditor* CurveEditor = Sequencer.GetCurveEditor().Get()) + { + FScopedCurveEditorTreeEventGuard ScopedEventGuard = CurveEditor->GetTree()->ScopedEventGuard(); + + for (auto It = CurveEditorTreeItemIDs.CreateIterator(); It; ++It) + { + CurveEditor->RemoveTreeItem(It->Value); + } + } + CurveEditorTreeItemIDs.Empty(); +} diff --git a/Engine/Source/Editor/Sequencer/Private/SequencerNodeTree.h b/Engine/Source/Editor/Sequencer/Private/SequencerNodeTree.h index 1d8a08179e4d..3c1d6f54d4da 100644 --- a/Engine/Source/Editor/Sequencer/Private/SequencerNodeTree.h +++ b/Engine/Source/Editor/Sequencer/Private/SequencerNodeTree.h @@ -8,6 +8,7 @@ #include "Tree/CurveEditorTree.h" #include "DisplayNodes/SequencerDisplayNode.h" #include "SectionHandle.h" +#include "MovieSceneSequence.h" class FSequencer; class FCurveEditor; @@ -288,6 +289,12 @@ private: */ bool KeyAreaHasCurves(const FSequencerSectionKeyAreaNode& KeyAreaNode) const; + /** + * Destroys all nodes contained within this tree. + * @note: Does not broadcast update notifications + */ + void DestroyAllNodes(); + public: int32 GetTotalDisplayNodeCount() const { return DisplayNodeCount; } int32 GetFilteredDisplayNodeCount() const { return FilteredNodes.Num(); } @@ -330,6 +337,8 @@ private: /** Level based track filtering */ TSharedPtr TrackFilterLevelFilter; + TWeakObjectPtr WeakCurrentSequence; + /** The total number of DisplayNodes in the tree, both displayed and hidden */ uint32 DisplayNodeCount; diff --git a/Engine/Source/Editor/Sequencer/Private/SequencerSelectionCurveFilter.h b/Engine/Source/Editor/Sequencer/Private/SequencerSelectionCurveFilter.h index f304808cd57d..88aaa3546912 100644 --- a/Engine/Source/Editor/Sequencer/Private/SequencerSelectionCurveFilter.h +++ b/Engine/Source/Editor/Sequencer/Private/SequencerSelectionCurveFilter.h @@ -25,7 +25,7 @@ struct FSequencerSelectionCurveFilter : FCurveEditorTreeFilter */ void Update(const TSet>& SelectedNodes) { - NodesToFilter.Reserve(SelectedNodes.Num()); + NodesToFilter.Empty(SelectedNodes.Num()); for (const TSharedRef& SelectedNode : SelectedNodes) { diff --git a/Engine/Source/Editor/Sequencer/Private/SequencerSettings.cpp b/Engine/Source/Editor/Sequencer/Private/SequencerSettings.cpp index d77a57e0ed20..8b96fee8227e 100644 --- a/Engine/Source/Editor/Sequencer/Private/SequencerSettings.cpp +++ b/Engine/Source/Editor/Sequencer/Private/SequencerSettings.cpp @@ -45,6 +45,7 @@ USequencerSettings::USequencerSettings( const FObjectInitializer& ObjectInitiali bShowCombinedKeyframes = true; bInfiniteKeyAreas = false; bShowChannelColors = false; + ReduceKeysTolerance = KINDA_SMALL_NUMBER; bDeleteKeysWhenTrimming = true; bDisableSectionsAfterBaking = true; bCleanPlaybackMode = true; @@ -564,6 +565,20 @@ void USequencerSettings::SetShowChannelColors(bool InbShowChannelColors) } } +float USequencerSettings::GetReduceKeysTolerance() const +{ + return ReduceKeysTolerance; +} + +void USequencerSettings::SetReduceKeysTolerance(float InReduceKeysTolerance) +{ + if (ReduceKeysTolerance != InReduceKeysTolerance) + { + ReduceKeysTolerance = InReduceKeysTolerance; + SaveConfig(); + } +} + bool USequencerSettings::GetDeleteKeysWhenTrimming() const { return bDeleteKeysWhenTrimming; diff --git a/Engine/Source/Editor/Sequencer/Public/ISequencer.h b/Engine/Source/Editor/Sequencer/Public/ISequencer.h index 8a8b0dec512e..2d0f5a6853a4 100644 --- a/Engine/Source/Editor/Sequencer/Public/ISequencer.h +++ b/Engine/Source/Editor/Sequencer/Public/ISequencer.h @@ -343,7 +343,13 @@ public: /** Set the global time directly, without performing any auto-scroll, snapping or other adjustments to the supplied time */ virtual void SetGlobalTime(FFrameTime Time) = 0; - /** Forcefully reevaluate the sequence */ + /** Invalidate cached data so that it will be reevaluated on the next frame */ + virtual void RequestInvalidateCachedData() = 0; + + /** Forcefully reevaluate the sequence on the next frame */ + virtual void RequestEvaluate() = 0; + + /** Forcefully reevaluate the sequence immediately */ virtual void ForceEvaluate() = 0; /** Reset the timing manager to the clock source specified by the root movie scene */ diff --git a/Engine/Source/Editor/Sequencer/Public/LevelEditorSequencerIntegration.h b/Engine/Source/Editor/Sequencer/Public/LevelEditorSequencerIntegration.h index 319bd09160ab..b50791a6c744 100644 --- a/Engine/Source/Editor/Sequencer/Public/LevelEditorSequencerIntegration.h +++ b/Engine/Source/Editor/Sequencer/Public/LevelEditorSequencerIntegration.h @@ -83,6 +83,10 @@ public: void RemoveSequencer(TSharedRef InSequencer); + TArray> GetSequencers(); + DECLARE_MULTICAST_DELEGATE(FOnSequencersChanged); + FOnSequencersChanged& GetOnSequencersChanged() { return OnSequencersChanged; }; + private: /** Called before the world is going to be saved. The sequencer puts everything back to its initial state. */ @@ -199,4 +203,6 @@ private: TSharedPtr KeyFrameHandler; bool bDeferUpdates; + + FOnSequencersChanged OnSequencersChanged; }; diff --git a/Engine/Source/Editor/Sequencer/Public/SequencerAddKeyOperation.h b/Engine/Source/Editor/Sequencer/Public/SequencerAddKeyOperation.h index 3158f39664d0..83783e239259 100644 --- a/Engine/Source/Editor/Sequencer/Public/SequencerAddKeyOperation.h +++ b/Engine/Source/Editor/Sequencer/Public/SequencerAddKeyOperation.h @@ -42,6 +42,12 @@ struct SEQUENCER_API FAddKeyOperation static FAddKeyOperation FromNode(TSharedRef InNode); + /** + * Construct an operation from some key areas on a track. + */ + static FAddKeyOperation FromKeyAreas(ISequencerTrackEditor* TrackEditor, const TArrayView> InKeyAreas); + + /** * Commit this operation by choosing the section(s) to key for each key area, and adding a key at the specified time * @@ -87,6 +93,15 @@ private: bool ProcessKeyArea(FSequencerTrackNode* InTrackNode, TSharedPtr InKeyArea); + /** + * Add a key area to this operation + * + * @param InTrackEditor The track editor responsible for the key area + * @param InKeyArea The key area to add + */ + bool ProcessKeyArea(ISequencerTrackEditor* InTrackEditor, TSharedPtr InKeyArea); + + /** * Retrieve the operation that relates to a specific track editor instance */ diff --git a/Engine/Source/Editor/Sequencer/Public/SequencerSettings.h b/Engine/Source/Editor/Sequencer/Public/SequencerSettings.h index c6064305016f..9604bac28c4d 100644 --- a/Engine/Source/Editor/Sequencer/Public/SequencerSettings.h +++ b/Engine/Source/Editor/Sequencer/Public/SequencerSettings.h @@ -279,6 +279,11 @@ public: /** Set whether to show channel colors */ void SetShowChannelColors(bool bInShowChannelColors); + /** @return The tolerance to use when reducing keys */ + float GetReduceKeysTolerance() const; + /** Set the tolerance to use when reducing keys */ + void SetReduceKeysTolerance(float InReduceKeysTolerance); + /** @return true if deleting keys that fall beyond the section range when trimming */ bool GetDeleteKeysWhenTrimming() const; /** Set whether to delete keys that fall beyond the section range when trimming */ @@ -495,6 +500,10 @@ protected: UPROPERTY(config, EditAnywhere, Category = Timeline) bool bShowChannelColors; + /** The tolerance to use when reducing keys */ + UPROPERTY(config, EditAnywhere, Category = Timeline) + float ReduceKeysTolerance; + /** Enable or disable deleting keys that fall beyond the section range when trimming. */ UPROPERTY(config, EditAnywhere, Category = Timeline) bool bDeleteKeysWhenTrimming; diff --git a/Engine/Source/Editor/StaticMeshEditor/Private/SStaticMeshEditorViewport.cpp b/Engine/Source/Editor/StaticMeshEditor/Private/SStaticMeshEditorViewport.cpp index 3038a85a1a77..04e67ae8bb6f 100644 --- a/Engine/Source/Editor/StaticMeshEditor/Private/SStaticMeshEditorViewport.cpp +++ b/Engine/Source/Editor/StaticMeshEditor/Private/SStaticMeshEditorViewport.cpp @@ -32,7 +32,15 @@ void SStaticMeshEditorViewport::Construct(const FArguments& InArgs) { //PreviewScene = new FAdvancedPreviewScene(FPreviewScene::ConstructionValues(), - PreviewScene->SetFloorOffset(-InArgs._ObjectToEdit->ExtendedBounds.Origin.Z + InArgs._ObjectToEdit->ExtendedBounds.BoxExtent.Z); + StaticMeshEditorPtr = InArgs._StaticMeshEditor; + + TSharedPtr PinnedEditor = StaticMeshEditorPtr.Pin(); + StaticMesh = PinnedEditor.IsValid() ? PinnedEditor->GetStaticMesh() : nullptr; + + if (StaticMesh) + { + PreviewScene->SetFloorOffset(-StaticMesh->ExtendedBounds.Origin.Z + StaticMesh->ExtendedBounds.BoxExtent.Z); + } // restore last used feature level UWorld* World = PreviewScene->GetWorld(); @@ -47,10 +55,6 @@ void SStaticMeshEditorViewport::Construct(const FArguments& InArgs) PreviewScene->GetWorld()->ChangeFeatureLevel(NewFeatureLevel); }); - StaticMeshEditorPtr = InArgs._StaticMeshEditor; - - StaticMesh = InArgs._ObjectToEdit; - CurrentViewMode = VMI_Lit; FStaticMeshViewportLODCommands::Register(); @@ -375,7 +379,7 @@ void SStaticMeshEditorViewport::SetViewModeVertexColorImplementation(bool bValue SetViewModePhysicalMaterialMasksSubImplementation(false); } - StaticMeshEditorPtr.Pin()->GetStaticMeshComponent()->MarkRenderStateDirty(); + PreviewMeshComponent->MarkRenderStateDirty(); SceneViewport->Invalidate(); } @@ -386,7 +390,7 @@ void SStaticMeshEditorViewport::SetViewModeVertexColorSubImplementation(bool bVa EditorViewportClient->EngineShowFlags.SetIndirectLightingCache(!bValue); EditorViewportClient->EngineShowFlags.SetPostProcessing(!bValue); EditorViewportClient->SetFloorAndEnvironmentVisibility(!bValue); - StaticMeshEditorPtr.Pin()->GetStaticMeshComponent()->bDisplayVertexColors = bValue; + PreviewMeshComponent->bDisplayVertexColors = bValue; } bool SStaticMeshEditorViewport::IsInViewModeVertexColorChecked() const @@ -414,14 +418,14 @@ void SStaticMeshEditorViewport::SetViewModePhysicalMaterialMasksImplementation(b SetViewModeVertexColorSubImplementation(false); } - StaticMeshEditorPtr.Pin()->GetStaticMeshComponent()->MarkRenderStateDirty(); + PreviewMeshComponent->MarkRenderStateDirty(); SceneViewport->Invalidate(); } void SStaticMeshEditorViewport::SetViewModePhysicalMaterialMasksSubImplementation(bool bValue) { EditorViewportClient->EngineShowFlags.SetPhysicalMaterialMasks(bValue); - StaticMeshEditorPtr.Pin()->GetStaticMeshComponent()->bDisplayPhysicalMaterialMasks = bValue; + PreviewMeshComponent->bDisplayPhysicalMaterialMasks = bValue; } bool SStaticMeshEditorViewport::IsInViewModePhysicalMaterialMasksChecked() const diff --git a/Engine/Source/Editor/StaticMeshEditor/Private/SStaticMeshEditorViewport.h b/Engine/Source/Editor/StaticMeshEditor/Private/SStaticMeshEditorViewport.h index 5d7bfe88e5b2..cce66d1a5d3c 100644 --- a/Engine/Source/Editor/StaticMeshEditor/Private/SStaticMeshEditorViewport.h +++ b/Engine/Source/Editor/StaticMeshEditor/Private/SStaticMeshEditorViewport.h @@ -27,7 +27,6 @@ class SStaticMeshEditorViewport : public SAssetEditorViewport, public FGCObject, public: SLATE_BEGIN_ARGS( SStaticMeshEditorViewport ){} SLATE_ARGUMENT(TWeakPtr, StaticMeshEditor) - SLATE_ARGUMENT(UStaticMesh*, ObjectToEdit) SLATE_END_ARGS() void Construct(const FArguments& InArgs); diff --git a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp index 28770599dd9f..9e3fd8464197 100644 --- a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp +++ b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp @@ -178,26 +178,11 @@ void FStaticMeshEditor::InitEditorForStaticMesh(UStaticMesh* ObjectToEdit) BindCommands(); - TWeakPtr WeakSharedThis(SharedThis(this)); - MakeViewportFunc = [ObjectToEdit, WeakSharedThis](const FAssetEditorViewportConstructionArgs& InArgs) - { - return SNew(SStaticMeshEditorViewport) - .StaticMeshEditor(WeakSharedThis) - .ObjectToEdit(ObjectToEdit); - }; - // The tab must be created before the viewport layout because the layout needs them TSharedRef< SDockTab > DockableTab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Viewports")); - // Create a new tab - ViewportTabContent = MakeShareable(new FEditorViewportTabContent()); - ViewportTabContent->OnViewportTabContentLayoutChanged().AddRaw(this, &FStaticMeshEditor::OnEditorLayoutChanged); - - const FString LayoutId = FString("StaticMeshEditorViewport"); - ViewportTabContent->Initialize(MakeViewportFunc, DockableTab, LayoutId); - FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked(TEXT("PropertyEditor")); FDetailsViewArgs DetailsViewArgs; @@ -212,33 +197,11 @@ void FStaticMeshEditor::InitEditorForStaticMesh(UStaticMesh* ObjectToEdit) FOnGetDetailCustomizationInstance LayoutCustomStaticMeshProperties = FOnGetDetailCustomizationInstance::CreateSP( this, &FStaticMeshEditor::MakeStaticMeshDetails ); StaticMeshDetailsView->RegisterInstancedCustomPropertyLayout( UStaticMesh::StaticClass(), LayoutCustomStaticMeshProperties ); - SetEditorMesh(ObjectToEdit); - - bool LocalDrawGrids = false; - TFunction)> CheckShowGridFunc = - [this, &LocalDrawGrids](FName Name, TSharedPtr Entity) - { - TSharedRef StaticMeshEditorViewport = StaticCastSharedRef(Entity->AsWidget()); - FStaticMeshEditorViewportClient& StaticMeshEditorViewportClient = StaticMeshEditorViewport->GetViewportClient(); - LocalDrawGrids |= StaticMeshEditorViewportClient.IsSetShowGridChecked(); - }; - - ViewportTabContent->PerformActionOnViewports(CheckShowGridFunc); - - bDrawGrids = LocalDrawGrids; + StaticMesh = ObjectToEdit; } void FStaticMeshEditor::InitStaticMeshEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UStaticMesh* ObjectToEdit ) { - if (StaticMesh != ObjectToEdit) - { - // InitEditorForStaticMesh() should always be called first, otherwise plugins can't register themselved before the editor is built. - check(false); - InitEditorForStaticMesh(ObjectToEdit); - } - - BuildSubTools(); - TSharedRef ExtentionTabStack( FTabManager::NewStack() ->SetSizeCoefficient(0.3f) @@ -510,8 +473,19 @@ TSharedRef FStaticMeshEditor::SpawnTab_Viewport( const FSpawnTabArgs& SNew(SDockTab) .Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Viewports")); + TWeakPtr WeakSharedThis(SharedThis(this)); + MakeViewportFunc = [WeakSharedThis](const FAssetEditorViewportConstructionArgs& InArgs) + { + return SNew(SStaticMeshEditorViewport) + .StaticMeshEditor(WeakSharedThis); + }; + + // Create a new tab + ViewportTabContent = MakeShareable(new FEditorViewportTabContent()); + ViewportTabContent->OnViewportTabContentLayoutChanged().AddRaw(this, &FStaticMeshEditor::OnEditorLayoutChanged); + const FString LayoutId = FString("StaticMeshEditorViewport"); - ViewportTabContent->Initialize(MakeViewportFunc, DockableTab, LayoutId); + ViewportTabContent->Initialize(MakeViewportFunc, DockableTab, LayoutId); return DockableTab; } @@ -584,14 +558,36 @@ TSharedRef FStaticMeshEditor::SpawnTab_SecondaryToolbar( const FSpawnT return SpawnedTab; } -TSharedPtr FStaticMeshEditor::GetStaticMeshViewport() const +TSharedPtr FStaticMeshEditor::GetStaticMeshViewport() const { - // we can use static cast here b/c we know in this editor we will have a static mesh viewport - return StaticCastSharedPtr(ViewportTabContent->GetFirstViewport()); + if (ViewportTabContent.IsValid()) + { + // we can use static cast here b/c we know in this editor we will have a static mesh viewport + return StaticCastSharedPtr(ViewportTabContent->GetFirstViewport()); + } + + return TSharedPtr(); } void FStaticMeshEditor::OnEditorLayoutChanged() { + SetEditorMesh(StaticMesh); + + BuildSubTools(); + + bool LocalDrawGrids = false; + TFunction)> CheckShowGridFunc = + [this, &LocalDrawGrids](FName Name, TSharedPtr Entity) + { + TSharedRef StaticMeshEditorViewport = StaticCastSharedRef(Entity->AsWidget()); + FStaticMeshEditorViewportClient& StaticMeshEditorViewportClient = StaticMeshEditorViewport->GetViewportClient(); + LocalDrawGrids |= StaticMeshEditorViewportClient.IsSetShowGridChecked(); + }; + + ViewportTabContent->PerformActionOnViewports(CheckShowGridFunc); + + bDrawGrids = LocalDrawGrids; + OnPreviewSceneChangedDelegate.Broadcast(GetStaticMeshViewport()->GetPreviewScene()); } @@ -926,7 +922,7 @@ FLinearColor FStaticMeshEditor::GetWorldCentricTabColorScale() const UStaticMeshComponent* FStaticMeshEditor::GetStaticMeshComponent() const { - return GetStaticMeshViewport()->GetStaticMeshComponent(); + return GetStaticMeshViewport().IsValid() ? GetStaticMeshViewport()->GetStaticMeshComponent() : nullptr; } void FStaticMeshEditor::SetSelectedSocket(UStaticMeshSocket* InSelectedSocket) @@ -1406,7 +1402,10 @@ void FStaticMeshEditor::RefreshTool() void FStaticMeshEditor::RefreshViewport() { - GetStaticMeshViewport()->RefreshViewport(); + if (GetStaticMeshViewport().IsValid()) + { + GetStaticMeshViewport()->RefreshViewport(); + } } TSharedRef FStaticMeshEditor::GenerateUVChannelComboList() @@ -1914,8 +1913,11 @@ void FStaticMeshEditor::SetEditorMesh(UStaticMesh* InStaticMesh, bool bResetCame // Set the details view. StaticMeshDetailsView->SetObject(StaticMesh); - GetStaticMeshViewport()->UpdatePreviewMesh(StaticMesh, bResetCamera); - GetStaticMeshViewport()->RefreshViewport(); + if (GetStaticMeshViewport().IsValid()) + { + GetStaticMeshViewport()->UpdatePreviewMesh(StaticMesh, bResetCamera); + GetStaticMeshViewport()->RefreshViewport(); + } } void FStaticMeshEditor::OnChangeMesh() diff --git a/Engine/Source/Editor/TextureEditor/Private/Models/TextureEditorViewportClient.cpp b/Engine/Source/Editor/TextureEditor/Private/Models/TextureEditorViewportClient.cpp index 1d6d5fcce3bd..85808a521959 100644 --- a/Engine/Source/Editor/TextureEditor/Private/Models/TextureEditorViewportClient.cpp +++ b/Engine/Source/Editor/TextureEditor/Private/Models/TextureEditorViewportClient.cpp @@ -11,6 +11,7 @@ #include "Engine/VolumeTexture.h" #include "Engine/TextureRenderTarget2D.h" #include "Engine/TextureRenderTargetCube.h" +#include "Engine/TextureRenderTargetVolume.h" #include "UnrealEdGlobals.h" #include "CubemapUnwrapUtils.h" #include "Slate/SceneViewport.h" @@ -81,6 +82,7 @@ void FTextureEditorViewportClient::Draw(FViewport* Viewport, FCanvas* Canvas) UVolumeTexture* VolumeTexture = Cast(Texture); UTextureRenderTarget2D* TextureRT2D = Cast(Texture); UTextureRenderTargetCube* RTTextureCube = Cast(Texture); + UTextureRenderTargetVolume* RTTextureVolume = Cast(Texture); // Fully stream in the texture before drawing it. if (Texture2D) @@ -117,6 +119,16 @@ void FTextureEditorViewportClient::Draw(FViewport* Viewport, FCanvas* Canvas) true, TextureEditorPtr.Pin()->GetVolumeOrientation()); } + else if (RTTextureVolume) + { + BatchedElementParameters = new FBatchedElementVolumeTexturePreviewParameters( + Settings.VolumeViewMode == TextureEditorVolumeViewMode_DepthSlices, + FMath::Max(RTTextureVolume->SizeZ >> RTTextureVolume->GetCachedLODBias(), 1), + MipLevel, + (float)TextureEditorPtr.Pin()->GetVolumeOpacity(), + true, + TextureEditorPtr.Pin()->GetVolumeOrientation()); + } else if (Texture2D) { bool bIsNormalMap = Texture2D->IsNormalMap(); @@ -265,28 +277,76 @@ bool FTextureEditorViewportClient::InputKey(FViewport* Viewport, int32 Controlle return false; } +bool IsTextureUsingVolumeOrientation(UTexture* Texture) +{ + return Texture && (Cast(Texture) || Cast(Texture)); +} + bool FTextureEditorViewportClient::InputAxis(FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime, int32 NumSamples, bool bGamepad) { if (Key == EKeys::MouseX || Key == EKeys::MouseY) { - FRotator DeltaRotator(ForceInitToZero); - const float RotationSpeed = .2f; - if (Key == EKeys::MouseY) + UTexture* Texture = TextureEditorPtr.Pin()->GetTexture(); + if (IsTextureUsingVolumeOrientation(Texture)) { - DeltaRotator.Pitch = Delta * RotationSpeed; - } - else - { - DeltaRotator.Yaw = Delta * RotationSpeed; - } + FRotator DeltaRotator(ForceInitToZero); + const float RotationSpeed = .2f; + if (Key == EKeys::MouseY) + { + DeltaRotator.Pitch = Delta * RotationSpeed; + } + else + { + DeltaRotator.Yaw = Delta * RotationSpeed; + } - TextureEditorPtr.Pin()->SetVolumeOrientation((FRotationMatrix::Make(DeltaRotator) * FRotationMatrix::Make(TextureEditorPtr.Pin()->GetVolumeOrientation())).Rotator()); + TextureEditorPtr.Pin()->SetVolumeOrientation((FRotationMatrix::Make(DeltaRotator) * FRotationMatrix::Make(TextureEditorPtr.Pin()->GetVolumeOrientation())).Rotator()); + } + else if (ShouldUseMousePanning(Viewport)) + { + TSharedPtr EditorViewport = TextureEditorViewportPtr.Pin(); + + uint32 Height = 1; + uint32 Width = 1; + TextureEditorPtr.Pin()->CalculateTextureDimensions(Width, Height); + + if (Key == EKeys::MouseY) + { + float VDistFromBottom = EditorViewport->GetVerticalScrollBar()->DistanceFromBottom(); + float VRatio = GetViewportVerticalScrollBarRatio(); + float localDelta = (Delta / static_cast(Height)); + EditorViewport->GetVerticalScrollBar()->SetState(FMath::Clamp((1.f - VDistFromBottom - VRatio) + localDelta, 0.0f, 1.0f - VRatio), VRatio); + } + else + { + float HDistFromBottom = EditorViewport->GetHorizontalScrollBar()->DistanceFromBottom(); + float HRatio = GetViewportHorizontalScrollBarRatio(); + float localDelta = (Delta / static_cast(Width)) * -1.f; // delta needs to be inversed + EditorViewport->GetHorizontalScrollBar()->SetState(FMath::Clamp((1.f - HDistFromBottom - HRatio) + localDelta, 0.0f, 1.0f - HRatio), HRatio); + } + } return true; } return false; } +bool FTextureEditorViewportClient::ShouldUseMousePanning(FViewport* Viewport) const +{ + if (!IsTextureUsingVolumeOrientation(TextureEditorPtr.Pin()->GetTexture()) && Viewport->KeyState(EKeys::RightMouseButton)) + { + TSharedPtr EditorViewport = TextureEditorViewportPtr.Pin(); + return EditorViewport.IsValid() && EditorViewport->GetVerticalScrollBar().IsValid() && EditorViewport->GetHorizontalScrollBar().IsValid(); + } + + return false; +} + +EMouseCursor::Type FTextureEditorViewportClient::GetCursor(FViewport* Viewport, int32 X, int32 Y) +{ + return ShouldUseMousePanning(Viewport) ? EMouseCursor::GrabHandClosed : EMouseCursor::Default; +} + bool FTextureEditorViewportClient::InputGesture(FViewport* Viewport, EGestureEvent GestureType, const FVector2D& GestureDelta, bool bIsDirectionInvertedFromDevice) { const bool LeftMouseButtonDown = Viewport->KeyState(EKeys::LeftMouseButton); diff --git a/Engine/Source/Editor/TextureEditor/Private/Models/TextureEditorViewportClient.h b/Engine/Source/Editor/TextureEditor/Private/Models/TextureEditorViewportClient.h index 8ec0c30c7ca4..9bdc3737540f 100644 --- a/Engine/Source/Editor/TextureEditor/Private/Models/TextureEditorViewportClient.h +++ b/Engine/Source/Editor/TextureEditor/Private/Models/TextureEditorViewportClient.h @@ -27,6 +27,7 @@ public: virtual bool InputAxis(FViewport* Viewport,int32 ControllerId,FKey Key,float Delta,float DeltaTime,int32 NumSamples=1,bool bGamepad=false) override; virtual bool InputGesture(FViewport* Viewport, EGestureEvent GestureType, const FVector2D& GestureDelta, bool bIsDirectionInvertedFromDevice) override; virtual UWorld* GetWorld() const override { return nullptr; } + virtual EMouseCursor::Type GetCursor(FViewport* Viewport, int32 X, int32 Y) override; /** FGCObject interface */ virtual void AddReferencedObjects(FReferenceCollector& Collector) override; @@ -51,6 +52,9 @@ private: /** Destroy the checkerboard texture if one exists */ void DestroyCheckerboardTexture(); + /** TRUE if right clicking and dragging for panning a texture 2D */ + bool ShouldUseMousePanning(FViewport* Viewport) const; + private: /** Pointer back to the Texture editor tool that owns us */ TWeakPtr TextureEditorPtr; diff --git a/Engine/Source/Editor/TextureEditor/Private/TextureEditorToolkit.cpp b/Engine/Source/Editor/TextureEditor/Private/TextureEditorToolkit.cpp index d3dea17518ee..1ff2312aa45e 100644 --- a/Engine/Source/Editor/TextureEditor/Private/TextureEditorToolkit.cpp +++ b/Engine/Source/Editor/TextureEditor/Private/TextureEditorToolkit.cpp @@ -25,6 +25,7 @@ #include "Engine/TextureRenderTarget.h" #include "Engine/TextureRenderTarget2D.h" #include "Engine/TextureRenderTargetCube.h" +#include "Engine/TextureRenderTargetVolume.h" #include "Interfaces/ITextureEditorModule.h" #include "TextureEditor.h" #include "Slate/SceneViewport.h" @@ -273,6 +274,7 @@ void FTextureEditorToolkit::CalculateTextureDimensions( uint32& Width, uint32& H if (CurrentZoomMode == ETextureEditorZoomMode::Fit || CurrentZoomMode == ETextureEditorZoomMode::Fill) { const UVolumeTexture* VolumeTexture = Cast(Texture); + const UTextureRenderTargetVolume* VolumeTextureRT = Cast< UTextureRenderTargetVolume>(Texture); // Subtract off the viewport space devoted to padding (2 * PreviewPadding) // so that the texture is padded on all sides @@ -286,7 +288,7 @@ void FTextureEditorToolkit::CalculateTextureDimensions( uint32& Width, uint32& H const bool bNoSourceImage = Texture->Source.GetNumSlices() == 0; Width *= (bNoSourceImage || bMultipleSourceImages) ? 2 : 1; } - else if (VolumeTexture) + else if (VolumeTexture || VolumeTextureRT) { UTextureEditorSettings& Settings = *GetMutableDefault(); if (Settings.VolumeViewMode == ETextureEditorVolumeViewMode::TextureEditorVolumeViewMode_VolumeTrace) @@ -438,19 +440,31 @@ void FTextureEditorToolkit::PopulateQuickInfo( ) UTexture2D* Texture2D = Cast(Texture); UTextureRenderTarget2D* Texture2DRT = Cast(Texture); - UTextureRenderTargetCube* TextureCubeRT = Cast(Texture); UTextureCube* TextureCube = Cast(Texture); UTexture2DArray* Texture2DArray = Cast(Texture); UTexture2DDynamic* Texture2DDynamic = Cast(Texture); UVolumeTexture* VolumeTexture = Cast(Texture); + UTextureRenderTargetVolume* VolumeTextureRT = Cast(Texture); const uint32 SurfaceWidth = (uint32)Texture->GetSurfaceWidth(); const uint32 SurfaceHeight = (uint32)Texture->GetSurfaceHeight(); - const uint32 SurfaceDepth = VolumeTexture ? (uint32)VolumeTexture->GetSizeZ() : 1; + const uint32 SurfaceDepth = + [&]() -> uint32 + { + if (VolumeTexture) + { + return (uint32)VolumeTexture->GetSizeZ(); + } + else if (VolumeTextureRT) + { + return (uint32)VolumeTextureRT->SizeZ; + } + return 1; + }(); const uint32 ImportedWidth = FMath::Max(SurfaceWidth, Texture->Source.GetSizeX()); const uint32 ImportedHeight = FMath::Max(SurfaceHeight, Texture->Source.GetSizeY()); - const uint32 ImportedDepth = FMath::Max(SurfaceDepth, VolumeTexture ? Texture->Source.GetNumSlices() : 1); + const uint32 ImportedDepth = FMath::Max(SurfaceDepth, VolumeTexture || VolumeTextureRT ? Texture->Source.GetNumSlices() : 1); const int32 ActualMipBias = Texture2D ? (Texture2D->GetNumMips() - Texture2D->GetNumResidentMips()) : Texture->GetCachedLODBias(); const uint32 ActualWidth = FMath::Max(SurfaceWidth >> ActualMipBias, 1); @@ -484,7 +498,7 @@ void FTextureEditorToolkit::PopulateQuickInfo( ) Options.UseGrouping = false; - if (VolumeTexture) + if (VolumeTexture || VolumeTextureRT) { ImportedText->SetText(FText::Format( NSLOCTEXT("TextureEditor", "QuickInfo_Imported_3x", "Imported: {0}x{1}x{2}"), FText::AsNumber(ImportedWidth, &Options), FText::AsNumber(ImportedHeight, &Options), FText::AsNumber(ImportedDepth, &Options))); CurrentText->SetText(FText::Format( NSLOCTEXT("TextureEditor", "QuickInfo_Displayed_3x", "Displayed: {0}x{1}x{2}"), FText::AsNumber(PreviewEffectiveTextureWidth, &Options ), FText::AsNumber(PreviewEffectiveTextureHeight, &Options), FText::AsNumber(PreviewEffectiveTextureDepth, &Options))); @@ -552,6 +566,10 @@ void FTextureEditorToolkit::PopulateQuickInfo( ) { TextureFormatIndex = VolumeTexture->GetPixelFormat(); } + else if (VolumeTextureRT) + { + TextureFormatIndex = VolumeTextureRT->GetFormat(); + } if (TextureFormatIndex != PF_MAX) { @@ -583,6 +601,10 @@ void FTextureEditorToolkit::PopulateQuickInfo( ) { NumMips = VolumeTexture->GetNumMips(); } + else if (VolumeTextureRT) + { + NumMips = VolumeTextureRT->GetNumMips(); + } NumMipsText->SetText(FText::Format(NSLOCTEXT("TextureEditor", "QuickInfo_NumMips", "Number of Mips: {0}"), FText::AsNumber(NumMips))); @@ -1174,6 +1196,7 @@ TOptional FTextureEditorToolkit::GetMaxMipLevel( ) const const UTextureCube* TextureCube = Cast(Texture); const UTexture2DArray* Texture2DArray = Cast(Texture); const UTextureRenderTargetCube* RTTextureCube = Cast(Texture); + const UTextureRenderTargetVolume* RTTextureVolume = Cast(Texture); const UTextureRenderTarget2D* RTTexture2D = Cast(Texture); const UVolumeTexture* VolumeTexture = Cast(Texture); @@ -1197,6 +1220,11 @@ TOptional FTextureEditorToolkit::GetMaxMipLevel( ) const return RTTextureCube->GetNumMips() - 1; } + if (RTTextureVolume) + { + return RTTextureVolume->GetNumMips() - 1; + } + if (RTTexture2D) { return RTTexture2D->GetNumMips() - 1; diff --git a/Engine/Source/Editor/TextureEditor/Private/Widgets/STextureEditorViewport.cpp b/Engine/Source/Editor/TextureEditor/Private/Widgets/STextureEditorViewport.cpp index 039868b1c440..20e51030d556 100644 --- a/Engine/Source/Editor/TextureEditor/Private/Widgets/STextureEditorViewport.cpp +++ b/Engine/Source/Editor/TextureEditor/Private/Widgets/STextureEditorViewport.cpp @@ -11,6 +11,7 @@ #include "Widgets/Input/SSlider.h" #include "Engine/Texture.h" #include "Engine/VolumeTexture.h" +#include "Engine/TextureRenderTargetVolume.h" #include "Slate/SceneViewport.h" #include "TextureEditorConstants.h" #include "Widgets/STextureEditorViewportToolbar.h" @@ -85,7 +86,7 @@ void STextureEditorViewport::Construct( const FArguments& InArgs, const TSharedR FText FormattedText = InToolkit->HasValidTextureResource() ? FText::FromString(TEXT("{0}")) : LOCTEXT( "InvalidTexture", "{0} (Invalid Texture)"); TextureName = FText::Format(FormattedText, FText::FromName(InToolkit->GetTexture()->GetFName())); - bIsVolumeTexture = InToolkit->GetTexture()->IsA(); + bIsVolumeTexture = InToolkit->GetTexture()->IsA() || InToolkit->GetTexture()->IsA(); } diff --git a/Engine/Source/Editor/UMGEditor/Private/Animation/Sequencer2DTransformTrackEditor.cpp b/Engine/Source/Editor/UMGEditor/Private/Animation/Sequencer2DTransformTrackEditor.cpp index 81e01964d18d..a1af759de776 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Animation/Sequencer2DTransformTrackEditor.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Animation/Sequencer2DTransformTrackEditor.cpp @@ -114,7 +114,7 @@ FWidgetTransform F2DTransformTrackEditor::RecomposeTransform(const FWidgetTransf if (EntityLinker && EntityID) { - UMovieScenePropertyInstantiatorSystem* System = EntityLinker->SystemGraph.FindSystemOfType(); + UMovieScenePropertyInstantiatorSystem* System = EntityLinker->FindSystem(); if (System) { FDecompositionQuery Query; diff --git a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp index 79f406d80130..a99dd7a5bbae 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp @@ -23,10 +23,13 @@ #include "ScopedTransaction.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Components/PanelSlot.h" -#include "Details/SPropertyBinding.h" +#include "IPropertyAccessEditor.h" #include "Widgets/Layout/SWidgetSwitcher.h" #include "IDetailsView.h" #include "IDetailPropertyExtensionHandler.h" +#include "Binding/PropertyBinding.h" +#include "Components/WidgetComponent.h" +#include "Algo/Transform.h" #define LOCTEXT_NAMESPACE "UMG" @@ -90,6 +93,384 @@ private: TSharedPtr Action; }; +TSharedRef FBlueprintWidgetCustomization::MakePropertyBindingWidget(TWeakPtr InEditor, FDelegateProperty* InDelegateProperty, TSharedRef InPropertyHandle, bool bInGeneratePureBindings) +{ + const FName PropertyName = InPropertyHandle->GetProperty()->GetFName(); + + TArray Objects; + InPropertyHandle->GetOuterObjects(Objects); + + UWidget* Widget = CastChecked(Objects[0]); + + FString WidgetName; + if ( Widget && !Widget->IsGeneratedName() ) + { + WidgetName = TEXT("_") + Widget->GetName() + TEXT("_"); + } + + if(IModularFeatures::Get().IsModularFeatureAvailable("PropertyAccessEditor")) + { + FPropertyBindingWidgetArgs Args; + Args.Property = InPropertyHandle->GetProperty(); + Args.BindableSignature = InDelegateProperty->SignatureFunction; + Args.OnGenerateBindingName = FOnGenerateBindingName::CreateLambda([WidgetName]() + { + return WidgetName; + }); + + Args.OnGotoBinding = FOnGotoBinding::CreateLambda([InEditor, Objects](FName InPropertyName) + { + UWidgetBlueprint* ThisBlueprint = InEditor.Pin()->GetWidgetBlueprintObj(); + + //TODO UMG O(N) Isn't good for this, needs to be map, but map isn't serialized, need cached runtime map for fast lookups. + + for ( int32 ObjectIndex = 0; ObjectIndex < Objects.Num(); ObjectIndex++ ) + { + // Ignore null outer objects + if ( Objects[ObjectIndex] == nullptr ) + { + continue; + } + + for ( const FDelegateEditorBinding& Binding : ThisBlueprint->Bindings ) + { + if ( Binding.ObjectName == Objects[ObjectIndex]->GetName() && Binding.PropertyName == InPropertyName ) + { + if ( Binding.Kind == EBindingKind::Function ) + { + TArray AllGraphs; + ThisBlueprint->GetAllGraphs(AllGraphs); + + FGuid SearchForGuid = Binding.MemberGuid; + if ( !Binding.SourcePath.IsEmpty() ) + { + SearchForGuid = Binding.SourcePath.Segments.Last().GetMemberGuid(); + } + + for ( UEdGraph* Graph : AllGraphs ) + { + if ( Graph->GraphGuid == SearchForGuid ) + { + InEditor.Pin()->SetCurrentMode(FWidgetBlueprintApplicationModes::GraphMode); + InEditor.Pin()->OpenDocument(Graph, FDocumentTracker::OpenNewDocument); + } + } + + // Either way return + return true; + } + } + } + } + + return false; + }); + + Args.OnCanGotoBinding = FOnCanGotoBinding::CreateLambda([InEditor, Objects](FName InPropertyName) + { + UWidgetBlueprint* ThisBlueprint = InEditor.Pin()->GetWidgetBlueprintObj(); + + for ( int32 ObjectIndex = 0; ObjectIndex < Objects.Num(); ObjectIndex++ ) + { + // Ignore null outer objects + if ( Objects[ObjectIndex] == nullptr ) + { + continue; + } + + for ( const FDelegateEditorBinding& Binding : ThisBlueprint->Bindings ) + { + if ( Binding.ObjectName == Objects[ObjectIndex]->GetName() && Binding.PropertyName == InPropertyName ) + { + if ( Binding.Kind == EBindingKind::Function ) + { + return true; + } + } + } + } + + return false; + }); + + Args.OnCanBindProperty = FOnCanBindProperty::CreateLambda([InDelegateProperty](FProperty* InProperty) + { + if ( FProperty* ReturnProperty = InDelegateProperty->SignatureFunction->GetReturnProperty() ) + { + // Find the binder that can handle the delegate return type. + TSubclassOf Binder = UWidget::FindBinderClassForDestination(ReturnProperty); + if ( Binder != nullptr ) + { + // Ensure that the binder also can handle binding from the property we care about. + return ( Binder->GetDefaultObject()->IsSupportedSource(InProperty) ); + } + } + + return false; + }); + + Args.OnCanBindFunction = FOnCanBindFunction::CreateLambda([InDelegateProperty](UFunction* InFunction) + { + auto HasFunctionBinder = [InFunction](UFunction* InBindableSignature) + { + if ( InFunction->NumParms == 1 && InBindableSignature->NumParms == 1 ) + { + if ( FProperty* FunctionReturn = InFunction->GetReturnProperty() ) + { + if ( FProperty* DelegateReturn = InBindableSignature->GetReturnProperty() ) + { + // Find the binder that can handle the delegate return type. + TSubclassOf Binder = UWidget::FindBinderClassForDestination(DelegateReturn); + if ( Binder != nullptr ) + { + // Ensure that the binder also can handle binding from the property we care about. + if ( Binder->GetDefaultObject()->IsSupportedSource(FunctionReturn) ) + { + return true; + } + } + } + } + } + + return false; + }; + + // We ignore CPF_ReturnParm because all that matters for binding to script functions is that the number of out parameters match. + return ( InFunction->IsSignatureCompatibleWith(InDelegateProperty->SignatureFunction, UFunction::GetDefaultIgnoredSignatureCompatibilityFlags() | CPF_ReturnParm) || + HasFunctionBinder(InDelegateProperty->SignatureFunction) ); + }); + + Args.OnCanBindToClass = FOnCanBindToClass::CreateLambda([](UClass* InClass) + { + if (InClass == UUserWidget::StaticClass() || + InClass == AActor::StaticClass() || + InClass == APawn::StaticClass() || + InClass == UObject::StaticClass() || + InClass == UPrimitiveComponent::StaticClass() || + InClass == USceneComponent::StaticClass() || + InClass == UActorComponent::StaticClass() || + InClass == UWidgetComponent::StaticClass() || + InClass == UStaticMeshComponent::StaticClass() || + InClass == UWidgetAnimation::StaticClass() ) + { + return false; + } + + return true; + }); + + Args.OnCanBindToSubObjectClass = FOnCanBindToSubObjectClass::CreateLambda([](UClass* InClass) + { + // Ignore any properties that are widgets, we don't want users binding widgets to other widgets. + return InClass->IsChildOf(UWidget::StaticClass()); + }); + + Args.OnAddBinding = FOnAddBinding::CreateLambda([InEditor, Objects](FName InPropertyName, const TArray& InBindingChain) + { + UWidgetBlueprint* ThisBlueprint = InEditor.Pin()->GetWidgetBlueprintObj(); + UBlueprintGeneratedClass* SkeletonClass = Cast(ThisBlueprint->SkeletonGeneratedClass); + + ThisBlueprint->Modify(); + + TArray FieldChain; + Algo::Transform(InBindingChain, FieldChain, [](const FBindingChainElement& InElement) + { + return InElement.Field; + }); + + UFunction* Function = FieldChain.Last().Get(); + FProperty* Property = FieldChain.Last().Get(); + + check(Function != nullptr || Property != nullptr); + + for ( UObject* SelectedObject : Objects ) + { + FDelegateEditorBinding Binding; + Binding.ObjectName = SelectedObject->GetName(); + Binding.PropertyName = InPropertyName; + Binding.SourcePath = FEditorPropertyPath(FieldChain); + + if ( Function != nullptr) + { + Binding.FunctionName = Function->GetFName(); + + UBlueprint::GetGuidFromClassByFieldName( + Function->GetOwnerClass(), + Function->GetFName(), + Binding.MemberGuid); + + Binding.Kind = EBindingKind::Function; + } + else if( Property != nullptr ) + { + Binding.SourceProperty = Property->GetFName(); + + UBlueprint::GetGuidFromClassByFieldName( + SkeletonClass, + Property->GetFName(), + Binding.MemberGuid); + + Binding.Kind = EBindingKind::Property; + } + + ThisBlueprint->Bindings.Remove(Binding); + ThisBlueprint->Bindings.AddUnique(Binding); + } + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(ThisBlueprint); + }); + + Args.OnRemoveBinding = FOnRemoveBinding::CreateLambda([InEditor, Objects](FName InPropertyName) + { + UWidgetBlueprint* ThisBlueprint = InEditor.Pin()->GetWidgetBlueprintObj(); + + ThisBlueprint->Modify(); + + for ( UObject* SelectedObject : Objects ) + { + FDelegateEditorBinding Binding; + Binding.ObjectName = SelectedObject->GetName(); + Binding.PropertyName = InPropertyName; + + ThisBlueprint->Bindings.Remove(Binding); + } + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(ThisBlueprint); + }); + + Args.OnCanRemoveBinding = FOnCanRemoveBinding::CreateLambda([InEditor, Objects](FName InPropertyName) + { + UWidgetBlueprint* ThisBlueprint = InEditor.Pin()->GetWidgetBlueprintObj(); + + for ( UObject* SelectedObject : Objects ) + { + for ( const FDelegateEditorBinding& Binding : ThisBlueprint->Bindings ) + { + if ( Binding.ObjectName == SelectedObject->GetName() && Binding.PropertyName == InPropertyName ) + { + return true; + } + } + } + + return false; + }); + + Args.CurrentBindingText = MakeAttributeLambda([InEditor, Objects, PropertyName]() + { + UWidgetBlueprint* ThisBlueprint = InEditor.Pin()->GetWidgetBlueprintObj(); + + //TODO UMG O(N) Isn't good for this, needs to be map, but map isn't serialized, need cached runtime map for fast lookups. + + for ( int32 ObjectIndex = 0; ObjectIndex < Objects.Num(); ObjectIndex++ ) + { + // Ignore null outer objects + if ( Objects[ObjectIndex] == nullptr ) + { + continue; + } + + //TODO UMG handle multiple things selected + + for ( const FDelegateEditorBinding& Binding : ThisBlueprint->Bindings ) + { + if ( Binding.ObjectName == Objects[ObjectIndex]->GetName() && Binding.PropertyName == PropertyName ) + { + if ( !Binding.SourcePath.IsEmpty() ) + { + return Binding.SourcePath.GetDisplayText(); + } + else + { + if ( Binding.Kind == EBindingKind::Function ) + { + if ( Binding.MemberGuid.IsValid() ) + { + // Graph function, look up by Guid + FName FoundName = ThisBlueprint->GetFieldNameFromClassByGuid(ThisBlueprint->GeneratedClass, Binding.MemberGuid); + return FText::FromString(FName::NameToDisplayString(FoundName.ToString(), false)); + } + else + { + // No GUID, native function, return function name. + return FText::FromName(Binding.FunctionName); + } + } + else // Property + { + if ( Binding.MemberGuid.IsValid() ) + { + FName FoundName = ThisBlueprint->GetFieldNameFromClassByGuid(ThisBlueprint->GeneratedClass, Binding.MemberGuid); + return FText::FromString(FName::NameToDisplayString(FoundName.ToString(), false)); + } + else + { + // No GUID, native property, return source property. + return FText::FromName(Binding.SourceProperty); + } + } + } + } + } + + //TODO UMG Do something about missing functions, little exclamation points if they're missing and such. + + break; + } + + return LOCTEXT("Bind", "Bind"); + }); + + Args.CurrentBindingImage = MakeAttributeLambda([InEditor, Objects, PropertyName]() -> const FSlateBrush* + { + static FName PropertyIcon(TEXT("Kismet.Tabs.Variables")); + static FName FunctionIcon(TEXT("GraphEditor.Function_16x")); + + UWidgetBlueprint* ThisBlueprint = InEditor.Pin()->GetWidgetBlueprintObj(); + + //TODO UMG O(N) Isn't good for this, needs to be map, but map isn't serialized, need cached runtime map for fast lookups. + + for ( int32 ObjectIndex = 0; ObjectIndex < Objects.Num(); ObjectIndex++ ) + { + // Ignore null outer objects + if ( Objects[ObjectIndex] == NULL ) + { + continue; + } + + //TODO UMG handle multiple things selected + + for ( const FDelegateEditorBinding& Binding : ThisBlueprint->Bindings ) + { + if ( Binding.ObjectName == Objects[ObjectIndex]->GetName() && Binding.PropertyName == PropertyName ) + { + if ( Binding.Kind == EBindingKind::Function ) + { + return FEditorStyle::GetBrush(FunctionIcon); + } + else // Property + { + return FEditorStyle::GetBrush(PropertyIcon); + } + } + } + } + + return nullptr; + }); + + Args.bGeneratePureBindings = bInGeneratePureBindings; + + IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); + return PropertyAccessEditor.MakePropertyBindingWidget(InEditor.Pin()->GetBlueprintObj(), Args); + } + else + { + return SNullWidget::NullWidget; + } +} + void FBlueprintWidgetCustomization::CreateEventCustomization( IDetailLayoutBuilder& DetailLayout, FDelegateProperty* Property, UWidget* Widget ) { TSharedRef DelegatePropertyHandle = DetailLayout.GetProperty(Property->GetFName(), Property->GetOwnerChecked()); @@ -136,8 +517,7 @@ void FBlueprintWidgetCustomization::CreateEventCustomization( IDetailLayoutBuild .MinDesiredWidth(200) .MaxDesiredWidth(250) [ - SNew(SPropertyBinding, Editor.Pin().ToSharedRef(), Property, DelegatePropertyHandle) - .GeneratePureBindings(false) + MakePropertyBindingWidget(Editor.Pin(), Property, DelegatePropertyHandle, false) ]; } @@ -388,7 +768,7 @@ void FBlueprintWidgetCustomization::CustomizeAccessibilityProperty(IDetailLayout // Make sure the old AccessibleText properties are hidden so we don't get duplicate widgets DetailLayout.HideProperty(AccessibleTextPropertyHandle); - TSharedRef BindingWidget = SNew(SPropertyBinding, Editor.Pin().ToSharedRef(), AccessibleTextDelegateProperty, AccessibleTextPropertyHandle).GeneratePureBindings(false); + TSharedRef BindingWidget = MakePropertyBindingWidget(Editor, AccessibleTextDelegateProperty, AccessibleTextPropertyHandle, false); TSharedRef CustomTextLayout = SNew(SHorizontalBox) .Visibility(TAttribute::Create([AccessibleBehaviorPropertyHandle]() -> EVisibility { diff --git a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.h b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.h index c9103bc47301..af9c239566be 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.h +++ b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.h @@ -31,6 +31,9 @@ public: /** IDetailCustomization interface */ virtual void CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) override; + /** Make a property binding widget */ + static TSharedRef MakePropertyBindingWidget(TWeakPtr InEditor, FDelegateProperty* InProperty, TSharedRef InDelegatePropertyHandle, bool bInGeneratePureBindings); + private: void PerformBindingCustomization(IDetailLayoutBuilder& DetailLayout); diff --git a/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.cpp b/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.cpp index 76fea148c808..4fabad4b29ec 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.cpp @@ -1585,6 +1585,7 @@ FVector2D SDesignerView::GetExtensionSize(TSharedRef Ex void SDesignerView::ClearDropPreviews() { + UWidgetBlueprint* BP = GetBlueprint(); for (const auto& DropPreview : DropPreviews) { if (DropPreview.Parent) @@ -1592,7 +1593,6 @@ void SDesignerView::ClearDropPreviews() DropPreview.Parent->RemoveChild(DropPreview.Widget); } - UWidgetBlueprint* BP = GetBlueprint(); BP->WidgetTree->RemoveWidget(DropPreview.Widget); // Since the widget has been removed from the widget tree, move it into the transient package. Otherwise, @@ -2954,18 +2954,18 @@ void SDesignerView::ProcessDropAndAddWidget(const FGeometry& MyGeometry, const F if (Target && Target->IsA(UPanelWidget::StaticClass())) { bWidgetMoved = true; - UPanelWidget* NewParent = Cast(Target); UWidget* Widget = bIsPreview ? DraggedWidget.Preview : DraggedWidget.Template; - UWidget* ParentWidget = bIsPreview ? (DraggedWidget.ParentWidget.GetPreview()) : (DraggedWidget.ParentWidget.GetTemplate()); + UWidget* ParentWidget = bIsPreview ? DraggedWidget.ParentWidget.GetPreview() : DraggedWidget.ParentWidget.GetTemplate(); if (ensure(Widget)) { - bool bIsChangingParent = ParentWidget != NewParent; - UBlueprint* OriginalBP = nullptr; + UPanelWidget* NewParent = Cast(Target); + const bool bIsChangingParent = ParentWidget != Target; if (bIsChangingParent) { check(ParentWidget != nullptr); + UBlueprint* OriginalBP = nullptr; // If this isn't a preview operation we need to modify a few things to properly undo the operation. if (!bIsPreview) @@ -3045,9 +3045,9 @@ void SDesignerView::ProcessDropAndAddWidget(const FGeometry& MyGeometry, const F { Slot = NewParent->AddChild(Widget); } - else if (UPanelWidget* ParentwidgetAsPanel = Cast(ParentWidget)) + else if (UPanelWidget* ParentWidgetAsPanel = Cast(ParentWidget)) { - Slot = ParentwidgetAsPanel->InsertChildAt(ParentwidgetAsPanel->GetChildIndex(Widget), Widget); + Slot = ParentWidgetAsPanel->InsertChildAt(ParentWidgetAsPanel->GetChildIndex(Widget), Widget); } if (Slot != nullptr) diff --git a/Engine/Source/Editor/UMGEditor/Private/Details/DetailWidgetExtensionHandler.cpp b/Engine/Source/Editor/UMGEditor/Private/Details/DetailWidgetExtensionHandler.cpp index be8fed88b117..f3e0066b968e 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Details/DetailWidgetExtensionHandler.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Details/DetailWidgetExtensionHandler.cpp @@ -1,12 +1,12 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "Details/DetailWidgetExtensionHandler.h" -#include "Details/SPropertyBinding.h" #include "UMGEditorProjectSettings.h" #include "WidgetBlueprintEditor.h" #include "Engine/Blueprint.h" #include "Binding/WidgetBinding.h" #include "WidgetBlueprint.h" +#include "Customizations/UMGDetailCustomizations.h" FDetailWidgetExtensionHandler::FDetailWidgetExtensionHandler(TSharedPtr InBlueprintEditor) : BlueprintEditor( InBlueprintEditor ) @@ -74,6 +74,5 @@ TSharedRef FDetailWidgetExtensionHandler::GenerateExtensionWidget(const } } - return SNew(SPropertyBinding, BlueprintEditor.Pin().ToSharedRef(), DelegateProperty, InPropertyHandle.ToSharedRef()) - .GeneratePureBindings(true); + return FBlueprintWidgetCustomization::MakePropertyBindingWidget(BlueprintEditor, DelegateProperty, InPropertyHandle.ToSharedRef(), true); } diff --git a/Engine/Source/Editor/UMGEditor/Private/Details/SPropertyBinding.cpp b/Engine/Source/Editor/UMGEditor/Private/Details/SPropertyBinding.cpp deleted file mode 100644 index dd46658d07da..000000000000 --- a/Engine/Source/Editor/UMGEditor/Private/Details/SPropertyBinding.cpp +++ /dev/null @@ -1,795 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "Details/SPropertyBinding.h" -#include "Framework/Application/SlateApplication.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Text/STextBlock.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SComboButton.h" - -#if WITH_EDITOR - #include "Components/PrimitiveComponent.h" - #include "Components/StaticMeshComponent.h" - #include "Engine/BlueprintGeneratedClass.h" -#endif // WITH_EDITOR -#include "EdGraph/EdGraph.h" -#include "EdGraphSchema_K2.h" -#include "Blueprint/WidgetBlueprintGeneratedClass.h" -#include "Animation/WidgetAnimation.h" -#include "WidgetBlueprint.h" - -#include "DetailLayoutBuilder.h" -#include "BlueprintModes/WidgetBlueprintApplicationModes.h" -#include "WidgetGraphSchema.h" -#include "Kismet2/BlueprintEditorUtils.h" -#include "ScopedTransaction.h" -#include "Components/WidgetComponent.h" -#include "Binding/PropertyBinding.h" - - -#define LOCTEXT_NAMESPACE "UMG" - - -///////////////////////////////////////////////////// -// SPropertyBinding - -void SPropertyBinding::Construct(const FArguments& InArgs, TSharedRef InEditor, FDelegateProperty* DelegateProperty, TSharedRef Property) -{ - Editor = InEditor; - Blueprint = InEditor->GetWidgetBlueprintObj(); - - GeneratePureBindings = InArgs._GeneratePureBindings; - BindableSignature = DelegateProperty->SignatureFunction; - - TArray Objects; - Property->GetOuterObjects(Objects); - - UWidget* Widget = CastChecked(Objects[0]); - - ChildSlot - [ - SNew(SHorizontalBox) - - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew(SComboButton) - .OnGetMenuContent(this, &SPropertyBinding::OnGenerateDelegateMenu, Widget, Property) - .ContentPadding(1) - .ButtonContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - [ - SNew(SImage) - .Image(this, &SPropertyBinding::GetCurrentBindingImage, Property) - .ColorAndOpacity(FLinearColor(0.25f, 0.25f, 0.25f)) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(4, 1, 0, 0) - [ - SNew(STextBlock) - .Text(this, &SPropertyBinding::GetCurrentBindingText, Property) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ] - - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") - .Visibility(this, &SPropertyBinding::GetGotoBindingVisibility, Property) - .OnClicked(this, &SPropertyBinding::HandleGotoBindingClicked, Property) - .VAlign(VAlign_Center) - .ToolTipText(LOCTEXT("GotoFunction", "Goto Function")) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.Button_Browse")) - ] - ] - ]; -} - -static bool IsClassBlackListed(UClass* OwnerClass) -{ - if ( OwnerClass == UUserWidget::StaticClass() || - OwnerClass == AActor::StaticClass() || - OwnerClass == APawn::StaticClass() || - OwnerClass == UObject::StaticClass() || - OwnerClass == UPrimitiveComponent::StaticClass() || - OwnerClass == USceneComponent::StaticClass() || - OwnerClass == UActorComponent::StaticClass() || - OwnerClass == UWidgetComponent::StaticClass() || - OwnerClass == UStaticMeshComponent::StaticClass() || - OwnerClass == UWidgetAnimation::StaticClass() ) - { - return true; - } - - return false; -} - -static bool IsFieldFromBlackListedClass(FFieldVariant Field) -{ - return IsClassBlackListed(Field.GetOwnerClass()); -} - -static bool HasFunctionBinder(UFunction* Function, UFunction* BindableSignature) -{ - if ( Function->NumParms == 1 && BindableSignature->NumParms == 1 ) - { - if ( FProperty* FunctionReturn = Function->GetReturnProperty() ) - { - if ( FProperty* DelegateReturn = BindableSignature->GetReturnProperty() ) - { - // Find the binder that can handle the delegate return type. - TSubclassOf Binder = UWidget::FindBinderClassForDestination(DelegateReturn); - if ( Binder != nullptr ) - { - // Ensure that the binder also can handle binding from the property we care about. - if ( Binder->GetDefaultObject()->IsSupportedSource(FunctionReturn) ) - { - return true; - } - } - } - } - } - - return false; -} - -template -void SPropertyBinding::ForEachBindableFunction(UClass* FromClass, Predicate Pred) const -{ - const UWidgetGraphSchema* Schema = GetDefault(); - const FSlateFontInfo DetailFontInfo = IDetailLayoutBuilder::GetDetailFont(); - - UBlueprintGeneratedClass* SkeletonClass = Cast(Blueprint->GeneratedClass); - - // Walk up class hierarchy for native functions and properties - for ( TFieldIterator FuncIt(FromClass, EFieldIteratorFlags::IncludeSuper); FuncIt; ++FuncIt ) - { - UFunction* Function = *FuncIt; - - // Stop processing functions after reaching a base class that it doesn't make sense to go beyond. - if ( IsFieldFromBlackListedClass(Function) ) - { - break; - } - - // Only allow binding pure functions if we're limited to pure function bindings. - if ( GeneratePureBindings && !Function->HasAnyFunctionFlags(FUNC_Const | FUNC_BlueprintPure) ) - { - continue; - } - - // Only bind to functions that are callable from blueprints - if ( !UEdGraphSchema_K2::CanUserKismetCallFunction(Function) ) - { - continue; - } - - // We ignore CPF_ReturnParm because all that matters for binding to script functions is that the number of out parameters match. - if ( Function->IsSignatureCompatibleWith(BindableSignature, UFunction::GetDefaultIgnoredSignatureCompatibilityFlags() | CPF_ReturnParm) || - HasFunctionBinder(Function, BindableSignature) ) - { - TSharedPtr Info = MakeShareable(new FFunctionInfo()); - Info->DisplayName = FText::FromName(Function->GetFName()); - Info->Tooltip = Function->GetMetaData("Tooltip"); - Info->FuncName = Function->GetFName(); - Info->Function = Function; - - Pred(Info); - } - } -} - -template -void SPropertyBinding::ForEachBindableProperty(UStruct* InStruct, Predicate Pred) const -{ - UBlueprintGeneratedClass* SkeletonClass = Cast(Blueprint->GeneratedClass); - - for ( TFieldIterator PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt ) - { - FProperty* Property = *PropIt; - - // Stop processing properties after reaching the stoppped base class - if ( IsFieldFromBlackListedClass(Property) ) - { - break; - } - - if ( !UEdGraphSchema_K2::CanUserKismetAccessVariable(Property, SkeletonClass, UEdGraphSchema_K2::CannotBeDelegate) ) - { - continue; - } - - // Also ignore advanced properties - if ( Property->HasAnyPropertyFlags(CPF_AdvancedDisplay | CPF_EditorOnly) ) - { - continue; - } - - // Add matching properties, ensure they return the same type as the property. - if ( FProperty* ReturnProperty = BindableSignature->GetReturnProperty() ) - { - // Find the binder that can handle the delegate return type. - TSubclassOf Binder = UWidget::FindBinderClassForDestination(ReturnProperty); - if ( Binder != nullptr ) - { - // Ensure that the binder also can handle binding from the property we care about. - if ( Binder->GetDefaultObject()->IsSupportedSource(Property) ) - { - Pred(Property); - } - } - } - } -} - -TSharedRef SPropertyBinding::OnGenerateDelegateMenu(UWidget* Widget, TSharedRef PropertyHandle) -{ - const bool bInShouldCloseWindowAfterMenuSelection = true; - FMenuBuilder MenuBuilder(bInShouldCloseWindowAfterMenuSelection, nullptr); - - MenuBuilder.BeginSection("BindingActions"); - { - if ( CanRemoveBinding(PropertyHandle) ) - { - MenuBuilder.AddMenuEntry( - LOCTEXT("RemoveBinding", "Remove Binding"), - LOCTEXT("RemoveBindingToolTip", "Removes the current binding"), - FSlateIcon(FEditorStyle::GetStyleSetName(), "Cross"), - FUIAction(FExecuteAction::CreateSP(this, &SPropertyBinding::HandleRemoveBinding, PropertyHandle)) - ); - } - - MenuBuilder.AddMenuEntry( - LOCTEXT("CreateBinding", "Create Binding"), - LOCTEXT("CreateBindingToolTip", "Creates a new function on the widget blueprint that will return the binding data for this property."), - FSlateIcon(FEditorStyle::GetStyleSetName(), "Plus"), - FUIAction(FExecuteAction::CreateSP(this, &SPropertyBinding::HandleCreateAndAddBinding, Widget, PropertyHandle)) - ); - } - MenuBuilder.EndSection(); //CreateBinding - - // Properties - { - // Get the current skeleton class, think header for the blueprint. - UBlueprintGeneratedClass* SkeletonClass = Cast(Blueprint->GeneratedClass); - - TArray BindingChain; - FillPropertyMenu(MenuBuilder, PropertyHandle, SkeletonClass, BindingChain); - } - - FDisplayMetrics DisplayMetrics; - FSlateApplication::Get().GetCachedDisplayMetrics(DisplayMetrics); - - return - SNew(SVerticalBox) - - + SVerticalBox::Slot() - .MaxHeight(DisplayMetrics.PrimaryDisplayHeight * 0.5) - [ - MenuBuilder.MakeWidget() - ]; -} - -void SPropertyBinding::FillPropertyMenu(FMenuBuilder& MenuBuilder, TSharedRef PropertyHandle, UStruct* OwnerStruct, TArray BindingChain) -{ - bool bFoundEntry = false; - - //--------------------------------------- - // Function Bindings - - if ( UClass* OwnerClass = Cast(OwnerStruct) ) - { - static FName FunctionIcon(TEXT("GraphEditor.Function_16x")); - - MenuBuilder.BeginSection("Functions", LOCTEXT("Functions", "Functions")); - { - ForEachBindableFunction(OwnerClass, [&] (TSharedPtr Info) { - TArray NewBindingChain(BindingChain); - NewBindingChain.Add(Info->Function); - - bFoundEntry = true; - - MenuBuilder.AddMenuEntry( - Info->DisplayName, - FText::FromString(Info->Tooltip), - FSlateIcon(FEditorStyle::GetStyleSetName(), FunctionIcon), - FUIAction(FExecuteAction::CreateSP(this, &SPropertyBinding::HandleAddFunctionBinding, PropertyHandle, Info, NewBindingChain)) - ); - }); - } - MenuBuilder.EndSection(); //Functions - } - - //--------------------------------------- - // Property Bindings - - // Get the current skeleton class, think header for the blueprint. - UBlueprintGeneratedClass* SkeletonClass = Cast(Blueprint->GeneratedClass); - - // Only show bindable subobjects and variables if we're generating pure bindings. - if ( GeneratePureBindings ) - { - FProperty* ReturnProperty = BindableSignature->GetReturnProperty(); - - // Find the binder that can handle the delegate return type, don't bother allowing people - // to look for bindings that we don't support - if ( ensure(UWidget::FindBinderClassForDestination(ReturnProperty) != nullptr) ) - { - static FName PropertyIcon(TEXT("Kismet.Tabs.Variables")); - - MenuBuilder.BeginSection("Properties", LOCTEXT("Properties", "Properties")); - { - ForEachBindableProperty(OwnerStruct, [&] (FProperty* Property) { - TArray NewBindingChain(BindingChain); - NewBindingChain.Add(Property); - - bFoundEntry = true; - - MenuBuilder.AddMenuEntry( - Property->GetDisplayNameText(), - Property->GetToolTipText(), - FSlateIcon(FEditorStyle::GetStyleSetName(), PropertyIcon), - FUIAction(FExecuteAction::CreateSP(this, &SPropertyBinding::HandleAddPropertyBinding, PropertyHandle, Property, NewBindingChain)) - ); - }); - } - MenuBuilder.EndSection(); //Properties - - MenuBuilder.BeginSection("SubObjectProperties", LOCTEXT("SubObjectProperties", "Sub-Object Properties")); - { - // Add all the properties that are not bindable, but are object or struct members that could contain children - // properties that are bindable. - for ( TFieldIterator PropIt(OwnerStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt ) - { - FProperty* Property = *PropIt; - - // Stop processing properties after reaching the user widget properties. - if ( IsFieldFromBlackListedClass(Property) ) - { - break; - } - - // If the owner is a class then use the blueprint scheme to determine if it's visible. - if ( !UEdGraphSchema_K2::CanUserKismetAccessVariable(Property, SkeletonClass, UEdGraphSchema_K2::CannotBeDelegate) ) - { - continue; - } - - if ( Property->HasAllPropertyFlags(CPF_BlueprintVisible) ) - { - FObjectProperty* ObjectProperty = CastField(Property); - FWeakObjectProperty* WeakObjectProperty = CastField(Property); - FStructProperty* StructProperty = CastField(Property); - - UStruct* Struct = nullptr; - UClass* Class = nullptr; - - if ( ObjectProperty ) - { - Struct = Class = ObjectProperty->PropertyClass; - } - else if ( WeakObjectProperty ) - { - Struct = Class = WeakObjectProperty->PropertyClass; - } - else if ( StructProperty ) - { - Struct = StructProperty->Struct; - } - - if ( Struct ) - { - if ( Class ) - { - // Ignore any properties that are widgets, we don't want users binding widgets to other widgets. - // also ignore any class that is explicitly on the black list. - if ( IsClassBlackListed(Class) || Class->IsChildOf(UWidget::StaticClass()) ) - { - continue; - } - } - - // Stop processing properties after reaching the user widget properties. - if ( IsFieldFromBlackListedClass(Property) ) - { - break; - } - - TArray NewBindingChain(BindingChain); - NewBindingChain.Add(Property); - - bFoundEntry = true; - - MenuBuilder.AddSubMenu( - Property->GetDisplayNameText(), - Property->GetToolTipText(), - FNewMenuDelegate::CreateSP(this, &SPropertyBinding::FillPropertyMenu, PropertyHandle, Struct, NewBindingChain) - ); - } - } - } - } - MenuBuilder.EndSection(); //SubObjectProperties - } - } - - if ( bFoundEntry == false && OwnerStruct != SkeletonClass ) - { - MenuBuilder.BeginSection("None", OwnerStruct->GetDisplayNameText()); - MenuBuilder.AddWidget(SNew(STextBlock).Text(LOCTEXT("None", "None")), FText::GetEmpty()); - MenuBuilder.EndSection(); //None - } -} - -const FSlateBrush* SPropertyBinding::GetCurrentBindingImage(TSharedRef PropertyHandle) const -{ - static FName PropertyIcon(TEXT("Kismet.Tabs.Variables")); - static FName FunctionIcon(TEXT("GraphEditor.Function_16x")); - - TArray OuterObjects; - PropertyHandle->GetOuterObjects(OuterObjects); - - //TODO UMG O(N) Isn't good for this, needs to be map, but map isn't serialized, need cached runtime map for fast lookups. - - FName PropertyName = PropertyHandle->GetProperty()->GetFName(); - for ( int32 ObjectIndex = 0; ObjectIndex < OuterObjects.Num(); ObjectIndex++ ) - { - // Ignore null outer objects - if ( OuterObjects[ObjectIndex] == NULL ) - { - continue; - } - - //TODO UMG handle multiple things selected - - for ( const FDelegateEditorBinding& Binding : Blueprint->Bindings ) - { - if ( Binding.ObjectName == OuterObjects[ObjectIndex]->GetName() && Binding.PropertyName == PropertyName ) - { - if ( Binding.Kind == EBindingKind::Function ) - { - return FEditorStyle::GetBrush(FunctionIcon); - } - else // Property - { - return FEditorStyle::GetBrush(PropertyIcon); - } - } - } - } - - return nullptr; -} - -FText SPropertyBinding::GetCurrentBindingText(TSharedRef PropertyHandle) const -{ - TArray OuterObjects; - PropertyHandle->GetOuterObjects(OuterObjects); - - //TODO UMG O(N) Isn't good for this, needs to be map, but map isn't serialized, need cached runtime map for fast lookups. - - FName PropertyName = PropertyHandle->GetProperty()->GetFName(); - for ( int32 ObjectIndex = 0; ObjectIndex < OuterObjects.Num(); ObjectIndex++ ) - { - // Ignore null outer objects - if ( OuterObjects[ObjectIndex] == nullptr ) - { - continue; - } - - //TODO UMG handle multiple things selected - - for ( const FDelegateEditorBinding& Binding : Blueprint->Bindings ) - { - if ( Binding.ObjectName == OuterObjects[ObjectIndex]->GetName() && Binding.PropertyName == PropertyName ) - { - if ( !Binding.SourcePath.IsEmpty() ) - { - return Binding.SourcePath.GetDisplayText(); - } - else - { - if ( Binding.Kind == EBindingKind::Function ) - { - if ( Binding.MemberGuid.IsValid() ) - { - // Graph function, look up by Guid - FName FoundName = Blueprint->GetFieldNameFromClassByGuid(Blueprint->GeneratedClass, Binding.MemberGuid); - return FText::FromString(FName::NameToDisplayString(FoundName.ToString(), false)); - } - else - { - // No GUID, native function, return function name. - return FText::FromName(Binding.FunctionName); - } - } - else // Property - { - if ( Binding.MemberGuid.IsValid() ) - { - FName FoundName = Blueprint->GetFieldNameFromClassByGuid(Blueprint->GeneratedClass, Binding.MemberGuid); - return FText::FromString(FName::NameToDisplayString(FoundName.ToString(), false)); - } - else - { - // No GUID, native property, return source property. - return FText::FromName(Binding.SourceProperty); - } - } - } - } - } - - //TODO UMG Do something about missing functions, little exclamation points if they're missing and such. - - break; - } - - return LOCTEXT("Bind", "Bind"); -} - -bool SPropertyBinding::CanRemoveBinding(TSharedRef PropertyHandle) -{ - FName PropertyName = PropertyHandle->GetProperty()->GetFName(); - - TArray OuterObjects; - PropertyHandle->GetOuterObjects(OuterObjects); - for ( UObject* SelectedObject : OuterObjects ) - { - for ( const FDelegateEditorBinding& Binding : Blueprint->Bindings ) - { - if ( Binding.ObjectName == SelectedObject->GetName() && Binding.PropertyName == PropertyName ) - { - return true; - } - } - } - - return false; -} - -void SPropertyBinding::HandleRemoveBinding(TSharedRef PropertyHandle) -{ - const FScopedTransaction Transaction(LOCTEXT("UnbindDelegate", "Remove Binding")); - - Blueprint->Modify(); - - TArray OuterObjects; - PropertyHandle->GetOuterObjects(OuterObjects); - for ( UObject* SelectedObject : OuterObjects ) - { - FDelegateEditorBinding Binding; - Binding.ObjectName = SelectedObject->GetName(); - Binding.PropertyName = PropertyHandle->GetProperty()->GetFName(); - - Blueprint->Bindings.Remove(Binding); - } - - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); -} - -void SPropertyBinding::HandleAddFunctionBinding(TSharedRef PropertyHandle, TSharedPtr SelectedFunction, TArray BindingChain) -{ - FEditorPropertyPath BindingPath(BindingChain); - HandleAddFunctionBinding(PropertyHandle, SelectedFunction, BindingPath); -} - -void SPropertyBinding::HandleAddFunctionBinding(TSharedRef PropertyHandle, TSharedPtr SelectedFunction, FEditorPropertyPath& BindingPath) -{ - const FScopedTransaction Transaction(LOCTEXT("BindDelegate", "Set Binding")); - - Blueprint->Modify(); - - TArray OuterObjects; - PropertyHandle->GetOuterObjects(OuterObjects); - for ( UObject* SelectedObject : OuterObjects ) - { - FDelegateEditorBinding Binding; - Binding.ObjectName = SelectedObject->GetName(); - Binding.PropertyName = PropertyHandle->GetProperty()->GetFName(); - Binding.FunctionName = SelectedFunction->FuncName; - - Binding.SourcePath = BindingPath; - - if ( SelectedFunction->Function ) - { - UBlueprint::GetGuidFromClassByFieldName( - SelectedFunction->Function->GetOwnerClass(), - SelectedFunction->Function->GetFName(), - Binding.MemberGuid); - } - - Binding.Kind = EBindingKind::Function; - - Blueprint->Bindings.Remove(Binding); - Blueprint->Bindings.AddUnique(Binding); - } - - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); -} - -void SPropertyBinding::HandleAddPropertyBinding(TSharedRef PropertyHandle, FProperty* SelectedProperty, TArray BindingChain) -{ - const FScopedTransaction Transaction(LOCTEXT("BindDelegate", "Set Binding")); - - // Get the current skeleton class, think header for the blueprint. - UBlueprintGeneratedClass* SkeletonClass = Cast(Blueprint->GeneratedClass); - - Blueprint->Modify(); - - FGuid MemberGuid; - UBlueprint::GetGuidFromClassByFieldName(SkeletonClass, SelectedProperty->GetFName(), MemberGuid); - - TArray OuterObjects; - PropertyHandle->GetOuterObjects(OuterObjects); - for ( UObject* SelectedObject : OuterObjects ) - { - FDelegateEditorBinding Binding; - Binding.ObjectName = SelectedObject->GetName(); - Binding.PropertyName = PropertyHandle->GetProperty()->GetFName(); - Binding.SourceProperty = SelectedProperty->GetFName(); - Binding.SourcePath = FEditorPropertyPath(BindingChain); - Binding.MemberGuid = MemberGuid; - Binding.Kind = EBindingKind::Property; - - Blueprint->Bindings.Remove(Binding); - Blueprint->Bindings.AddUnique(Binding); - } - - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); -} - -void SPropertyBinding::HandleCreateAndAddBinding(UWidget* Widget, TSharedRef PropertyHandle) -{ - const FScopedTransaction Transaction(LOCTEXT("CreateDelegate", "Create Binding")); - - Blueprint->Modify(); - - FString Pre = GeneratePureBindings ? FString(TEXT("Get")) : FString(TEXT("On")); - - FString WidgetName; - if ( Widget && !Widget->IsGeneratedName() ) - { - WidgetName = TEXT("_") + Widget->GetName() + TEXT("_"); - } - - FString Post = PropertyHandle->GetProperty()->GetName(); - Post.RemoveFromStart(TEXT("On")); - Post.RemoveFromEnd(TEXT("Event")); - - // Create the function graph. - FString FunctionName = Pre + WidgetName + Post; - UEdGraph* FunctionGraph = FBlueprintEditorUtils::CreateNewGraph( - Blueprint, - FBlueprintEditorUtils::FindUniqueKismetName(Blueprint, FunctionName), - UEdGraph::StaticClass(), - UEdGraphSchema_K2::StaticClass()); - - // Add the binding to the blueprint - TSharedPtr SelectedFunction = MakeShareable(new FFunctionInfo()); - SelectedFunction->FuncName = FunctionGraph->GetFName(); - - FEditorPropertyPath BindingPath; - BindingPath.Segments.Add(FEditorPropertyPathSegment(FunctionGraph)); - - HandleAddFunctionBinding(PropertyHandle, SelectedFunction, BindingPath); - - const bool bUserCreated = true; - FBlueprintEditorUtils::AddFunctionGraph(Blueprint, FunctionGraph, bUserCreated, BindableSignature); - - // Only mark bindings as pure that need to be. - if ( GeneratePureBindings ) - { - const UEdGraphSchema_K2* Schema_K2 = Cast(FunctionGraph->GetSchema()); - Schema_K2->AddExtraFunctionFlags(FunctionGraph, FUNC_BlueprintPure); - } - - GotoFunction(FunctionGraph); -} - -EVisibility SPropertyBinding::GetGotoBindingVisibility(TSharedRef PropertyHandle) const -{ - TArray OuterObjects; - PropertyHandle->GetOuterObjects(OuterObjects); - - //TODO UMG O(N) Isn't good for this, needs to be map, but map isn't serialized, need cached runtime map for fast lookups. - - FName PropertyName = PropertyHandle->GetProperty()->GetFName(); - for ( int32 ObjectIndex = 0; ObjectIndex < OuterObjects.Num(); ObjectIndex++ ) - { - // Ignore null outer objects - if ( OuterObjects[ObjectIndex] == nullptr ) - { - continue; - } - - //TODO UMG handle multiple things selected - - for ( const FDelegateEditorBinding& Binding : Blueprint->Bindings ) - { - if ( Binding.ObjectName == OuterObjects[ObjectIndex]->GetName() && Binding.PropertyName == PropertyName ) - { - if ( Binding.Kind == EBindingKind::Function ) - { - return EVisibility::Visible; - } - } - } - } - - return EVisibility::Collapsed; -} - -FReply SPropertyBinding::HandleGotoBindingClicked(TSharedRef PropertyHandle) -{ - TArray OuterObjects; - PropertyHandle->GetOuterObjects(OuterObjects); - - //TODO UMG O(N) Isn't good for this, needs to be map, but map isn't serialized, need cached runtime map for fast lookups. - - FName PropertyName = PropertyHandle->GetProperty()->GetFName(); - for ( int32 ObjectIndex = 0; ObjectIndex < OuterObjects.Num(); ObjectIndex++ ) - { - // Ignore null outer objects - if ( OuterObjects[ObjectIndex] == nullptr ) - { - continue; - } - - //TODO UMG handle multiple things selected - - for ( const FDelegateEditorBinding& Binding : Blueprint->Bindings ) - { - if ( Binding.ObjectName == OuterObjects[ObjectIndex]->GetName() && Binding.PropertyName == PropertyName ) - { - if ( Binding.Kind == EBindingKind::Function ) - { - TArray AllGraphs; - Blueprint->GetAllGraphs(AllGraphs); - - FGuid SearchForGuid = Binding.MemberGuid; - if ( !Binding.SourcePath.IsEmpty() ) - { - SearchForGuid = Binding.SourcePath.Segments.Last().GetMemberGuid(); - } - - for ( UEdGraph* Graph : AllGraphs ) - { - if ( Graph->GraphGuid == SearchForGuid ) - { - GotoFunction(Graph); - } - } - - // Either way return - return FReply::Handled(); - } - } - } - } - - return FReply::Unhandled(); -} - -void SPropertyBinding::GotoFunction(UEdGraph* FunctionGraph) -{ - Editor.Pin()->SetCurrentMode(FWidgetBlueprintApplicationModes::GraphMode); - - Editor.Pin()->OpenDocument(FunctionGraph, FDocumentTracker::OpenNewDocument); -} - - -#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UMGEditor/Private/Details/SPropertyBinding.h b/Engine/Source/Editor/UMGEditor/Private/Details/SPropertyBinding.h deleted file mode 100644 index d145c4fd2f83..000000000000 --- a/Engine/Source/Editor/UMGEditor/Private/Details/SPropertyBinding.h +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Layout/Visibility.h" -#include "Input/Reply.h" -#include "Widgets/SWidget.h" -#include "Widgets/DeclarativeSyntaxSupport.h" -#include "Widgets/SCompoundWidget.h" -#include "WidgetBlueprintEditor.h" -#include "EdGraph/EdGraphSchema.h" -#include "PropertyHandle.h" - -class FMenuBuilder; -class UEdGraph; -class UWidgetBlueprint; -struct FEditorPropertyPath; - -class SPropertyBinding : public SCompoundWidget -{ -public: - - SLATE_BEGIN_ARGS(SPropertyBinding) - : _GeneratePureBindings(true) - {} - SLATE_ARGUMENT(bool, GeneratePureBindings) - SLATE_END_ARGS() - - void Construct(const FArguments& InArgs, TSharedRef InEditor, FDelegateProperty* DelegateProperty, TSharedRef Property); - -protected: - struct FFunctionInfo - { - FFunctionInfo() - : Function(nullptr) - { - } - - FText DisplayName; - FString Tooltip; - - FName FuncName; - UFunction* Function; - }; - - TSharedRef OnGenerateDelegateMenu(UWidget* Widget, TSharedRef PropertyHandle); - void FillPropertyMenu(FMenuBuilder& MenuBuilder, TSharedRef PropertyHandle, UStruct* OwnerStruct, TArray BindingChain); - - const FSlateBrush* GetCurrentBindingImage(TSharedRef PropertyHandle) const; - FText GetCurrentBindingText(TSharedRef PropertyHandle) const; - - bool CanRemoveBinding(TSharedRef PropertyHandle); - void HandleRemoveBinding(TSharedRef PropertyHandle); - - void HandleAddFunctionBinding(TSharedRef PropertyHandle, TSharedPtr SelectedFunction, TArray BindingChain); - void HandleAddFunctionBinding(TSharedRef PropertyHandle, TSharedPtr SelectedFunction, FEditorPropertyPath& BindingPath); - void HandleAddPropertyBinding(TSharedRef PropertyHandle, FProperty* SelectedProperty, TArray BindingChain); - - void HandleCreateAndAddBinding(UWidget* Widget, TSharedRef PropertyHandle); - void GotoFunction(UEdGraph* FunctionGraph); - - EVisibility GetGotoBindingVisibility(TSharedRef PropertyHandle) const; - - FReply HandleGotoBindingClicked(TSharedRef PropertyHandle); - -private: - - template - void ForEachBindableProperty(UStruct* InStruct, Predicate Pred) const; - - template - void ForEachBindableFunction(UClass* FromClass, Predicate Pred) const; - - TWeakPtr Editor; - - UWidgetBlueprint* Blueprint; - - bool GeneratePureBindings; - UFunction* BindableSignature; -}; diff --git a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditor.cpp b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditor.cpp index f6a370d9e209..33d2fb4d7261 100644 --- a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditor.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditor.cpp @@ -1377,6 +1377,7 @@ void FWidgetBlueprintEditor::AddWidgetsToTrack(const TArray Wi UWidgetAnimation* WidgetAnimation = Cast(Sequencer->GetFocusedMovieSceneSequence()); UMovieScene* MovieScene = WidgetAnimation->GetMovieScene(); + FText ExistingBindingName; TArray WidgetsToAdd; for (const FWidgetReference& Widget : Widgets) { @@ -1388,11 +1389,15 @@ void FWidgetBlueprintEditor::AddWidgetsToTrack(const TArray Wi { WidgetsToAdd.Add(Widget); } + else if (ExistingBindingName.IsEmpty()) + { + ExistingBindingName = MovieScene->GetObjectDisplayName(SelectedWidgetId); + } } if (WidgetsToAdd.Num() == 0) { - FNotificationInfo Info(LOCTEXT("Widgetalreadybound", "Widget already bound")); + FNotificationInfo Info(FText::Format(LOCTEXT("WidgetAlreadyBound", "Widget already bound to {0}"), ExistingBindingName)); Info.FadeInDuration = 0.1f; Info.FadeOutDuration = 0.5f; Info.ExpireDuration = 2.5f; @@ -1528,35 +1533,29 @@ void FWidgetBlueprintEditor::RemoveMissingWidgetsFromTrack(FGuid ObjectId) void FWidgetBlueprintEditor::ReplaceTrackWithWidgets(TArray Widgets, FGuid ObjectId) { - const FScopedTransaction Transaction( LOCTEXT( "ReplaceTrackWithSelectedWidgets", "Replace Track with Selected Widgets" ) ); - UWidgetAnimation* WidgetAnimation = Cast(Sequencer->GetFocusedMovieSceneSequence()); UMovieScene* MovieScene = WidgetAnimation->GetMovieScene(); - WidgetAnimation->Modify(); - MovieScene->Modify(); - - // Remove everything from the track - RemoveAllWidgetsFromTrack(ObjectId); - // Filter out anything in the input array that is currently bound to another object in the animation + FText ExistingBindingName; for (int32 Index = Widgets.Num()-1; Index >= 0; --Index) { UWidget* PreviewWidget = Widgets[Index].GetPreview(); FGuid WidgetId = Sequencer->FindObjectId(*PreviewWidget, MovieSceneSequenceID::Root); - if (WidgetId.IsValid()) + if (WidgetId.IsValid() && WidgetId != ObjectId) { Widgets.RemoveAt(Index, 1, false); + + if (ExistingBindingName.IsEmpty()) + { + ExistingBindingName = MovieScene->GetObjectDisplayName(WidgetId); + } } } - if (Widgets.Num() > 0) + if (Widgets.Num() == 0) { - AddWidgetsToTrack(Widgets, ObjectId); - } - else - { - FNotificationInfo Info(LOCTEXT("Widgetalreadybound", "Widget already bound")); + FNotificationInfo Info(FText::Format(LOCTEXT("WidgetAlreadyBound", "Widget already bound to {0}"), ExistingBindingName)); Info.FadeInDuration = 0.1f; Info.FadeOutDuration = 0.5f; Info.ExpireDuration = 2.5f; @@ -1564,9 +1563,22 @@ void FWidgetBlueprintEditor::ReplaceTrackWithWidgets(TArray Wi NotificationItem->SetCompletionState(SNotificationItem::CS_Success); NotificationItem->ExpireAndFadeout(); + return; } + const FScopedTransaction Transaction( LOCTEXT( "ReplaceTrackWithSelectedWidgets", "Replace Track with Selected Widgets" ) ); + + + WidgetAnimation->Modify(); + MovieScene->Modify(); + + // Remove everything from the track + RemoveAllWidgetsFromTrack(ObjectId); + + AddWidgetsToTrack(Widgets, ObjectId); + UpdateTrackName(ObjectId); + Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged); } @@ -1728,19 +1740,36 @@ void FWidgetBlueprintEditor::SyncSequencerSelectionToSelectedWidgets() void FWidgetBlueprintEditor::UpdateTrackName(FGuid ObjectId) { + UUserWidget* PreviewRoot = GetPreview(); + UObject* BindingContext = GetAnimationPlaybackContext(); + UWidgetAnimation* WidgetAnimation = Cast(Sequencer->GetFocusedMovieSceneSequence()); UMovieScene* MovieScene = WidgetAnimation->GetMovieScene(); const TArray& WidgetBindings = WidgetAnimation->GetBindings(); - if (WidgetBindings.Num() > 0) + + for (FWidgetAnimationBinding& Binding : WidgetAnimation->AnimationBindings) { - FString NewLabel = WidgetBindings[0].WidgetName.ToString(); - if (WidgetBindings.Num() > 1) + if (Binding.AnimationGuid != ObjectId) { - NewLabel.Append(FString::Printf(TEXT(" (%d)"), WidgetBindings.Num())); + continue; } - MovieScene->SetObjectDisplayName(ObjectId, FText::FromString(NewLabel)); + TArray> BoundObjects; + WidgetAnimation->LocateBoundObjects(ObjectId, BindingContext, BoundObjects); + + if (BoundObjects.Num() > 0) + { + FString NewLabel = Binding.WidgetName.ToString(); + if (BoundObjects.Num() > 1) + { + NewLabel.Append(FString::Printf(TEXT(" (%d)"), BoundObjects.Num())); + } + + MovieScene->SetObjectDisplayName(ObjectId, FText::FromString(NewLabel)); + break; + } } } + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UMGEditor/UMGEditor.Build.cs b/Engine/Source/Editor/UMGEditor/UMGEditor.Build.cs index 10d138fc974e..3a081a25adb9 100644 --- a/Engine/Source/Editor/UMGEditor/UMGEditor.Build.cs +++ b/Engine/Source/Editor/UMGEditor/UMGEditor.Build.cs @@ -71,7 +71,7 @@ public class UMGEditor : ModuleRules "PropertyPath", "ToolMenus", "SlateReflector", - "DeveloperSettings" + "DeveloperSettings", } ); } diff --git a/Engine/Source/Editor/UnrealEd/Classes/Commandlets/ConvertLevelsToExternalActorsCommandlet.h b/Engine/Source/Editor/UnrealEd/Classes/Commandlets/ConvertLevelsToExternalActorsCommandlet.h index ef7182cdaaca..21448ff847ec 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Commandlets/ConvertLevelsToExternalActorsCommandlet.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Commandlets/ConvertLevelsToExternalActorsCommandlet.h @@ -22,8 +22,14 @@ protected: ULevel* LoadLevel(const FString& LevelToLoad) const; void GetSubLevelsToConvert(ULevel* MainLevel, TSet& SubLevels, bool bRecursive); + bool CheckExternalActors(const FString& Level, bool bRepair); + bool UseSourceControl() const { return SourceControlProvider != nullptr; } ISourceControlProvider& GetSourceControlProvider() { check(UseSourceControl()); return *SourceControlProvider; } + bool AddPackageToSourceControl(UPackage* Package); + bool CheckoutPackage(UPackage* Package); + bool SavePackage(UPackage* Package); + bool DeleteFile(const FString& Filename); protected: ISourceControlProvider* SourceControlProvider; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Commandlets/ResavePackagesCommandlet.h b/Engine/Source/Editor/UnrealEd/Classes/Commandlets/ResavePackagesCommandlet.h index 8970ad530461..da885552a830 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Commandlets/ResavePackagesCommandlet.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Commandlets/ResavePackagesCommandlet.h @@ -107,13 +107,14 @@ protected: /** Filter packages based on a collection **/ TSet CollectionFilter; - /** Should we generated HLOD proxy meshes */ + /** Should we update HLODs */ bool bShouldBuildHLOD; bool bGenerateClusters; bool bGenerateMeshProxies; bool bForceClusterGeneration; bool bForceProxyGeneration; bool bForceSingleClusterForLevel; + bool bHLODMapCleanup; FString ForceHLODSetupAsset; FString HLODSkipToMap; bool bForceUATEnvironmentVariableSet; diff --git a/Engine/Source/Editor/UnrealEd/Classes/CookOnTheSide/CookOnTheFlyServer.h b/Engine/Source/Editor/UnrealEd/Classes/CookOnTheSide/CookOnTheFlyServer.h index 0c5f1ca7ecec..0e861cf268d6 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/CookOnTheSide/CookOnTheFlyServer.h +++ b/Engine/Source/Editor/UnrealEd/Classes/CookOnTheSide/CookOnTheFlyServer.h @@ -73,7 +73,7 @@ enum class ECookByTheBookOptions FullLoadAndSave = 0x00004000, // Load all packages into memory and save them all at once in one tick for speed reasons. This requires a lot of RAM for large games. PackageStore = 0x00008000, // Cook package header information into a global package store CookAgainstFixedBase = 0x00010000, // If cooking DLC, assume that the base content can not be modified. - DLCNoCookAllAssets = 0x00020000, // If cooking DLC, do not include all assets and maps in the cook. You will be relying on other methods to add these files to the cook. + DlcLoadMainAssetRegistry = 0x00020000, // If cooking DLC, populate the main game asset registry // Deprecated flags DisableUnsolicitedPackages UE_DEPRECATED(4.26, "Use SkipSoftReferences and/or SkipHardReferences instead") = SkipSoftReferences | SkipHardReferences, @@ -927,6 +927,11 @@ private: */ bool IsCookingAgainstFixedBase() const; + /** + * Returns whether or not we should populate the Asset Registry using the main game content + */ + bool ShouldPopulateFullAssetRegistry() const; + /** * GetBaseDirectoryForDLC * diff --git a/Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h b/Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h index 966d8def0207..88bb4bae19c7 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h @@ -29,6 +29,7 @@ #include "EditorSubsystem.h" #include "Subsystems/SubsystemCollection.h" #include "RHI.h" +#include "UnrealEngine.h" #include "EditorEngine.generated.h" @@ -277,9 +278,9 @@ struct FPreviewPlatformInfo , bPreviewFeatureLevelActive(false) {} - FPreviewPlatformInfo(ERHIFeatureLevel::Type InFeatureLevel, FName InPreviewShaderPlatformName = NAME_None, bool InbPreviewFeatureLevelActive = false) + FPreviewPlatformInfo(ERHIFeatureLevel::Type InFeatureLevel, FName InPreviewShaderFormatName = NAME_None, bool InbPreviewFeatureLevelActive = false) : PreviewFeatureLevel(InFeatureLevel) - , PreviewShaderPlatformName(InPreviewShaderPlatformName) + , PreviewShaderFormatName(InPreviewShaderFormatName) , bPreviewFeatureLevelActive(InbPreviewFeatureLevelActive) {} @@ -287,7 +288,7 @@ struct FPreviewPlatformInfo ERHIFeatureLevel::Type PreviewFeatureLevel; /** The shader platform to preview, or NAME_None if there is no shader preview platform */ - FName PreviewShaderPlatformName; + FName PreviewShaderFormatName; /** Is feature level preview currently active */ bool bPreviewFeatureLevelActive; @@ -295,15 +296,15 @@ struct FPreviewPlatformInfo /** Checks if two FPreviewPlatformInfos are for the same preview platform. Note, this does NOT compare the bPreviewFeatureLevelActive flag */ bool Matches(const FPreviewPlatformInfo& Other) const { - return PreviewFeatureLevel == Other.PreviewFeatureLevel && PreviewShaderPlatformName == Other.PreviewShaderPlatformName; + return PreviewFeatureLevel == Other.PreviewFeatureLevel && PreviewShaderFormatName == Other.PreviewShaderFormatName; } /** Convert platform name like "Android", or NAME_None if none is set or the preview feature level is not active */ FName GetEffectivePreviewPlatformName() const { - if (PreviewShaderPlatformName != NAME_None && bPreviewFeatureLevelActive) + if (PreviewShaderFormatName != NAME_None && bPreviewFeatureLevelActive) { - ITargetPlatform* TargetPlatform = GetTargetPlatformManager()->FindTargetPlatformWithSupport(TEXT("ShaderFormat"), PreviewShaderPlatformName); + ITargetPlatform* TargetPlatform = GetTargetPlatformManager()->FindTargetPlatformWithSupport(TEXT("ShaderFormat"), PreviewShaderFormatName); if (TargetPlatform) { return FName(*TargetPlatform->IniPlatformName()); @@ -340,7 +341,7 @@ public: * Separate from UGameEngine because it may have much different functionality than desired for an instance of a game itself. */ UCLASS(config=Engine, transient) -class UNREALED_API UEditorEngine : public UEngine, public FGCObject +class UNREALED_API UEditorEngine : public UEngine { public: GENERATED_BODY() @@ -827,8 +828,13 @@ private: virtual void RemapGamepadControllerIdForPIE(class UGameViewportClient* GameViewport, int32 &ControllerId) override; virtual TSharedPtr GetGameViewportWidget() const override; virtual void TriggerStreamingDataRebuild() override; + + PRAGMA_DISABLE_DEPRECATION_WARNINGS virtual bool NetworkRemapPath(UNetDriver* Driver, FString& Str, bool bReading = true) override; + PRAGMA_ENABLE_DEPRECATION_WARNINGS + virtual bool NetworkRemapPath(UNetConnection* Connection, FString& Str, bool bReading = true) override; virtual bool NetworkRemapPath(UPendingNetGame* PendingNetGame, FString& Str, bool bReading = true) override; + virtual bool AreEditorAnalyticsEnabled() const override; virtual void CreateStartupAnalyticsAttributes(TArray& StartSessionAttributes) const override; virtual void VerifyLoadMapWorldCleanup() override; @@ -2478,9 +2484,11 @@ public: */ FWorldContext &GetEditorWorldContext(bool bEnsureIsGWorld = false); - /** Returns the WorldContext for the PIE world. - */ - FWorldContext* GetPIEWorldContext(); + /** + * Returns the WorldContext for the PIE world, by default will get the first one which will be the server or simulate instance. + * You need to iterate the context list if you want all the pie world contexts. + */ + FWorldContext* GetPIEWorldContext(int32 WorldPIEInstance = 0); /** * mostly done to check if PIE is being set up, go GWorld is going to change, and it's not really _the_G_World_ @@ -2899,6 +2907,15 @@ private: /** The Timer manager for all timer delegates */ TSharedPtr TimerManager; + /** Currently active function execution world switcher, will be null most of the time */ + FScopedConditionalWorldSwitcher* FunctionStackWorldSwitcher = nullptr; + + /** Stack entry where world switcher was created, and should be destroyed at */ + int32 FunctionStackWorldSwitcherTag = -1; + + /** Delegate handles for function execution */ + FDelegateHandle ScriptExecutionStartHandle, ScriptExecutionEndHandle; + // This chunk is used for Play In New Process public: /** @@ -3024,16 +3041,31 @@ protected: /** * Hack to switch worlds for the PIE window before and after a slate event * - * @param WorldID The id of the world to restore or -1 if no world + * @param WorldID The id of the world to switch to where -1 is unknown, 0 is editor, and 1 is PIE + * @param PIEInstance When switching to a PIE instance, this is the specific client/server instance to use * @return The ID of the world to restore later or -1 if no world to restore */ - int32 OnSwitchWorldForSlatePieWindow(int32 WorldID); + int32 OnSwitchWorldForSlatePieWindow(int32 WorldID, int32 WorldPIEInstance); /** * Called via a delegate to toggle between the editor and pie world */ void OnSwitchWorldsForPIE(bool bSwitchToPieWorld, UWorld* OverrideWorld = nullptr); + /** + * Called to switch to a specific PIE instance, where -1 means the editor world + */ + void OnSwitchWorldsForPIEInstance(int32 WorldPIEInstance); + + /** Call to enable/disable callbacks for PIE world switching when PIE starts/stops */ + void EnableWorldSwitchCallbacks(bool bEnable); + + /** Callback when script execution starts, might switch world */ + void OnScriptExecutionStart(const struct FBlueprintContextTracker& ContextTracker, const UObject* ContextObject, const UFunction* ContextFunction); + + /** Callback when script execution starts, might switch world */ + void OnScriptExecutionEnd(const struct FBlueprintContextTracker& ContextTracker); + /** * Gives focus to the server or first PIE client viewport */ @@ -3215,15 +3247,6 @@ private: /** Delegate handle for game viewport close requests in PIE sessions. */ FDelegateHandle ViewportCloseRequestedDelegateHandle; -public: - // FGCObject Interface - virtual void AddReferencedObjects(FReferenceCollector& Collector) override; - virtual FString GetReferencerName() const override - { - return "EditorEngine"; - } - // ~FGCObject Interface - public: /** * Get a Subsystem of specified type diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/Factory.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/Factory.h index 5f333d54119e..dd9741d3f0a0 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/Factory.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/Factory.h @@ -217,7 +217,7 @@ public: /** * @return true if this factory is being used for automated import. Dialogs and user input should be disabled if this method returns true */ - bool IsAutomatedImport() const; + virtual bool IsAutomatedImport() const; public: /** diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportFbxAnimSequenceFactory.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportFbxAnimSequenceFactory.h index ad23e00fc088..b29e2f375c33 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportFbxAnimSequenceFactory.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportFbxAnimSequenceFactory.h @@ -28,6 +28,7 @@ class UReimportFbxAnimSequenceFactory : public UFbxFactory, public FReimportHand //~ Begin UFactory Interface virtual bool FactoryCanImport(const FString& Filename) override; + virtual bool IsAutomatedImport() const override; //~ End UFactory Interface }; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportFbxSkeletalMeshFactory.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportFbxSkeletalMeshFactory.h index 5c8e0c6f3adc..4493dbc49ff7 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportFbxSkeletalMeshFactory.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportFbxSkeletalMeshFactory.h @@ -36,5 +36,6 @@ class UReimportFbxSkeletalMeshFactory : public UFbxFactory, public FReimportHand //~ Begin UFactory Interface virtual bool FactoryCanImport(const FString& Filename) override; + virtual bool IsAutomatedImport() const override; //~ End UFactory Interface }; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportFbxStaticMeshFactory.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportFbxStaticMeshFactory.h index 7daf12a4a145..5adb4c35bac4 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportFbxStaticMeshFactory.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportFbxStaticMeshFactory.h @@ -28,5 +28,6 @@ class UReimportFbxStaticMeshFactory : public UFbxFactory, public FReimportHandle //~ Begin UFactory Interface virtual bool FactoryCanImport(const FString& Filename) override; + virtual bool IsAutomatedImport() const override; //~ End UFactory Interface }; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportTextureFactory.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportTextureFactory.h index 8ab5eeeceabf..55589f0fe556 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportTextureFactory.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/ReimportTextureFactory.h @@ -31,6 +31,10 @@ class UReimportTextureFactory : public UTextureFactory, public FReimportHandler virtual int32 GetPriority() const override; //~ End FReimportHandler Interface + //~ Begin UFactory Interface + virtual bool IsAutomatedImport() const override; + //~ End UFactory Interface + private: //~ Begin UTextureFactory Interface virtual UTexture2D* CreateTexture2D( UObject* InParent, FName Name, EObjectFlags Flags ) override; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/TextureRenderTargetVolumeFactoryNew.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/TextureRenderTargetVolumeFactoryNew.h new file mode 100644 index 000000000000..b1c20ce32665 --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/TextureRenderTargetVolumeFactoryNew.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +//~============================================================================= +// TextureRenderTargetCubeFactoryNew +//~============================================================================= + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Factories/Factory.h" +#include "TextureRenderTargetVolumeFactoryNew.generated.h" + +UCLASS(MinimalAPI, hidecategories=(Object, Texture)) +class UTextureRenderTargetVolumeFactoryNew : public UFactory +{ + GENERATED_UCLASS_BODY() + + /** width of new texture */ + UPROPERTY(meta=(ToolTip="Width of the texture render target")) + int32 Width; + + /** height of new texture */ + UPROPERTY(meta=(ToolTip = "Height of the texture render target")) + int32 Height; + + /** depth of new texture */ + UPROPERTY(meta=(ToolTip = "Depth of the texture render target")) + int32 Depth; + + /** surface format of new texture */ + UPROPERTY(meta=(ToolTip="Pixel format of the texture render target")) + uint8 Format; + + + //~ Begin UFactory Interface + virtual UObject* FactoryCreateNew(UClass* Class,UObject* InParent,FName Name,EObjectFlags Flags,UObject* Context,FFeedbackContext* Warn) override; + //~ Begin UFactory Interface +}; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Settings/EditorExperimentalSettings.h b/Engine/Source/Editor/UnrealEd/Classes/Settings/EditorExperimentalSettings.h index 8d2c712b3852..613d6a5ac83d 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Settings/EditorExperimentalSettings.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Settings/EditorExperimentalSettings.h @@ -64,10 +64,6 @@ public: /** Break on Exceptions allows you to trap Access Nones and other exceptional events in Blueprints. */ UPROPERTY(EditAnywhere, config, Category=Blueprints, meta=(DisplayName="Blueprint Break on Exceptions")) bool bBreakOnExceptions; - - /** Enables "Find and Replace All" tool in the MyBlueprint window for variables */ - UPROPERTY(EditAnywhere, config, Category = Blueprints, meta = (DisplayName = "Find and Replace All References Tool")) - bool bEnableFindAndReplaceReferences; protected: /** Any blueprint deriving from one of these base classes will be allowed to recompile during Play-in-Editor */ diff --git a/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorMiscSettings.h b/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorMiscSettings.h index f11ae03fe91c..249f1e1d8c45 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorMiscSettings.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorMiscSettings.h @@ -48,6 +48,10 @@ public: UPROPERTY(EditAnywhere, config, AdvancedDisplay, Category = Editing, meta = (ConfigRestartRequired = true)) uint32 bEnableLegacyMeshPaintMode : 1; + /** If enabled, will avoid relabeling actors in UUnrealEdEngine::edactPasteSelected */ + UPROPERTY(EditAnywhere, config, Category = Editing, meta = (DisplayName = "Avoid Actor Relabel on Paste Selected")) + uint32 bAvoidRelabelOnPasteSelected:1; + public: /** If checked audio playing in the editor will continue to play even if the editor is in the background */ UPROPERTY(EditAnywhere, config, Category=Sound) diff --git a/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorViewportSettings.h b/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorViewportSettings.h index 29a741466862..d80ef1adcb81 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorViewportSettings.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorViewportSettings.h @@ -499,6 +499,22 @@ public: UPROPERTY(EditAnywhere, config, Category = LookAndFeel) TEnumAsByte MeasuringToolUnits; + /** The size adjustment to apply to selected spline points (in screen space units). */ + UPROPERTY(EditAnywhere, config, Category = LookAndFeel, AdvancedDisplay, meta = (ClampMin = "-5.00", ClampMax = "20.00")) + float SelectedSplinePointSizeAdjustment; + + /** The size adjustment to apply to spline line thickness which increases the spline's hit tolerance. */ + UPROPERTY(EditAnywhere, config, Category = LookAndFeel, AdvancedDisplay, meta = (ClampMin = "0.00")) + float SplineLineThicknessAdjustment; + + /** The size adjustment to apply to spline tangent handle (in screen space units). */ + UPROPERTY(EditAnywhere, config, Category = LookAndFeel, AdvancedDisplay, meta = (ClampMin = "-5.00", ClampMax = "20.00")) + float SplineTangentHandleSizeAdjustment; + + /** The scale to apply to spline tangent lengths */ + UPROPERTY(EditAnywhere, config, Category = LookAndFeel, AdvancedDisplay, meta = (ClampMin = "0.00")) + float SplineTangentScale; + private: // Per-instance viewport settings. diff --git a/Engine/Source/Editor/UnrealEd/Private/AssetDeleteModel.cpp b/Engine/Source/Editor/UnrealEd/Private/AssetDeleteModel.cpp index 2b061f11872b..ff97b05058dd 100644 --- a/Engine/Source/Editor/UnrealEd/Private/AssetDeleteModel.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/AssetDeleteModel.cpp @@ -362,22 +362,16 @@ bool FAssetDeleteModel::CanReplaceReferencesWith( const FAssetData& InAssetData { // Get BP native parent classes UClass* OriginalBPParentClass = CastChecked(PendingDeletes[0]->GetObject())->ParentClass; - const FString BPClassNameToTest = InAssetData.GetTagValueRef(FBlueprintTags::ParentClassPath); + const FString NativeClassName = InAssetData.GetTagValueRef(FBlueprintTags::NativeParentClassPath); - if (!BPClassNameToTest.IsEmpty()) + if (!NativeClassName.IsEmpty()) { - UClass* ParentClassToTest = FindObject(ANY_PACKAGE, *BPClassNameToTest); - if (!ParentClassToTest) - { - ParentClassToTest = LoadObject(nullptr, *BPClassNameToTest); - } - + UClass* NativeParentClassToTest = FindObject(ANY_PACKAGE, *NativeClassName); UClass* NativeParentClassToReplace = FBlueprintEditorUtils::FindFirstNativeClass(OriginalBPParentClass); - UClass* NativeParentClassToTest = FBlueprintEditorUtils::FindFirstNativeClass(ParentClassToTest); if (!NativeParentClassToTest || !NativeParentClassToTest->IsChildOf(NativeParentClassToReplace)) { - // If we couldn't determine the asset parent class (e.g. because the ParentClass tag wasn't present in the FAssetData), or if + // If we couldn't determine the asset parent class (e.g. because the NativeParentClass tag wasn't present in the FAssetData), or if // the asset parent class wasn't equal to or derived from the pending delete BP class, filter i return true; } diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp index e78c461b2790..185e0fcad513 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp @@ -1582,7 +1582,7 @@ bool FAssetRegistryGenerator::GenerateAssetChunkInformationCSV(const FString& Ou const FAssetPackageData* PackageData = State.GetAssetPackageData(AssetData.PackageName); // Add only assets that have actually been cooked and belong to any chunk and that have a file size - if (AssetData.ChunkIDs.Num() > 0 && PackageData->DiskSize > 0) + if (PackageData != nullptr && AssetData.ChunkIDs.Num() > 0 && PackageData->DiskSize > 0) { for (int32 PakchunkIndex : AssetData.ChunkIDs) { diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/ContentCommandlets.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/ContentCommandlets.cpp index 3ee75f332e34..92a9b5f2be76 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/ContentCommandlets.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/ContentCommandlets.cpp @@ -71,12 +71,14 @@ DEFINE_LOG_CATEGORY(LogContentCommandlet); #include "HierarchicalLODUtilitiesModule.h" #include "HierarchicalLOD.h" #include "HierarchicalLODProxyProcessor.h" +#include "HLOD/HLODEngineSubsystem.h" #include "GenericPlatform/GenericPlatformProcess.h" #include "HAL/ThreadManager.h" #include "ShaderCompiler.h" #include "ICollectionManager.h" #include "CollectionManagerModule.h" #include "UObject/UObjectThreadContext.h" +#include "Engine/LODActor.h" /**----------------------------------------------------------------------------- * UResavePackages commandlet. @@ -93,6 +95,7 @@ DEFINE_LOG_CATEGORY(LogContentCommandlet); UResavePackagesCommandlet::UResavePackagesCommandlet(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) + , bForceUATEnvironmentVariableSet(false) { } @@ -941,43 +944,47 @@ int32 UResavePackagesCommandlet::Main( const FString& Params ) } } - /** determine if we are building lighting for the map packages on the pass. **/ + /** determine if we are building HLODs for the map packages on the pass. **/ bShouldBuildHLOD = Switches.Contains(TEXT("BuildHLOD")); - FString HLODOptions; - FParse::Value(*Params, TEXT("BuildOptions="), HLODOptions); - bGenerateClusters = HLODOptions.Contains("Clusters"); - bGenerateMeshProxies = HLODOptions.Contains("Proxies"); - bForceClusterGeneration = HLODOptions.Contains("ForceClusters"); - bForceProxyGeneration = HLODOptions.Contains("ForceProxies"); - bForceSingleClusterForLevel = HLODOptions.Contains("ForceSingleCluster"); - if (bShouldBuildHLOD) { + FString HLODOptions; + FParse::Value(*Params, TEXT("BuildOptions="), HLODOptions); + bGenerateClusters = HLODOptions.Contains("Clusters"); + bGenerateMeshProxies = HLODOptions.Contains("Proxies"); + bForceClusterGeneration = HLODOptions.Contains("ForceClusters"); + bForceProxyGeneration = HLODOptions.Contains("ForceProxies"); + bForceSingleClusterForLevel = HLODOptions.Contains("ForceSingleCluster"); + bHLODMapCleanup = HLODOptions.Contains("MapCleanup"); + + ForceHLODSetupAsset = FString(); + FParse::Value(*Params, TEXT("ForceHLODSetupAsset="), ForceHLODSetupAsset); + + HLODSkipToMap = FString(); + FParse::Value(*Params, TEXT("SkipToMap="), HLODSkipToMap); + UE_LOG(LogContentCommandlet, Display, TEXT("Rebuilding HLODs... Options are:")); UE_LOG(LogContentCommandlet, Display, TEXT(" [%s] Clusters"), bGenerateClusters ? TEXT("X") : TEXT(" ")); UE_LOG(LogContentCommandlet, Display, TEXT(" [%s] Proxies"), bGenerateMeshProxies ? TEXT("X") : TEXT(" ")); UE_LOG(LogContentCommandlet, Display, TEXT(" [%s] ForceClusters"), bForceClusterGeneration ? TEXT("X") : TEXT(" ")); UE_LOG(LogContentCommandlet, Display, TEXT(" [%s] ForceProxies"), bForceProxyGeneration ? TEXT("X") : TEXT(" ")); UE_LOG(LogContentCommandlet, Display, TEXT(" [%s] ForceSingleCluster"), bForceSingleClusterForLevel ? TEXT("X") : TEXT(" ")); - } + UE_LOG(LogContentCommandlet, Display, TEXT(" [%s] Map Cleanup"), bHLODMapCleanup ? TEXT("X") : TEXT(" ")); - ForceHLODSetupAsset = FString(); - FParse::Value(*Params, TEXT("ForceHLODSetupAsset="), ForceHLODSetupAsset); - - HLODSkipToMap = FString(); - FParse::Value(*Params, TEXT("SkipToMap="), HLODSkipToMap); - - bForceUATEnvironmentVariableSet = false; - if (bShouldBuildHLOD) - { + // Allow multiple instances when building HLODs FString MutexVariableValue = FPlatformMisc::GetEnvironmentVariable(TEXT("uebp_UATMutexNoWait")); if (MutexVariableValue != TEXT("1")) { FPlatformMisc::SetEnvironmentVar(TEXT("uebp_UATMutexNoWait"), TEXT("1")); bForceUATEnvironmentVariableSet = true; } - } + if (bHLODMapCleanup) + { + GEngine->GetEngineSubsystem()->DisableHLODCleanupOnLoad(true); + } + } + if (bShouldBuildLighting || bShouldBuildHLOD || bShouldBuildReflectionCaptures) { check( Switches.Contains(TEXT("AllowCommandletRendering")) ); @@ -1285,7 +1292,14 @@ bool UResavePackagesCommandlet::CheckoutFile(const FString& Filename, bool bAddF } else { - UE_LOG(LogContentCommandlet, Error, TEXT("[REPORT] %s could not be added!"), *Filename); + if (!bIgnoreAlreadyCheckedOut) + { + UE_LOG(LogContentCommandlet, Error, TEXT("[REPORT] %s could not be added!"), *Filename); + } + else + { + UE_LOG(LogContentCommandlet, Warning, TEXT("[REPORT] %s could not be added!"), *Filename); + } } } } @@ -1618,6 +1632,20 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, World->GetWorldSettings()->bGenerateSingleClusterForLevel = true; } + bool bHLODLeaveMapUntouched = GetDefault()->bSaveLODActorsToHLODPackages && !bHLODMapCleanup; + + // Maintain a list of packages that needs to be saved after cluster rebuilding. + TSet PackagesToSave; + + if (bHLODMapCleanup) + { + bool bPerformedCleanup = GEngine->GetEngineSubsystem()->CleanupHLODs(World); + if (bPerformedCleanup) + { + PackagesToSave.Add(World->GetOutermost()); + } + } + FHierarchicalLODBuilder Builder(World); if (bForceClusterGeneration) @@ -1630,21 +1658,6 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, Builder.PreviewBuild(); } - bool bSaveHLODActorsToProxyPackage = GetDefault()->bSaveLODActorsToHLODPackages; - - // Get the list of packages that needs to be saved after cluster rebuilding. - TSet PackagesToSave; - if (!bSaveHLODActorsToProxyPackage || bForceClusterGeneration) - { - for (ULevel* Level : World->GetLevels()) - { - if (Level->bIsVisible) - { - PackagesToSave.Add(Level->GetOutermost()); - } - } - } - if (bGenerateMeshProxies || bForceProxyGeneration) { Builder.BuildMeshesForLODActors(bForceProxyGeneration); @@ -1690,7 +1703,7 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, // If the only operation performed by this commandlet is to update HLOD proxy packages, // avoid saving the level files. - if (bSaveHLODActorsToProxyPackage && !bBuildingNonHLODData && !bShouldBuildNavigationData) + if (bHLODLeaveMapUntouched && !bBuildingNonHLODData && !bShouldBuildNavigationData) { bRevertCheckedOutFilesIfNotSaving = false; bShouldProceedWithRebuild = false; diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/ConvertLevelsToExternalActorsCommandlet.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/ConvertLevelsToExternalActorsCommandlet.cpp index 6a22af1047ba..3db8c5901fb8 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/ConvertLevelsToExternalActorsCommandlet.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/ConvertLevelsToExternalActorsCommandlet.cpp @@ -7,19 +7,20 @@ #include "Commandlets/ConvertLevelsToExternalActorsCommandlet.h" #include "HAL/FileManager.h" #include "Misc/Paths.h" -#include "Misc/ConfigCacheIni.h" #include "Editor.h" #include "Engine/Level.h" #include "Engine/World.h" #include "Engine/LevelStreaming.h" #include "UObject/UObjectHash.h" +#include "AssetRegistryModule.h" #include "PackageHelperFunctions.h" #include "ISourceControlOperation.h" #include "SourceControlOperations.h" #include "SourceControlHelpers.h" #include "ISourceControlModule.h" -#include "UObject/MetaData.h" #include "ProfilingDebugging/ScopedTimers.h" +#include "Algo/Sort.h" +#include "Algo/Unique.h" DEFINE_LOG_CATEGORY_STATIC(LogConvertLevelsToExternalActorsCommandlet, All, All); @@ -60,6 +61,242 @@ void UConvertLevelsToExternalActorsCommandlet::GetSubLevelsToConvert(ULevel* Mai } } +bool UConvertLevelsToExternalActorsCommandlet::CheckExternalActors(const FString& Level, bool bRepair) +{ + const FString LevelExternalPathActors = ULevel::GetExternalActorsPath(Level); + + // Gather duplicated actor files. + TMultiMap DuplicatedActorFiles; + { + TMap ActorFiles; + + IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")).Get(); + AssetRegistry.OnAssetAdded().AddLambda([&ActorFiles](const FAssetData& AssetData) + { + check(!ActorFiles.Contains(AssetData.ObjectPath)); + ActorFiles.Add(AssetData.ObjectPath, AssetData.PackageName); + }); + + AssetRegistry.OnAssetUpdated().AddLambda([&ActorFiles, &DuplicatedActorFiles](const FAssetData& AssetData) + { + FName ExistingPackageName; + if (ActorFiles.RemoveAndCopyValue(AssetData.ObjectPath, ExistingPackageName)) + { + DuplicatedActorFiles.Add(AssetData.ObjectPath, ExistingPackageName); + } + + DuplicatedActorFiles.Add(AssetData.ObjectPath, AssetData.PackageName); + }); + + AssetRegistry.ScanPathsSynchronous({LevelExternalPathActors}); + } + + if (DuplicatedActorFiles.Num()) + { + // Gather unique keys from the duplicated map. + // Note: TMultiMap::GenerateKeyArray will return duplicated keys, clean that. + TArray DuplicatedActorFilesKeys; + DuplicatedActorFiles.GenerateKeyArray(DuplicatedActorFilesKeys); + DuplicatedActorFilesKeys.Sort([](const FName& A, const FName& B) { return A.FastLess(B); }); + int32 EndIndex = Algo::Unique(DuplicatedActorFilesKeys); + DuplicatedActorFilesKeys.RemoveAt(EndIndex, DuplicatedActorFilesKeys.Num() - EndIndex); + + // Report or delete duplicated entries, keeping the latest one + for (const FName& DuplicatedActorFileKey : DuplicatedActorFilesKeys) + { + TArray DuplicatedActorFilesPaths; + DuplicatedActorFiles.MultiFind(DuplicatedActorFileKey, DuplicatedActorFilesPaths); + check(DuplicatedActorFilesPaths.Num() > 1); + + FDateTime MostRecentStamp; + int32 MostRecentIndex = INDEX_NONE; + + for (int32 i=0; i MostRecentStamp)) + { + MostRecentIndex = i; + MostRecentStamp = FileTimeStamp; + } + } + + check(MostRecentIndex != INDEX_NONE); + + for (int32 i=0; iIsSourceControlled()) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Log, TEXT("Adding package %s to source control"), *PackageFilename); + if (GetSourceControlProvider().Execute(ISourceControlOperation::Create(), Package) != ECommandResult::Succeeded) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Error adding %s to source control."), *PackageFilename); + return false; + } + } + } + + return true; +} + +bool UConvertLevelsToExternalActorsCommandlet::SavePackage(UPackage* Package) +{ + FString PackageFileName = SourceControlHelpers::PackageFilename(Package); + if (!UPackage::SavePackage(Package, nullptr, RF_Standalone, *PackageFileName, GError, nullptr, false, true, SAVE_None)) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Error saving %s"), *PackageFileName); + return false; + } + + return true; +} + +bool UConvertLevelsToExternalActorsCommandlet::CheckoutPackage(UPackage* Package) +{ + if (UseSourceControl()) + { + FString PackageFilename = SourceControlHelpers::PackageFilename(Package); + FSourceControlStatePtr SourceControlState = GetSourceControlProvider().GetState(PackageFilename, EStateCacheUsage::ForceUpdate); + + if (SourceControlState.IsValid()) + { + FString OtherCheckedOutUser; + if (SourceControlState->IsCheckedOutOther(&OtherCheckedOutUser)) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Overwriting package %s already checked out by %s, will not submit"), *PackageFilename, *OtherCheckedOutUser); + return false; + } + else if (!SourceControlState->IsCurrent()) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Overwriting package %s (not at head revision), will not submit"), *PackageFilename); + return false; + } + else if (SourceControlState->IsCheckedOut() || SourceControlState->IsAdded()) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Log, TEXT("Skipping package %s (already checked out)"), *PackageFilename); + return true; + } + else if (SourceControlState->IsSourceControlled()) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Log, TEXT("Checking out package %s from source control"), *PackageFilename); + return GetSourceControlProvider().Execute(ISourceControlOperation::Create(), Package) == ECommandResult::Succeeded; + } + } + } + else + { + FString PackageFilename = SourceControlHelpers::PackageFilename(Package); + if (IPlatformFile::GetPlatformPhysical().FileExists(*PackageFilename)) + { + if (!IPlatformFile::GetPlatformPhysical().SetReadOnly(*PackageFilename, false)) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Error setting %s writable"), *PackageFilename); + return false; + } + } + } + + return true; +} + +bool UConvertLevelsToExternalActorsCommandlet::DeleteFile(const FString& Filename) +{ + if (!UseSourceControl()) + { + if (!IPlatformFile::GetPlatformPhysical().SetReadOnly(*Filename, false) || + !IPlatformFile::GetPlatformPhysical().DeleteFile(*Filename)) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Error deleting %s"), *Filename); + return false; + } + } + else + { + FSourceControlStatePtr SourceControlState = GetSourceControlProvider().GetState(Filename, EStateCacheUsage::ForceUpdate); + + if (SourceControlState.IsValid() && SourceControlState->IsSourceControlled()) + { + FString OtherCheckedOutUser; + if (SourceControlState->IsCheckedOutOther(&OtherCheckedOutUser)) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Overwriting package %s already checked out by %s, will not submit"), *Filename, *OtherCheckedOutUser); + return false; + } + else if (!SourceControlState->IsCurrent()) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Overwriting package %s (not at head revision), will not submit"), *Filename); + return false; + } + else if (SourceControlState->IsAdded()) + { + if (GetSourceControlProvider().Execute(ISourceControlOperation::Create(), Filename) != ECommandResult::Succeeded) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Error reverting package %s from source control"), *Filename); + return false; + } + } + else + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Log, TEXT("Deleting package %s from source control"), *Filename); + + if (SourceControlState->IsCheckedOut()) + { + if (GetSourceControlProvider().Execute(ISourceControlOperation::Create(), Filename) != ECommandResult::Succeeded) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Error reverting package %s from source control"), *Filename); + return false; + } + } + + if (GetSourceControlProvider().Execute(ISourceControlOperation::Create(), Filename) != ECommandResult::Succeeded) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Error deleting package %s from source control"), *Filename); + return false; + } + } + } + else + { + if (!IFileManager::Get().Delete(*Filename, false, true)) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Error deleting package %s locally"), *Filename); + return false; + } + } + } + + return true; +} + int32 UConvertLevelsToExternalActorsCommandlet::Main(const FString& Params) { FAutoScopedDurationTimer ConversionTimer; @@ -78,10 +315,25 @@ int32 UConvertLevelsToExternalActorsCommandlet::Main(const FString& Params) bool bConvertSubLevel = Switches.Contains(TEXT("convertsublevels")); bool bRecursiveSubLevel = Switches.Contains(TEXT("recursive")); bool bConvertToExternal = !Switches.Contains(TEXT("internal")); + bool bRepairActorFiles = Switches.Contains(TEXT("repair")); FScopedSourceControl SourceControl; SourceControlProvider = bNoSourceControl ? nullptr : &ISourceControlModule::Get().GetProvider(); + // This will convert incomplete package name to a fully qualifed path + if (!FPackageName::SearchForPackageOnDisk(Tokens[0], &Tokens[0])) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Unknown level '%s'"), *Tokens[0]); + return 1; + } + + // Check external actors consistency for this level + if (!CheckExternalActors(Tokens[0], bRepairActorFiles)) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("External actor files inconsistency")); + return 1; + } + // Load persistent level ULevel* MainLevel = LoadLevel(Tokens[0]); if (!MainLevel) @@ -99,6 +351,15 @@ int32 UConvertLevelsToExternalActorsCommandlet::Main(const FString& Params) { GetSubLevelsToConvert(MainLevel, LevelsToConvert, bRecursiveSubLevel); } + + for(ULevel* Level : LevelsToConvert) + { + if (!Level->bContainsStableActorGUIDs) + { + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Unable to convert level '%s' with non-stable actor GUIDs. Resave the level before converting."), *Level->GetPackage()->GetName()); + return 1; + } + } TArray PackagesToSave; for(ULevel* Level : LevelsToConvert) @@ -110,27 +371,33 @@ int32 UConvertLevelsToExternalActorsCommandlet::Main(const FString& Params) PackagesToSave.Append(Level->GetLoadedExternalActorPackages()); } - if (UseSourceControl()) + for (UPackage* PackageToSave : PackagesToSave) { - FEditorFileUtils::CheckoutPackages(PackagesToSave, nullptr, false); - } - else - { - for (UPackage* Package : PackagesToSave) + if(!CheckoutPackage(PackageToSave)) { - FString PackageFilename = SourceControlHelpers::PackageFilename(Package); - if (IPlatformFile::GetPlatformPhysical().FileExists(*PackageFilename)) - { - if (!IPlatformFile::GetPlatformPhysical().SetReadOnly(*PackageFilename, false)) - { - UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Error, TEXT("Error setting %s writable"), *PackageFilename); - return 1; - } - } + return 1; } } - FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, false, false, nullptr, true, false); - UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Log, TEXT("Conversion took %.2f seconds"), ConversionTimer.GetTime()); + // Save packages + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Log, TEXT("Saving %d packages."), PackagesToSave.Num()); + for (UPackage* PackageToSave : PackagesToSave) + { + if (!SavePackage(PackageToSave)) + { + return 1; + } + } + + // Add new packages to source control + for (UPackage* PackageToSave : PackagesToSave) + { + if(!AddPackageToSourceControl(PackageToSave)) + { + return 1; + } + } + + UE_LOG(LogConvertLevelsToExternalActorsCommandlet, Log, TEXT("Conversion took %.2f seconds"), ConversionTimer.GetTime()); return 0; } \ No newline at end of file diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/CookCommandlet.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/CookCommandlet.cpp index d73e797b61d3..fa7954c8b5d5 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/CookCommandlet.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/CookCommandlet.cpp @@ -855,7 +855,7 @@ bool UCookCommandlet::CookByTheBook( const TArray& Platforms) CookOptions |= Switches.Contains(TEXT("SkipSoftReferences")) ? ECookByTheBookOptions::SkipSoftReferences : ECookByTheBookOptions::None; CookOptions |= Switches.Contains(TEXT("SkipHardReferences")) ? ECookByTheBookOptions::SkipHardReferences : ECookByTheBookOptions::None; CookOptions |= Switches.Contains(TEXT("CookAgainstFixedBase")) ? ECookByTheBookOptions::CookAgainstFixedBase : ECookByTheBookOptions::None; - CookOptions |= Switches.Contains(TEXT("DLCNoCookAllAssets")) ? ECookByTheBookOptions::DLCNoCookAllAssets : ECookByTheBookOptions::None; + CookOptions |= (Switches.Contains(TEXT("DlcLoadMainAssetRegistry")) || !bErrorOnEngineContentUse) ? ECookByTheBookOptions::DlcLoadMainAssetRegistry : ECookByTheBookOptions::None; if (bCookSinglePackage) { diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/ShaderPipelineCacheToolsCommandlet.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/ShaderPipelineCacheToolsCommandlet.cpp index b89c1aceabb8..5ab206b4ff23 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/ShaderPipelineCacheToolsCommandlet.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/ShaderPipelineCacheToolsCommandlet.cpp @@ -14,6 +14,7 @@ #include "Serialization/MemoryReader.h" #include "Serialization/MemoryWriter.h" #include "String/ParseLines.h" +#include "HAL/PlatformFileManager.h" DEFINE_LOG_CATEGORY_STATIC(LogShaderPipelineCacheTools, Log, All); @@ -21,7 +22,16 @@ const TCHAR* STABLE_CSV_EXT = TEXT("stablepc.csv"); const TCHAR* STABLE_CSV_COMPRESSED_EXT = TEXT("stablepc.csv.compressed"); const TCHAR* STABLE_COMPRESSED_EXT = TEXT(".compressed"); const int32 STABLE_COMPRESSED_EXT_LEN = 11; // len of ".compressed"; -const int32 STABLE_COMPRESSED_VER = 1; +const int32 STABLE_COMPRESSED_VER = 2; +const int64 STABLE_MAX_CHUNK_SIZE = MAX_int32 - 100 * 1024 * 1024; + +struct FSCDataChunk +{ + FSCDataChunk() : UncomressedOutputLines(), OutputLinesAr(UncomressedOutputLines) {} + + TArray UncomressedOutputLines; + FMemoryWriter OutputLinesAr; +}; void ExpandWildcards(TArray& Parts) @@ -124,7 +134,7 @@ static void LoadStableSCLs(TMultiMap& StableMap, TArray& UncompressedData) +static bool LoadAndDecompressStableCSV(const FString& Filename, TArray& OutputLines) { bool bResult = false; FArchive* Ar = IFileManager::Get().CreateFileReader(*Filename); @@ -133,29 +143,48 @@ static bool LoadAndDecompressStableCSV(const FString& Filename, TArray& U if (Ar->TotalSize() > 8) { int32 CompressedVersion = 0; - int32 UncompressedSize = 0; - int32 CompressedSize = 0; - - Ar->Serialize(&CompressedVersion, sizeof(int32)); - Ar->Serialize(&UncompressedSize, sizeof(int32)); - Ar->Serialize(&CompressedSize, sizeof(int32)); - - TArray CompressedData; - CompressedData.SetNumUninitialized(CompressedSize); - Ar->Serialize(CompressedData.GetData(), CompressedSize); + int32 NumChunks = 1; - UncompressedData.SetNumUninitialized(UncompressedSize); - bResult = FCompression::UncompressMemory(NAME_Zlib, UncompressedData.GetData(), UncompressedSize, CompressedData.GetData(), CompressedSize); - if (!bResult) + Ar->Serialize(&CompressedVersion, sizeof(int32)); + if (CompressedVersion > 1) { - UE_LOG(LogShaderPipelineCacheTools, Display, TEXT("Failed to decompress file %s"), *Filename); + Ar->Serialize(&NumChunks, sizeof(int32)); + } + + for (int32 Index = 0; Index < NumChunks; ++Index) + { + int32 UncompressedSize = 0; + int32 CompressedSize = 0; + + Ar->Serialize(&UncompressedSize, sizeof(int32)); + Ar->Serialize(&CompressedSize, sizeof(int32)); + + TArray CompressedData; + CompressedData.SetNumUninitialized(CompressedSize); + Ar->Serialize(CompressedData.GetData(), CompressedSize); + + TArray UncompressedData; + UncompressedData.SetNumUninitialized(UncompressedSize); + bResult = FCompression::UncompressMemory(NAME_Zlib, UncompressedData.GetData(), UncompressedSize, CompressedData.GetData(), CompressedSize); + if (!bResult) + { + UE_LOG(LogShaderPipelineCacheTools, Display, TEXT("Failed to decompress file %s"), *Filename); + } + + FMemoryReader MemArchive(UncompressedData); + FString LineCSV; + while (!MemArchive.AtEnd()) + { + MemArchive << LineCSV; + OutputLines.Add(LineCSV); + } } } else { UE_LOG(LogShaderPipelineCacheTools, Display, TEXT("Corrupted file %s"), *Filename); } - + delete Ar; } else @@ -166,89 +195,95 @@ static bool LoadAndDecompressStableCSV(const FString& Filename, TArray& U return bResult; } -struct FRawStableCSV +static void ReadStableCSV(const TArray& CSVLines, const TFunctionRef& LineVisitor) { - TArray SerializedData; - FString CSV; -}; - -static bool LoadStableCSV(const FString& FileName, FRawStableCSV& RawStableCSV) -{ - if (FileName.EndsWith(STABLE_CSV_COMPRESSED_EXT)) + for (const FString& LineCSV : CSVLines) { - return LoadAndDecompressStableCSV(FileName, RawStableCSV.SerializedData); - } - else - { - return FFileHelper::LoadFileToString(RawStableCSV.CSV, *FileName); + LineVisitor(LineCSV); } } -static void ReadStableCSV(const FRawStableCSV& RawStableCSV, const TFunctionRef& LineVisitor) +static bool LoadStableCSV(const FString& Filename, TArray& OutputLines) { - if (RawStableCSV.SerializedData.Num()) + bool bResult = false; + if (Filename.EndsWith(STABLE_CSV_COMPRESSED_EXT)) { - FMemoryReader MemArchive(RawStableCSV.SerializedData); - FString LineCSV; - while (!MemArchive.AtEnd()) + if (LoadAndDecompressStableCSV(Filename, OutputLines)) { - MemArchive << LineCSV; - LineVisitor(LineCSV); + bResult = true; } } else { - UE::String::ParseLines(RawStableCSV.CSV, LineVisitor); + bResult = FFileHelper::LoadFileToStringArray(OutputLines, *Filename); } + + return bResult; } -static bool LoadStableCSV(const FString& FileName, TArray& OutputLines) -{ - FRawStableCSV RawStableCSV; - if (LoadStableCSV(FileName, RawStableCSV)) - { - ReadStableCSV(RawStableCSV, [&OutputLines](FStringView Line) { OutputLines.Emplace(Line); }); - return true; - } - return false; -} - -static int64 SaveStableCSV(const FString& Filename, const TArray& UncompressedData) +static int64 SaveStableCSV(const FString& Filename, const FSCDataChunk* DataChunks, int32 NumChunks) { if (Filename.EndsWith(STABLE_CSV_COMPRESSED_EXT)) { - int32 UncompressedSize = UncompressedData.Num(); - UE_LOG(LogShaderPipelineCacheTools, Display, TEXT("Compressing output, size = %.1fKB"), UncompressedSize/1024.f); - int32 CompressedSize = FCompression::CompressMemoryBound(NAME_Zlib, UncompressedSize); - TArray CompressedData; - CompressedData.SetNumZeroed(CompressedSize); + UE_LOG(LogShaderPipelineCacheTools, Display, TEXT("Compressing output, %d chunks"), NumChunks); - if (FCompression::CompressMemory(NAME_Zlib, CompressedData.GetData(), CompressedSize, UncompressedData.GetData(), UncompressedSize)) + struct FSCCompressedChunk { - FArchive* Ar = IFileManager::Get().CreateFileWriter(*Filename); - if (!Ar) + FSCCompressedChunk(int32 UncompressedSize) { - UE_LOG(LogShaderPipelineCacheTools, Fatal, TEXT("Failed to open %s"), *Filename); - return -1; + CompressedSize = FCompression::CompressMemoryBound(NAME_Zlib, UncompressedSize); + CompressedData.SetNumZeroed(CompressedSize); } - - int32 CompressedVersion = STABLE_COMPRESSED_VER; - - Ar->Serialize(&CompressedVersion, sizeof(int32)); - Ar->Serialize(&UncompressedSize, sizeof(int32)); - Ar->Serialize(&CompressedSize, sizeof(int32)); - Ar->Serialize(CompressedData.GetData(), CompressedSize); - delete Ar; - } - else + + TArray CompressedData; + int32 CompressedSize; + }; + + TArray CompressedChunks; + + for (int32 Index = 0; Index < NumChunks; ++Index) { - UE_LOG(LogShaderPipelineCacheTools, Fatal, TEXT("Failed to compress (%.1f KB)"), UncompressedSize/1024.f); + const FSCDataChunk& Chunk = DataChunks[Index]; + CompressedChunks.Add(FSCCompressedChunk(Chunk.UncomressedOutputLines.Num())); + + UE_LOG(LogShaderPipelineCacheTools, Display, TEXT("Compressing chunk %d, size = %.1fKB"), Index, Chunk.UncomressedOutputLines.Num() / 1024.f); + if (FCompression::CompressMemory(NAME_Zlib, CompressedChunks[Index].CompressedData.GetData(), CompressedChunks[Index].CompressedSize, Chunk.UncomressedOutputLines.GetData(), Chunk.UncomressedOutputLines.Num()) == false) + { + UE_LOG(LogShaderPipelineCacheTools, Fatal, TEXT("Failed to compress chunk %d (%.1f KB)"), Index, Chunk.UncomressedOutputLines.Num() / 1024.f); + } + } + + FArchive* Ar = IFileManager::Get().CreateFileWriter(*Filename); + if (!Ar) + { + UE_LOG(LogShaderPipelineCacheTools, Fatal, TEXT("Failed to open %s"), *Filename); return -1; } + + int32 CompressedVersion = STABLE_COMPRESSED_VER; + + Ar->Serialize(&CompressedVersion, sizeof(int32)); + Ar->Serialize(&NumChunks, sizeof(int32)); + + for (int32 Index = 0; Index < NumChunks; ++Index) + { + int32 UncompressedSize = DataChunks[Index].UncomressedOutputLines.Num(); + int32 CompressedSize = CompressedChunks[Index].CompressedSize; + Ar->Serialize(&UncompressedSize, sizeof(int32)); + Ar->Serialize(&CompressedSize, sizeof(int32)); + Ar->Serialize(CompressedChunks[Index].CompressedData.GetData(), CompressedSize); + } + + delete Ar; } else { - FMemoryReader MemArchive(UncompressedData); + if (NumChunks > 1) + { + UE_LOG(LogShaderPipelineCacheTools, Fatal, TEXT("SaveStableCSV does not support saving uncompressed files larger than 2GB.")); + } + + FMemoryReader MemArchive(DataChunks[0].UncomressedOutputLines); FString CombinedCSV; FString LineCSV; while (!MemArchive.AtEnd()) @@ -260,7 +295,7 @@ static int64 SaveStableCSV(const FString& Filename, const TArray& Uncompr FFileHelper::SaveStringToFile(CombinedCSV, *Filename); } - + int64 Size = IFileManager::Get().FileSize(*Filename); if (Size < 1) { @@ -927,9 +962,9 @@ int32 ExpandPSOSC(const TArray& Tokens) } int32 NumLines = 0; - TArray UncomressedOutputLines; - FMemoryWriter OutputLinesAr(UncomressedOutputLines); - TSet DeDup; + FSCDataChunk DataChunks[16]; + int32 CurrentChunk = 0; + TSet DeDup; { FString PSOLine = FString::Printf(TEXT("\"%s\""), *FPipelineCacheFileFormatPSO::CommonHeaderLine()); @@ -939,7 +974,7 @@ int32 ExpandPSOSC(const TArray& Tokens) PSOLine += FString::Printf(TEXT(",\"shaderslot%d: %s\""), SlotIndex, *FStableShaderKeyAndValue::HeaderLine()); } - OutputLinesAr << PSOLine; + DataChunks[CurrentChunk].OutputLinesAr << PSOLine; NumLines++; } @@ -1065,10 +1100,15 @@ int32 ExpandPSOSC(const TArray& Tokens) UE_LOG(LogShaderPipelineCacheTools, Error, TEXT("Unexpected pipeline cache descriptor type %d"), int32(Item.PSO->Type)); } - if (!DeDup.Contains(PSOLine)) + const uint32 PSOLineHash = FCrc::MemCrc32(PSOLine.GetCharArray().GetData(), sizeof(TCHAR) * PSOLine.Len()); + if (!DeDup.Contains(PSOLineHash)) { - DeDup.Add(PSOLine); - OutputLinesAr << PSOLine; + DeDup.Add(PSOLineHash); + if (DataChunks[CurrentChunk].OutputLinesAr.TotalSize() + (int64)((PSOLine.Len() + 1) * sizeof(TCHAR)) >= STABLE_MAX_CHUNK_SIZE) + { + ++CurrentChunk; + } + DataChunks[CurrentChunk].OutputLinesAr << PSOLine; NumLines++; } } @@ -1108,7 +1148,7 @@ int32 ExpandPSOSC(const TArray& Tokens) } } - int64 FileSize = SaveStableCSV(OutputFilename, UncomressedOutputLines); + int64 FileSize = SaveStableCSV(OutputFilename, DataChunks, CurrentChunk + 1); if (FileSize < 1) { return 1; @@ -1139,13 +1179,13 @@ static void ParseQuoteComma(const FStringView& InLine, TArray ParseStableCSV(const FString& FileName, const FRawStableCSV& RawStableCSV, const TMultiMap& StableMap, FName& TargetPlatform) +static TSet ParseStableCSV(const FString& FileName, const TArray& CSVLines, const TMultiMap& StableMap, FName& TargetPlatform) { TSet PSOs; int32 LineIndex = 0; bool bParsed = true; - ReadStableCSV(RawStableCSV, [&FileName, &StableMap, &TargetPlatform, &PSOs, &LineIndex, &bParsed](FStringView Line) + ReadStableCSV(CSVLines, [&FileName, &StableMap, &TargetPlatform, &PSOs, &LineIndex, &bParsed](FStringView Line) { // Skip the header line. if (LineIndex++ == 0) @@ -1810,14 +1850,14 @@ int32 BuildPSOSC(const TArray& Tokens) // Read the stable PSO sets in parallel with the stable shaders. FGraphEventArray LoadPSOTasks; - TArray RawStableCSVs; + TArray> StableCSVs; LoadPSOTasks.Reserve(StablePipelineCacheFiles.Num()); - RawStableCSVs.AddDefaulted(StablePipelineCacheFiles.Num()); + StableCSVs.AddDefaulted(StablePipelineCacheFiles.Num()); for (int32 FileIndex = 0; FileIndex < StablePipelineCacheFiles.Num(); ++FileIndex) { - LoadPSOTasks.Add(FFunctionGraphTask::CreateAndDispatchWhenReady([&RawStableCSV = RawStableCSVs[FileIndex], &FileName = StablePipelineCacheFiles[FileIndex]] + LoadPSOTasks.Add(FFunctionGraphTask::CreateAndDispatchWhenReady([&StableCSV = StableCSVs[FileIndex], &FileName = StablePipelineCacheFiles[FileIndex]] { - if (!LoadStableCSV(FileName, RawStableCSV)) + if (!LoadStableCSV(FileName, StableCSV)) { UE_LOG(LogShaderPipelineCacheTools, Fatal, TEXT("Could not load %s"), *FileName); } @@ -1837,12 +1877,12 @@ int32 BuildPSOSC(const TArray& Tokens) ParsePSOTasks.Add(FFunctionGraphTask::CreateAndDispatchWhenReady( [&PSOs = PSOsByFile[FileIndex], &FileName = StablePipelineCacheFiles[FileIndex], - &RawStableCSV = RawStableCSVs[FileIndex], + &StableCSV = StableCSVs[FileIndex], &StableMap, &TargetPlatform = TargetPlatformByFile[FileIndex]] { - PSOs = ParseStableCSV(FileName, RawStableCSV, StableMap, TargetPlatform); - RawStableCSV = FRawStableCSV(); + PSOs = ParseStableCSV(FileName, StableCSV, StableMap, TargetPlatform); + StableCSV.Empty(); UE_LOG(LogShaderPipelineCacheTools, Display, TEXT("Loaded %d stable PSO lines from %s."), PSOs.Num(), *FileName); }, TStatId(), &PreReqs)); } @@ -2104,7 +2144,7 @@ int32 DiffStable(const TArray& Tokens) int32 DecompressCSV(const TArray& Tokens) { - TArray DecompressedData; + TArray DecompressedData; for (int32 TokenIndex = 0; TokenIndex < Tokens.Num(); TokenIndex++) { const FString& CompressedFilename = Tokens[TokenIndex]; @@ -2112,22 +2152,27 @@ int32 DecompressCSV(const TArray& Tokens) { continue; } - + FString CombinedCSV; DecompressedData.Reset(); if (LoadAndDecompressStableCSV(CompressedFilename, DecompressedData)) { - FMemoryReader MemArchive(DecompressedData); - FString LineCSV; - while (!MemArchive.AtEnd()) + FString FilenameCSV = CompressedFilename.LeftChop(STABLE_COMPRESSED_EXT_LEN); + FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*FilenameCSV); + + for (const FString& LineCSV : DecompressedData) { - MemArchive << LineCSV; CombinedCSV.Append(LineCSV); CombinedCSV.Append(LINE_TERMINATOR); + + if ((int64)(CombinedCSV.Len() * sizeof(TCHAR)) >= (int64)(MAX_int32 - 1024 * 1024)) + { + FFileHelper::SaveStringToFile(CombinedCSV, *FilenameCSV, FFileHelper::EEncodingOptions::AutoDetect, &IFileManager::Get(), FILEWRITE_Append); + CombinedCSV.Empty(); + } } - FString FilenameCSV = CompressedFilename.LeftChop(STABLE_COMPRESSED_EXT_LEN); - FFileHelper::SaveStringToFile(CombinedCSV, *FilenameCSV); + FFileHelper::SaveStringToFile(CombinedCSV, *FilenameCSV, FFileHelper::EEncodingOptions::AutoDetect, &IFileManager::Get(), FILEWRITE_Append); } } diff --git a/Engine/Source/Editor/UnrealEd/Private/ComponentTypeRegistry.cpp b/Engine/Source/Editor/UnrealEd/Private/ComponentTypeRegistry.cpp index cab421515d49..ff72699aa549 100644 --- a/Engine/Source/Editor/UnrealEd/Private/ComponentTypeRegistry.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/ComponentTypeRegistry.cpp @@ -209,6 +209,7 @@ FComponentTypeRegistryData::FComponentTypeRegistryData() void FComponentTypeRegistryData::ForceRefreshComponentList() { + bNeedsRefreshNextTick = false; ComponentClassList.Empty(); ComponentTypeList.Empty(); @@ -409,7 +410,6 @@ void FComponentTypeRegistryData::ForceRefreshComponentList() void FComponentTypeRegistryData::Tick(float) { bool bRequiresRefresh = bNeedsRefreshNextTick; - bNeedsRefreshNextTick = false; if (PendingAssetData.Num() != 0) { diff --git a/Engine/Source/Editor/UnrealEd/Private/ComponentVisualizerManager.cpp b/Engine/Source/Editor/UnrealEd/Private/ComponentVisualizerManager.cpp index c9b819ac0a42..500c52893c18 100644 --- a/Engine/Source/Editor/UnrealEd/Private/ComponentVisualizerManager.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/ComponentVisualizerManager.cpp @@ -107,10 +107,7 @@ bool FComponentVisualizerManager::HandleInputKey(FEditorViewportClient* Viewport if (EditedVisualizer.IsValid()) { - if(EditedVisualizer->HandleInputKey(ViewportClient, Viewport, Key, Event)) - { - return true; - } + return EditedVisualizer->HandleInputKey(ViewportClient, Viewport, Key, Event); } return false; @@ -120,12 +117,9 @@ bool FComponentVisualizerManager::HandleInputDelta(FEditorViewportClient* InView { TSharedPtr EditedVisualizer = EditedVisualizerPtr.Pin(); - if (EditedVisualizer.IsValid() && EditedVisualizerViewportClient == InViewportClient && InViewportClient->GetCurrentWidgetAxis() != EAxisList::None) + if (EditedVisualizer.IsValid() && InViewportClient && InViewportClient->GetCurrentWidgetAxis() != EAxisList::None) { - if (EditedVisualizer->HandleInputDelta(InViewportClient, InViewport, InDrag, InRot, InScale)) - { - return true; - } + return EditedVisualizer->HandleInputDelta(InViewportClient, InViewport, InDrag, InRot, InScale); } return false; @@ -135,12 +129,9 @@ bool FComponentVisualizerManager::HandleFrustumSelect(const FConvexVolume& InFru { TSharedPtr EditedVisualizer = EditedVisualizerPtr.Pin(); - if (EditedVisualizer.IsValid() && EditedVisualizerViewportClient == InViewportClient) + if (EditedVisualizer.IsValid()) { - if (EditedVisualizer->HandleFrustumSelect(InFrustum, InViewportClient, InViewport)) - { - return true; - } + return EditedVisualizer->HandleFrustumSelect(InFrustum, InViewportClient, InViewport); } return false; @@ -150,12 +141,9 @@ bool FComponentVisualizerManager::HandleBoxSelect(const FBox& InBox, FEditorView { TSharedPtr EditedVisualizer = EditedVisualizerPtr.Pin(); - if (EditedVisualizer.IsValid() && EditedVisualizerViewportClient == InViewportClient) + if (EditedVisualizer.IsValid()) { - if (EditedVisualizer->HandleBoxSelect(InBox, InViewportClient, InViewport)) - { - return true; - } + return EditedVisualizer->HandleBoxSelect(InBox, InViewportClient, InViewport); } return false; @@ -167,10 +155,7 @@ bool FComponentVisualizerManager::HasFocusOnSelectionBoundingBox(FBox& OutBoundi if (EditedVisualizer.IsValid()) { - if (EditedVisualizer->HasFocusOnSelectionBoundingBox(OutBoundingBox)) - { - return true; - } + return EditedVisualizer->HasFocusOnSelectionBoundingBox(OutBoundingBox); } return false; @@ -182,10 +167,7 @@ bool FComponentVisualizerManager::HandleSnapTo(const bool bInAlign, const bool b if (EditedVisualizer.IsValid()) { - if (EditedVisualizer->HandleSnapTo(bInAlign, bInUseLineTrace, bInUseBounds, bInUsePivot, InDestination)) - { - return true; - } + return EditedVisualizer->HandleSnapTo(bInAlign, bInUseLineTrace, bInUseBounds, bInUsePivot, InDestination); } return false; diff --git a/Engine/Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp b/Engine/Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp index 88bcffbf6e1e..1cea6038ba43 100644 --- a/Engine/Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp @@ -344,7 +344,7 @@ const FString& GetDevelopmentAssetRegistryFilename() */ void LogCookerMessage( const FString& MessageText, EMessageSeverity::Type Severity) { - FMessageLog MessageLog("CookResults"); + FMessageLog MessageLog("LogCook"); TSharedRef Message = FTokenizedMessage::Create(Severity); @@ -397,7 +397,7 @@ public: bool bFullLoadAndSave = false; bool bPackageStore = false; bool bCookAgainstFixedBase = false; - bool bDLCNoCookAllAssets = true; + bool bDlcLoadMainAssetRegistry = false; TArray StartupPackages; /** Mapping from source packages to their localized variants (based on the culture list in FCookByTheBookStartupOptions) */ @@ -647,7 +647,6 @@ bool UCookOnTheFlyServer::BroadcastFileserverPresence( const FGuid &InstanceId ) if ((NetworkFileServer == NULL || !NetworkFileServer->IsItReadyToAcceptConnections() || !NetworkFileServer->GetAddressList(AddressList))) { LogCookerMessage( FString(TEXT("Failed to create network file server")), EMessageSeverity::Error ); - UE_LOG(LogCook, Error, TEXT("Failed to create network file server")); continue; } @@ -845,7 +844,6 @@ void UCookOnTheFlyServer::GetDependentPackages( const TSet& RootPackages, FText::FromString(PackageDependencyString), OutReason); LogCookerMessage(FailMessage.ToString(), EMessageSeverity::Warning); - UE_LOG(LogCook, Warning, TEXT("%s"), *( FailMessage.ToString() )); continue; } else if (FPackageName::IsScriptPackage(PackageDependencyString) || FPackageName::IsMemoryPackage(PackageDependencyString)) @@ -1016,6 +1014,11 @@ bool UCookOnTheFlyServer::IsCookingAgainstFixedBase() const return IsCookingDLC() && CookByTheBookOptions && CookByTheBookOptions->bCookAgainstFixedBase; } +bool UCookOnTheFlyServer::ShouldPopulateFullAssetRegistry() const +{ + return !IsCookingDLC() || (CookByTheBookOptions && CookByTheBookOptions->bDlcLoadMainAssetRegistry); +} + FString UCookOnTheFlyServer::GetBaseDirectoryForDLC() const { TSharedPtr Plugin = IPluginManager::Get().FindPlugin(CookByTheBookOptions->DlcName); @@ -2103,7 +2106,6 @@ bool UCookOnTheFlyServer::LoadPackageForCooking(UE::Cook::FPackageData& PackageD if ((!IsCookOnTheFlyMode()) || (!IsCookingInEditor())) { LogCookerMessage(FString::Printf(TEXT("Error loading %s!"), *FileName), EMessageSeverity::Error); - UE_LOG(LogCook, Error, TEXT("Error loading %s!"), *FileName); } } GOutputCookingWarnings = false; @@ -3453,7 +3455,6 @@ void UCookOnTheFlyServer::SaveCookedPackage(UE::Cook::FPackageData& PackageData, if (FullFilename.Len() >= FPlatformMisc::GetMaxPathLength()) { LogCookerMessage(FString::Printf(TEXT("Couldn't save package, filename is too long (%d >= %d): %s"), FullFilename.Len(), FPlatformMisc::GetMaxPathLength(), *PlatFilename), EMessageSeverity::Error); - UE_LOG(LogCook, Error, TEXT("Couldn't save package, filename is too long (%d >= %d): %s"), FullFilename.Len(), FPlatformMisc::GetMaxPathLength(), *PlatFilename); Result = ESavePackageResult::Error; } else @@ -4945,7 +4946,16 @@ void UCookOnTheFlyServer::GenerateAssetRegistry() if (!bCanDelayAssetregistryProcessing) { TArray ScanPaths; - if (GConfig->GetArray(TEXT("AssetRegistry"), TEXT("PathsToScanForCook"), ScanPaths, GEngineIni) > 0 && !AssetRegistry->IsLoadingAssets()) + if (ShouldPopulateFullAssetRegistry()) + { + GConfig->GetArray(TEXT("AssetRegistry"), TEXT("PathsToScanForCook"), ScanPaths, GEngineIni); + } + else if (IsCookingDLC()) + { + ScanPaths.Add(FString::Printf(TEXT("/%s/"), *CookByTheBookOptions->DlcName)); + } + + if (ScanPaths.Num() > 0 && !AssetRegistry->IsLoadingAssets()) { AssetRegistry->ScanPathsSynchronous(ScanPaths); } @@ -5017,7 +5027,6 @@ void UCookOnTheFlyServer::GenerateLongPackageNames(TArray& FilesInPath) else { LogCookerMessage(FString::Printf(TEXT("Unable to generate long package name for %s because %s"), *FileInPath, *FailureReason), EMessageSeverity::Warning); - UE_LOG(LogCook, Warning, TEXT("Unable to generate long package name for %s because %s"), *FileInPath, *FailureReason); } } } @@ -5200,7 +5209,6 @@ void UCookOnTheFlyServer::CollectFilesToCook(TArray& FilesInPath, const T if (FPackageName::SearchForPackageOnDisk(CurrEntry, NULL, &OutFilename) == false) { LogCookerMessage( FString::Printf(TEXT("Unable to find package for map %s."), *CurrEntry), EMessageSeverity::Warning); - UE_LOG(LogCook, Warning, TEXT("Unable to find package for map %s."), *CurrEntry); } else { @@ -5212,38 +5220,18 @@ void UCookOnTheFlyServer::CollectFilesToCook(TArray& FilesInPath, const T AddFileToCook( FilesInPath,CurrEntry); } } - - const FString ExternalMountPointName(TEXT("/Game/")); if (IsCookingDLC()) { - // get the dlc and make sure we cook that directory - FString DLCPath = FPaths::Combine(*GetBaseDirectoryForDLC(), TEXT("Content")); + TArray PackagesToNeverCook; + UAssetManager::Get().ModifyDLCCook(CookByTheBookOptions->DlcName, FilesInPath, PackagesToNeverCook); - FString MountPoint = ExternalMountPointName; - TSharedPtr Plugin = IPluginManager::Get().FindPlugin(CookByTheBookOptions->DlcName); - if (Plugin.IsValid()) + for (FName NeverCookPackage : PackagesToNeverCook) { - MountPoint = Plugin->GetMountedAssetPath(); - } + const FName* StandardPackageFilename = GetPackageNameCache().GetCachedPackageNameFromStandardFileName(NeverCookPackage); - if (!CookByTheBookOptions->bDLCNoCookAllAssets) - { - TArray Files; - IFileManager::Get().FindFilesRecursive(Files, *DLCPath, *(FString(TEXT("*")) + FPackageName::GetAssetPackageExtension()), true, false, false); - IFileManager::Get().FindFilesRecursive(Files, *DLCPath, *(FString(TEXT("*")) + FPackageName::GetMapPackageExtension()), true, false, false); - - for (int32 Index = 0; Index < Files.Num(); Index++) + if (StandardPackageFilename && *StandardPackageFilename != NAME_None) { - FString StdFile = Files[Index]; - FPaths::MakeStandardFilename(StdFile); - AddFileToCook(FilesInPath, StdFile); - - // this asset may not be in our currently mounted content directories, so try to mount a new one now - FString LongPackageName; - if (!FPackageName::IsValidLongPackageName(StdFile) && !FPackageName::TryConvertFilenameToLongPackageName(StdFile, LongPackageName)) - { - FPackageName::RegisterMountPoint(MountPoint, DLCPath); - } + PackageTracker->NeverCookPackageList.Add(*StandardPackageFilename); } } } @@ -5251,6 +5239,7 @@ void UCookOnTheFlyServer::CollectFilesToCook(TArray& FilesInPath, const T if (!(FilesToCookFlags & ECookByTheBookOptions::SkipSoftReferences)) { + const FString ExternalMountPointName(TEXT("/Game/")); for (const FString& CurrEntry : CookDirectories) { TArray Files; @@ -6485,9 +6474,12 @@ void UCookOnTheFlyServer::StartCookByTheBook( const FCookByTheBookStartupOptions CookByTheBookOptions->bFullLoadAndSave = !!(CookOptions & ECookByTheBookOptions::FullLoadAndSave); CookByTheBookOptions->bPackageStore = !!(CookOptions & ECookByTheBookOptions::PackageStore); CookByTheBookOptions->bCookAgainstFixedBase = !!(CookOptions & ECookByTheBookOptions::CookAgainstFixedBase); - CookByTheBookOptions->bDLCNoCookAllAssets = !!(CookOptions & ECookByTheBookOptions::DLCNoCookAllAssets); + CookByTheBookOptions->bDlcLoadMainAssetRegistry = !!(CookOptions & ECookByTheBookOptions::DlcLoadMainAssetRegistry); CookByTheBookOptions->bErrorOnEngineContentUse = CookByTheBookStartupOptions.bErrorOnEngineContentUse; + // if we are going to change the state of dlc, we need to clean out our package filename cache (the generated filename cache is dependent on this key). This has to happen later on, but we want to set the DLC State earlier. + const bool bDlcStateChanged = CookByTheBookOptions->DlcName != DLCName; + CookByTheBookOptions->DlcName = DLCName; if (CookByTheBookOptions->bSkipHardReferences && !CookByTheBookOptions->bSkipSoftReferences) { UE_LOG(LogCook, Warning, TEXT("Setting bSkipSoftReferences to true since bSkipHardReferences is true and skipping hard references requires skipping soft references.")); @@ -6657,11 +6649,9 @@ void UCookOnTheFlyServer::StartCookByTheBook( const FCookByTheBookStartupOptions DiscoverPlatformSpecificNeverCookPackages(TargetPlatforms, UBTPlatformStrings); } - if ( CookByTheBookOptions->DlcName != DLCName ) + if (bDlcStateChanged) { - // we are going to change the state of dlc we need to clean out our package filename cache (the generated filename cache is dependent on this key) - CookByTheBookOptions->DlcName = DLCName; - + // If we changed the DLC State earlier on, we must clear out the package name cache TermSandbox(); } @@ -6721,44 +6711,63 @@ void UCookOnTheFlyServer::StartCookByTheBook( const FCookByTheBookStartupOptions // if we are cooking dlc we must be based on a release version cook check( !BasedOnReleaseVersion.IsEmpty() ); - for ( const ITargetPlatform* TargetPlatform : TargetPlatforms ) + auto ReadDevelopmentAssetRegistry = [this, &BasedOnReleaseVersion, bVerifyPackagesExist](TArray& OutPackageList, const FString& InPlatformName) { - FString PlatformNameString = TargetPlatform->PlatformName(); - FName PlatformName(*PlatformNameString); - FString OriginalSandboxRegistryFilename = GetBasedOnReleaseVersionAssetRegistryPath(BasedOnReleaseVersion, PlatformNameString ) / TEXT("Metadata") / GetDevelopmentAssetRegistryFilename(); + FString OriginalSandboxRegistryFilename = GetBasedOnReleaseVersionAssetRegistryPath(BasedOnReleaseVersion, InPlatformName ) / TEXT("Metadata") / GetDevelopmentAssetRegistryFilename(); - TArray PackageList; // if this check fails probably because the asset registry can't be found or read - bool bSucceeded = GetAllPackageFilenamesFromAssetRegistry(OriginalSandboxRegistryFilename, bVerifyPackagesExist, PackageList); + bool bSucceeded = GetAllPackageFilenamesFromAssetRegistry(OriginalSandboxRegistryFilename, bVerifyPackagesExist, OutPackageList); if (!bSucceeded) { - OriginalSandboxRegistryFilename = GetBasedOnReleaseVersionAssetRegistryPath(BasedOnReleaseVersion, PlatformNameString) / GetAssetRegistryFilename(); - bSucceeded = GetAllPackageFilenamesFromAssetRegistry(OriginalSandboxRegistryFilename, bVerifyPackagesExist, PackageList); + OriginalSandboxRegistryFilename = GetBasedOnReleaseVersionAssetRegistryPath(BasedOnReleaseVersion, InPlatformName) / GetAssetRegistryFilename(); + bSucceeded = GetAllPackageFilenamesFromAssetRegistry(OriginalSandboxRegistryFilename, bVerifyPackagesExist, OutPackageList); } if (!bSucceeded) { - // Check all possible flavors - // For example release version could be cooked as Android_ASTC flavor, but DLC can be made as Android_ETC2 - for (const PlatformInfo::FTargetPlatformInfo* PlatformFlavorInfo : TargetPlatform->GetTargetPlatformInfo().Flavors) + const PlatformInfo::FTargetPlatformInfo* PlatformInfo = PlatformInfo::FindPlatformInfo(*InPlatformName); + for (const PlatformInfo::FTargetPlatformInfo* PlatformFlavor : PlatformInfo->Flavors) { - OriginalSandboxRegistryFilename = GetBasedOnReleaseVersionAssetRegistryPath(BasedOnReleaseVersion, PlatformFlavorInfo->Name.ToString()) / GetAssetRegistryFilename(); - bSucceeded = GetAllPackageFilenamesFromAssetRegistry(OriginalSandboxRegistryFilename, bVerifyPackagesExist, PackageList); + OriginalSandboxRegistryFilename = GetBasedOnReleaseVersionAssetRegistryPath(BasedOnReleaseVersion, PlatformFlavor->Name.ToString()) / GetAssetRegistryFilename(); + bSucceeded = GetAllPackageFilenamesFromAssetRegistry(OriginalSandboxRegistryFilename, bVerifyPackagesExist, OutPackageList); if (bSucceeded) { break; } } } - check( bSucceeded ); - if ( bSucceeded ) + check(bSucceeded); + }; + + TArray OverridePackageList; + FString DevelopmentAssetRegistryPlatformOverride; + if (FParse::Value(FCommandLine::Get(), TEXT("DevelopmentAssetRegistryPlatformOverride="), DevelopmentAssetRegistryPlatformOverride)) + { + // Read the contents of the asset registry for the overriden platform. We'll use this for all requested platforms so we can just keep one copy of it here + ReadDevelopmentAssetRegistry(OverridePackageList, *DevelopmentAssetRegistryPlatformOverride); + checkf(OverridePackageList.Num() != 0, TEXT("DevelopmentAssetRegistry platform override is empty! An override is expected to exist and contain some valid data")); + } + + for ( const ITargetPlatform* TargetPlatform: TargetPlatforms ) + { + TArray PackageList; + FString PlatformNameString = TargetPlatform->PlatformName(); + FName PlatformName(*PlatformNameString); + + if (OverridePackageList.Num() == 0) + { + ReadDevelopmentAssetRegistry(PackageList, PlatformNameString); + } + + TArray& ActivePackageList = OverridePackageList.Num() > 0 ? OverridePackageList : PackageList; + if (ActivePackageList.Num() > 0) { TArray ResultPlatforms; ResultPlatforms.Add(TargetPlatform); TArray Succeeded; Succeeded.Add(true); - for (const FName& PackageFilename : PackageList) + for (const FName& PackageFilename : ActivePackageList) { UE::Cook::FPackageData* PackageData = PackageDatas->TryAddPackageDataByFileName(PackageFilename); if (PackageData) @@ -6767,7 +6776,16 @@ void UCookOnTheFlyServer::StartCookByTheBook( const FCookByTheBookStartupOptions } } } - CookByTheBookOptions->BasedOnReleaseCookedPackages.Add(PlatformName, MoveTemp(PackageList)); + + if (OverridePackageList.Num() > 0) + { + // This is the override list, so we can't give the memory away because we will need it for the other platforms + CookByTheBookOptions->BasedOnReleaseCookedPackages.Add(PlatformName, OverridePackageList); + } + else + { + CookByTheBookOptions->BasedOnReleaseCookedPackages.Add(PlatformName, MoveTemp(PackageList)); + } } PackageNameCache.SetAssetRegistry(CacheAssetRegistry); @@ -6827,7 +6845,6 @@ void UCookOnTheFlyServer::StartCookByTheBook( const FCookByTheBookStartupOptions if (FilesInPath.Num() == 0) { LogCookerMessage(FString::Printf(TEXT("No files found to cook.")), EMessageSeverity::Warning); - UE_LOG(LogCook, Warning, TEXT("No files found.")); } if (FParse::Param(FCommandLine::Get(), TEXT("RANDOMPACKAGEORDER")) || @@ -6863,7 +6880,6 @@ void UCookOnTheFlyServer::StartCookByTheBook( const FCookByTheBookStartupOptions { const FString FileName = FileFName.ToString(); LogCookerMessage( FString::Printf(TEXT("Unable to find package for cooking %s"), *FileName), EMessageSeverity::Warning ); - UE_LOG(LogCook, Warning, TEXT("Unable to find package for cooking %s"), *FileName) } } @@ -7247,11 +7263,11 @@ void UCookOnTheFlyServer::HandleNetworkFileServerRecompileShaders(const FShaderR bool UCookOnTheFlyServer::GetAllPackageFilenamesFromAssetRegistry( const FString& AssetRegistryPath, bool bVerifyPackagesExist, TArray& OutPackageFilenames ) const { UE_SCOPED_COOKTIMER(GetAllPackageFilenamesFromAssetRegistry); - FArrayReader SerializedAssetData; - if (FFileHelper::LoadFileToArray(SerializedAssetData, *AssetRegistryPath)) + TUniquePtr Reader(IFileManager::Get().CreateFileReader(*AssetRegistryPath)); + if (Reader) { FAssetRegistryState TempState; - TempState.Serialize(SerializedAssetData, FAssetRegistrySerializationOptions()); + TempState.Serialize(*Reader.Get(), FAssetRegistrySerializationOptions()); const TMap& RegistryDataMap = TempState.GetObjectPathToAssetDataMap(); diff --git a/Engine/Source/Editor/UnrealEd/Private/Editor.cpp b/Engine/Source/Editor/UnrealEd/Private/Editor.cpp index 9ee97c8d040a..e95042a0db14 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Editor.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Editor.cpp @@ -251,7 +251,7 @@ void FReimportManager::UpdateReimportPath(UObject* Obj, const FString& Filename, } -bool FReimportManager::Reimport( UObject* Obj, bool bAskForNewFileIfMissing, bool bShowNotification, FString PreferredReimportFile, FReimportHandler* SpecifiedReimportHandler, int32 SourceFileIndex, bool bForceNewFile /*= false*/) +bool FReimportManager::Reimport( UObject* Obj, bool bAskForNewFileIfMissing, bool bShowNotification, FString PreferredReimportFile, FReimportHandler* SpecifiedReimportHandler, int32 SourceFileIndex, bool bForceNewFile /*= false*/, bool bAutomated /*= false*/) { // Warn that were about to reimport, so prep for it PreReimport.Broadcast( Obj ); @@ -418,7 +418,10 @@ bool FReimportManager::Reimport( UObject* Obj, bool bAskForNewFileIfMissing, boo // Do the reimport + const bool bOriginalAutomated = CanReimportHandler->IsAutomatedReimport(); + CanReimportHandler->SetAutomatedReimport(bAutomated); EReimportResult::Type Result = CanReimportHandler->Reimport( Obj, SourceFileIndex ); + CanReimportHandler->SetAutomatedReimport(bOriginalAutomated); if( Result == EReimportResult::Succeeded ) { Obj->PostEditChange(); @@ -501,7 +504,7 @@ bool FReimportManager::Reimport( UObject* Obj, bool bAskForNewFileIfMissing, boo return bSuccess; } -void FReimportManager::ValidateAllSourceFileAndReimport(TArray &ToImportObjects, bool bShowNotification, int32 SourceFileIndex, bool bForceNewFile /*= false*/) +void FReimportManager::ValidateAllSourceFileAndReimport(TArray &ToImportObjects, bool bShowNotification, int32 SourceFileIndex, bool bForceNewFile /*= false*/, bool bAutomated /*= false*/) { //Copy the array to prevent iteration assert if a reimport factory change the selection TArray CopyOfSelectedAssets; @@ -623,7 +626,7 @@ void FReimportManager::ValidateAllSourceFileAndReimport(TArray &ToImpo //If user ignore those asset just not add them to CopyOfSelectedAssets } - FReimportManager::Instance()->ReimportMultiple(CopyOfSelectedAssets, /*bAskForNewFileIfMissing=*/false, bShowNotification, TEXT(""), nullptr, SourceFileIndex); + FReimportManager::Instance()->ReimportMultiple(CopyOfSelectedAssets, /*bAskForNewFileIfMissing=*/false, bShowNotification, TEXT(""), nullptr, SourceFileIndex, bForceNewFile, bAutomated); } void FReimportManager::AddReferencedObjects( FReferenceCollector& Collector ) @@ -648,7 +651,7 @@ void FReimportManager::SortHandlersIfNeeded() } } -bool FReimportManager::ReimportMultiple(TArrayView Objects, bool bAskForNewFileIfMissing /*= false*/, bool bShowNotification /*= true*/, FString PreferredReimportFile /*= TEXT("")*/, FReimportHandler* SpecifiedReimportHandler /*= nullptr */, int32 SourceFileIndex /*= INDEX_NONE*/) +bool FReimportManager::ReimportMultiple(TArrayView Objects, bool bAskForNewFileIfMissing /*= false*/, bool bShowNotification /*= true*/, FString PreferredReimportFile /*= TEXT("")*/, FReimportHandler* SpecifiedReimportHandler /*= nullptr */, int32 SourceFileIndex /*= INDEX_NONE*/, bool bForceNewFile /*= false*/, bool bAutomated /*= false*/) { bool bBulkSuccess = true; @@ -662,7 +665,7 @@ bool FReimportManager::ReimportMultiple(TArrayView Objects, bool bAskF FScopedSlowTask SingleObjectTask(1.0f, SingleTaskTest); SingleObjectTask.EnterProgressFrame(1.0f); - bBulkSuccess = bBulkSuccess && Reimport(CurrentObject, bAskForNewFileIfMissing, bShowNotification, PreferredReimportFile, SpecifiedReimportHandler, SourceFileIndex); + bBulkSuccess = bBulkSuccess && Reimport(CurrentObject, bAskForNewFileIfMissing, bShowNotification, PreferredReimportFile, SpecifiedReimportHandler, SourceFileIndex, bForceNewFile, bAutomated); } BulkReimportTask.EnterProgressFrame(1.0f); @@ -1400,186 +1403,238 @@ namespace EditorUtilities } // Copy component properties from source to target if they match. Note that the component lists may not be 1-1 due to context-specific components (e.g. editor-only sprites, etc.). - TInlineComponentArray SourceComponents; - TInlineComponentArray TargetComponents; - SourceActor->GetComponents(SourceComponents); - TargetActor->GetComponents(TargetComponents); + TArray> SourceTargetComponentPairs; - - int32 TargetComponentIndex = 0; - for( UActorComponent* SourceComponent : SourceComponents ) + auto BuildComponentPairs = [&SourceTargetComponentPairs, SourceActor](AActor* PrimaryActor, AActor* SecondaryActor) { - if (SourceComponent->CreationMethod == EComponentCreationMethod::UserConstructionScript) + TInlineComponentArray SecondaryComponents(SecondaryActor); + + const bool bPrimaryIsSource = (PrimaryActor == SourceActor); + int32 SecondaryComponentIndex = 0; + for (UActorComponent* PrimaryComponent : PrimaryActor->GetComponents()) { - continue; + if (PrimaryComponent->CreationMethod == EComponentCreationMethod::UserConstructionScript) + { + continue; + } + if (UActorComponent* SecondaryComponent = FindMatchingComponentInstance(PrimaryComponent, SecondaryActor, SecondaryComponents, SecondaryComponentIndex)) + { + if (bPrimaryIsSource) + { + SourceTargetComponentPairs.Emplace(PrimaryComponent, SecondaryComponent); + } + else + { + SourceTargetComponentPairs.Emplace(SecondaryComponent, PrimaryComponent); + } + } } - UActorComponent* TargetComponent = FindMatchingComponentInstance( SourceComponent, TargetActor, TargetComponents, TargetComponentIndex ); + }; - if( TargetComponent != nullptr ) + const bool bSourceActorIsCDO = SourceActor->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject); + const bool bTargetActorIsCDO = TargetActor->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject); + const bool bSourceActorIsBPCDO = bSourceActorIsCDO && ActorClass->HasAllClassFlags(CLASS_CompiledFromBlueprint); + + // If the source actor is a CDO, then the target actor should drive the collection of components since FindMatchingComponentInstance + // does work to seek out SCS and ICH components for blueprints + if (bSourceActorIsCDO) + { + BuildComponentPairs(TargetActor, SourceActor); + } + else + { + BuildComponentPairs(SourceActor, TargetActor); + } + + for (const TPair& ComponentPair : SourceTargetComponentPairs) + { + UActorComponent* SourceComponent = ComponentPair.Key; + UActorComponent* TargetComponent = ComponentPair.Value; + + UClass* ComponentClass = SourceComponent->GetClass(); + check( ComponentClass == TargetComponent->GetClass() ); + + // Build a list of matching component archetype instances for propagation (if requested) + TArray ComponentArchetypeInstances; + if( Options.Flags & ECopyOptions::PropagateChangesToArchetypeInstances ) { - UClass* ComponentClass = SourceComponent->GetClass(); - check( ComponentClass == TargetComponent->GetClass() ); - - // Build a list of matching component archetype instances for propagation (if requested) - TArray ComponentArchetypeInstances; - if( Options.Flags & ECopyOptions::PropagateChangesToArchetypeInstances ) + for( AActor* ArchetypeInstance : ArchetypeInstances ) { - for( AActor* ArchetypeInstance : ArchetypeInstances ) + if( ArchetypeInstance != nullptr ) { - if( ArchetypeInstance != nullptr ) + UActorComponent* ComponentArchetypeInstance = FindMatchingComponentInstance( TargetComponent, ArchetypeInstance ); + if( ComponentArchetypeInstance != nullptr ) { - UActorComponent* ComponentArchetypeInstance = FindMatchingComponentInstance( TargetComponent, ArchetypeInstance ); - if( ComponentArchetypeInstance != nullptr ) - { - ComponentArchetypeInstances.AddUnique( ComponentArchetypeInstance ); - } + ComponentArchetypeInstances.AddUnique( ComponentArchetypeInstance ); } } } + } - TSet SourceUCSModifiedProperties; - SourceComponent->GetUCSModifiedProperties(SourceUCSModifiedProperties); + TSet SourceUCSModifiedProperties; + SourceComponent->GetUCSModifiedProperties(SourceUCSModifiedProperties); - TArray ComponentInstancesToReregister; + TArray ComponentInstancesToReregister; - // Copy component properties - for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) + // Copy component properties + for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) + { + const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); + const bool bIsIdentical = Property->Identical_InContainer( SourceComponent, TargetComponent ); + const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); + const bool bIsTransform = + Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); + + auto SourceComponentIsRoot = [&]() { - const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); - const bool bIsIdentical = Property->Identical_InContainer( SourceComponent, TargetComponent ); - const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); - const bool bIsTransform = - Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); - - if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - && ( !bIsTransform || SourceComponent != SourceActor->GetRootComponent() || ( !SourceActor->HasAnyFlags( RF_ClassDefaultObject | RF_ArchetypeObject ) && !TargetActor->HasAnyFlags( RF_ClassDefaultObject | RF_ArchetypeObject ) ) ) ) + USceneComponent* RootComponent = SourceActor->GetRootComponent(); + if (SourceComponent == RootComponent) { - const bool bIsSafeToCopy = (!(Options.Flags & ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) - && (!(Options.Flags & ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); - if( bIsSafeToCopy ) + return true; + } + else if (RootComponent == nullptr && bSourceActorIsBPCDO) + { + // If we're dealing with a BP CDO as source, then look at the target for whether this is the root component + return (TargetComponent == TargetActor->GetRootComponent()); + } + return false; + }; + + if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + && ( !bIsTransform || (!bSourceActorIsCDO && !bTargetActorIsCDO) || !SourceComponentIsRoot() ) ) + { + const bool bIsSafeToCopy = (!(Options.Flags & ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) + && (!(Options.Flags & ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); + if( bIsSafeToCopy ) + { + if (!Options.CanCopyProperty(*Property, *SourceActor)) { - if (!Options.CanCopyProperty(*Property, *SourceActor)) - { - continue; - } + continue; + } - if( !bIsPreviewing ) + if( !bIsPreviewing ) + { + if( !ModifiedObjects.Contains(TargetComponent) ) { - if( !ModifiedObjects.Contains(TargetComponent) ) - { - TargetComponent->SetFlags(RF_Transactional); - TargetComponent->Modify(); - ModifiedObjects.Add(TargetComponent); - } + TargetComponent->SetFlags(RF_Transactional); + TargetComponent->Modify(); + ModifiedObjects.Add(TargetComponent); + } - if( Options.Flags & ECopyOptions::CallPostEditChangeProperty ) - { - // @todo simulate: Should we be calling this on the component instead? - TargetActor->PreEditChange( Property ); - } + if( Options.Flags & ECopyOptions::CallPostEditChangeProperty ) + { + // @todo simulate: Should we be calling this on the component instead? + TargetActor->PreEditChange( Property ); + } - // Determine which component archetype instances match the current property value of the target component (before it gets changed). We only want to propagate the change to those instances. - TArray ComponentArchetypeInstancesToChange; - if( Options.Flags & ECopyOptions::PropagateChangesToArchetypeInstances ) + // Determine which component archetype instances match the current property value of the target component (before it gets changed). We only want to propagate the change to those instances. + TArray ComponentArchetypeInstancesToChange; + if( Options.Flags & ECopyOptions::PropagateChangesToArchetypeInstances ) + { + for (UActorComponent* ComponentArchetypeInstance : ComponentArchetypeInstances) { - for (UActorComponent* ComponentArchetypeInstance : ComponentArchetypeInstances) + if( ComponentArchetypeInstance != nullptr && Property->Identical_InContainer( ComponentArchetypeInstance, TargetComponent ) ) { - if( ComponentArchetypeInstance != nullptr && Property->Identical_InContainer( ComponentArchetypeInstance, TargetComponent ) ) + bool bAdd = true; + // We also need to double check that either the direct archetype of the target is also identical + if (ComponentArchetypeInstance->GetArchetype() != TargetComponent) { - bool bAdd = true; - // We also need to double check that either the direct archetype of the target is also identical - if (ComponentArchetypeInstance->GetArchetype() != TargetComponent) + UActorComponent* CheckComponent = CastChecked(ComponentArchetypeInstance->GetArchetype()); + while (CheckComponent != ComponentArchetypeInstance) { - UActorComponent* CheckComponent = CastChecked(ComponentArchetypeInstance->GetArchetype()); - while (CheckComponent != ComponentArchetypeInstance) + if (!Property->Identical_InContainer( CheckComponent, TargetComponent )) { - if (!Property->Identical_InContainer( CheckComponent, TargetComponent )) - { - bAdd = false; - break; - } - CheckComponent = CastChecked(CheckComponent->GetArchetype()); + bAdd = false; + break; } + CheckComponent = CastChecked(CheckComponent->GetArchetype()); } + } - if (bAdd) - { - ComponentArchetypeInstancesToChange.Add( ComponentArchetypeInstance ); - } - } - } - } - - CopySingleProperty(SourceComponent, TargetComponent, Property); - - if( Options.Flags & ECopyOptions::CallPostEditChangeProperty ) - { - FPropertyChangedEvent PropertyChangedEvent( Property ); - TargetActor->PostEditChangeProperty( PropertyChangedEvent ); - } - - if( Options.Flags & ECopyOptions::PropagateChangesToArchetypeInstances ) - { - for( int32 InstanceIndex = 0; InstanceIndex < ComponentArchetypeInstancesToChange.Num(); ++InstanceIndex ) - { - UActorComponent* ComponentArchetypeInstance = ComponentArchetypeInstancesToChange[InstanceIndex]; - if( ComponentArchetypeInstance != nullptr ) + if (bAdd) { - if( !ModifiedObjects.Contains(ComponentArchetypeInstance) ) - { - // Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer. - // Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them. - if (!ComponentArchetypeInstance->IsCreatedByConstructionScript()) - { - ComponentArchetypeInstance->SetFlags(RF_Transactional); - ComponentArchetypeInstance->Modify(); - ModifiedObjects.Add(ComponentArchetypeInstance); - } - - // We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation. - AActor* Owner = ComponentArchetypeInstance->GetOwner(); - if( Owner != nullptr && !ModifiedObjects.Contains(Owner)) - { - Owner->Modify(); - ModifiedObjects.Add(Owner); - } - } - - if (ComponentArchetypeInstance->IsRegistered()) - { - ComponentArchetypeInstance->UnregisterComponent(); - ComponentInstancesToReregister.Add(ComponentArchetypeInstance); - } - - CopySingleProperty( TargetComponent, ComponentArchetypeInstance, Property ); + ComponentArchetypeInstancesToChange.Add( ComponentArchetypeInstance ); } } } } - ++CopiedPropertyCount; + CopySingleProperty(SourceComponent, TargetComponent, Property); - if( bIsTransform ) + if( Options.Flags & ECopyOptions::CallPostEditChangeProperty ) { - bTransformChanged = true; + FPropertyChangedEvent PropertyChangedEvent( Property ); + TargetActor->PostEditChangeProperty( PropertyChangedEvent ); } + + if( Options.Flags & ECopyOptions::PropagateChangesToArchetypeInstances ) + { + for( int32 InstanceIndex = 0; InstanceIndex < ComponentArchetypeInstancesToChange.Num(); ++InstanceIndex ) + { + UActorComponent* ComponentArchetypeInstance = ComponentArchetypeInstancesToChange[InstanceIndex]; + if( ComponentArchetypeInstance != nullptr ) + { + if( !ModifiedObjects.Contains(ComponentArchetypeInstance) ) + { + // Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer. + // Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them. + if (!ComponentArchetypeInstance->IsCreatedByConstructionScript()) + { + ComponentArchetypeInstance->SetFlags(RF_Transactional); + ComponentArchetypeInstance->Modify(); + ModifiedObjects.Add(ComponentArchetypeInstance); + } + + // We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation. + AActor* Owner = ComponentArchetypeInstance->GetOwner(); + if( Owner != nullptr && !ModifiedObjects.Contains(Owner)) + { + Owner->Modify(); + ModifiedObjects.Add(Owner); + } + } + + if (ComponentArchetypeInstance->IsRegistered()) + { + ComponentArchetypeInstance->UnregisterComponent(); + ComponentInstancesToReregister.Add(ComponentArchetypeInstance); + } + + CopySingleProperty( TargetComponent, ComponentArchetypeInstance, Property ); + } + } + } + } + + ++CopiedPropertyCount; + + if( bIsTransform ) + { + bTransformChanged = true; } } } + } - for (UActorComponent* ModifiedComponentInstance : ComponentInstancesToReregister) - { - ModifiedComponentInstance->RegisterComponent(); - } + for (UActorComponent* ModifiedComponentInstance : ComponentInstancesToReregister) + { + ModifiedComponentInstance->RegisterComponent(); } } - if (!bIsPreviewing && CopiedPropertyCount > 0 && TargetActor->HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject) && TargetActor->GetClass()->HasAllClassFlags(CLASS_CompiledFromBlueprint)) + if (!bIsPreviewing && CopiedPropertyCount > 0 && TargetActor->GetClass()->HasAllClassFlags(CLASS_CompiledFromBlueprint)) { - FBlueprintEditorUtils::PostEditChangeBlueprintActors(CastChecked(TargetActor->GetClass()->ClassGeneratedBy)); + if (bTargetActorIsCDO) + { + FBlueprintEditorUtils::PostEditChangeBlueprintActors(CastChecked(TargetActor->GetClass()->ClassGeneratedBy)); + } + else + { + TargetActor->RerunConstructionScripts(); + } } // If one of the changed properties was part of the actor's transformation, then we'll call PostEditMove too. diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorActor.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorActor.cpp index d0e9265e2457..263527e52d2f 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorActor.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorActor.cpp @@ -336,9 +336,12 @@ void UUnrealEdEngine::edactPasteSelected(UWorld* InWorld, bool bDuplicate, bool // Offset the actor's location. Actor->TeleportTo(Actor->GetActorLocation() + ActorLocationOffset, Actor->GetActorRotation(), false, true); - // Re-label duplicated actors so that labels become unique - FActorLabelUtilities::SetActorLabelUnique(Actor, Actor->GetActorLabel(), &ActorLabels); - ActorLabels.Add(Actor->GetActorLabel()); + if (!GetDefault()->bAvoidRelabelOnPasteSelected) + { + // Re-label duplicated actors so that labels become unique + FActorLabelUtilities::SetActorLabelUnique(Actor, Actor->GetActorLabel(), &ActorLabels); + ActorLabels.Add(Actor->GetActorLabel()); + } LayersSubsystem->InitializeNewActorLayers(Actor); diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorBuildUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorBuildUtils.cpp index 6f24e706d68f..e84c64d9bd10 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorBuildUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorBuildUtils.cpp @@ -1488,7 +1488,7 @@ bool FEditorBuildUtils::CompileShadersComplexityViewMode(EMaterialQualityLevel:: check(MaterialInterface); TSharedPtr SpecialResource = MakeShareable(new FMaterialOfflineCompilation()); - SpecialResource->SetMaterial(MaterialInterface->GetMaterial(), QualityLevel, true, FeatureLevel, Cast(MaterialInterface)); + SpecialResource->SetMaterial(MaterialInterface->GetMaterial(), Cast(MaterialInterface), FeatureLevel, QualityLevel); SpecialResource->CacheShaders(ShaderPlatform); diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp index d18ca0197053..8a52b81fb62f 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp @@ -84,6 +84,7 @@ #include "BSPOps.h" #include "EditorCommandLineUtils.h" #include "Engine/NetDriver.h" +#include "Engine/NetConnection.h" #include "Net/NetworkProfiler.h" #include "Interfaces/IPluginManager.h" #include "UObject/PackageReload.h" @@ -216,6 +217,7 @@ #include "ComponentRecreateRenderStateContext.h" #include "RenderTargetPool.h" #include "RenderGraphBuilder.h" +#include "CustomResourcePool.h" #include "ToolMenus.h" #include "IToolMenusEditorModule.h" #include "Subsystems/AssetEditorSubsystem.h" @@ -1296,6 +1298,12 @@ void UEditorEngine::AddReferencedObjects(UObject* InThis, FReferenceCollector& C Collector.AddReferencedObject( This->ActorFactories[ Index ], This ); } + // If we're in a PIE session, ensure we keep the current settings object alive. + if (This->PlayInEditorSessionInfo.IsSet() && This->PlayInEditorSessionInfo->OriginalRequestParams.EditorPlaySettings) + { + Collector.AddReferencedObject(This->PlayInEditorSessionInfo->OriginalRequestParams.EditorPlaySettings, This); + } + Super::AddReferencedObjects( This, Collector ); } @@ -1957,6 +1965,7 @@ void UEditorEngine::Tick( float DeltaSeconds, bool bIdleMode ) } GRenderTargetPool.TickPoolElements(); FRDGBuilder::TickPoolElements(); + ICustomResourcePool::TickPoolElements(); }); } @@ -7105,6 +7114,18 @@ FORCEINLINE bool NetworkRemapPath_local(FWorldContext& Context, FString& Str, bo return false; } +bool UEditorEngine::NetworkRemapPath(UNetConnection* Connection, FString& Str, bool bReading) +{ + if (Connection == nullptr) + { + return false; + } + + // Pretty sure there's no case where you can't have a world by this point. + FWorldContext& Context = GetWorldContextFromWorldChecked(Connection->GetWorld()); + return NetworkRemapPath_local(Context, Str, bReading, Connection->IsReplay()); +} + bool UEditorEngine::NetworkRemapPath(UNetDriver* Driver, FString& Str, bool bReading) { if (Driver == nullptr) @@ -7113,15 +7134,15 @@ bool UEditorEngine::NetworkRemapPath(UNetDriver* Driver, FString& Str, bool bRea } // Pretty sure there's no case where you can't have a world by this point. - bool bIsAReplay = (Driver->GetWorld()) ? (Driver->GetWorld()->DemoNetDriver != NULL) : false; + const bool bIsReplay = Driver->GetWorld() ? (Driver->GetWorld()->GetDemoNetDriver() != nullptr) : false; FWorldContext& Context = GetWorldContextFromWorldChecked(Driver->GetWorld()); - return NetworkRemapPath_local(Context, Str, bReading, bIsAReplay); + return NetworkRemapPath_local(Context, Str, bReading, bIsReplay); } bool UEditorEngine::NetworkRemapPath( UPendingNetGame *PendingNetGame, FString& Str, bool bReading) { FWorldContext& Context = GetWorldContextFromPendingNetGameChecked(PendingNetGame); - return NetworkRemapPath_local(Context, Str, bReading, PendingNetGame->DemoNetDriver != NULL); + return NetworkRemapPath_local(Context, Str, bReading, PendingNetGame->GetDemoNetDriver() != nullptr); } void UEditorEngine::VerifyLoadMapWorldCleanup() @@ -7247,11 +7268,11 @@ FWorldContext& UEditorEngine::GetEditorWorldContext(bool bEnsureIsGWorld) return CreateNewWorldContext(EWorldType::Editor); } -FWorldContext* UEditorEngine::GetPIEWorldContext() +FWorldContext* UEditorEngine::GetPIEWorldContext(int32 WorldPIEInstance) { - for(auto& WorldContext : WorldList) + for (FWorldContext& WorldContext : WorldList) { - if(WorldContext.WorldType == EWorldType::PIE) + if (WorldContext.WorldType == EWorldType::PIE && WorldContext.PIEInstance == WorldPIEInstance) { return &WorldContext; } @@ -7594,9 +7615,9 @@ void UEditorEngine::SetPreviewPlatform(const FPreviewPlatformInfo& NewPreviewPla #endif // If we have specified a MaterialQualityPlatform ensure its feature level matches the requested feature level. - check(NewPreviewPlatform.PreviewShaderPlatformName.IsNone() || GetMaxSupportedFeatureLevel(ShaderFormatToLegacyShaderPlatform(NewPreviewPlatform.PreviewShaderPlatformName)) == NewPreviewPlatform.PreviewFeatureLevel); + check(NewPreviewPlatform.PreviewShaderFormatName.IsNone() || GetMaxSupportedFeatureLevel(ShaderFormatToLegacyShaderPlatform(NewPreviewPlatform.PreviewShaderFormatName)) == NewPreviewPlatform.PreviewFeatureLevel); - const bool bChangedPreviewShaderPlatform = NewPreviewPlatform.PreviewShaderPlatformName != PreviewPlatform.PreviewShaderPlatformName; + const bool bChangedPreviewShaderPlatform = NewPreviewPlatform.PreviewShaderFormatName != PreviewPlatform.PreviewShaderFormatName; const bool bChangedFeatureLevel = NewPreviewPlatform.PreviewFeatureLevel != PreviewPlatform.PreviewFeatureLevel || NewPreviewPlatform.bPreviewFeatureLevelActive != PreviewPlatform.bPreviewFeatureLevelActive; const ERHIFeatureLevel::Type EffectiveFeatureLevel = NewPreviewPlatform.GetEffectivePreviewFeatureLevel(); @@ -7609,7 +7630,7 @@ void UEditorEngine::SetPreviewPlatform(const FPreviewPlatformInfo& NewPreviewPla if (bChangedPreviewShaderPlatform) { UMaterialShaderQualitySettings* MaterialShaderQualitySettings = UMaterialShaderQualitySettings::Get(); - MaterialShaderQualitySettings->SetPreviewPlatform(PreviewPlatform.PreviewShaderPlatformName); + MaterialShaderQualitySettings->SetPreviewPlatform(PreviewPlatform.PreviewShaderFormatName); } if (bChangedFeatureLevel) @@ -7691,7 +7712,7 @@ void UEditorEngine::ToggleFeatureLevelPreview() bool UEditorEngine::IsFeatureLevelPreviewEnabled() const { - return PreviewPlatform.PreviewFeatureLevel != GMaxRHIFeatureLevel || PreviewPlatform.PreviewShaderPlatformName != NAME_None; + return PreviewPlatform.PreviewFeatureLevel != GMaxRHIFeatureLevel || PreviewPlatform.PreviewShaderFormatName != NAME_None; } bool UEditorEngine::IsFeatureLevelPreviewActive() const @@ -7717,7 +7738,7 @@ void UEditorEngine::SaveEditorFeatureLevel() { auto* Settings = GetMutableDefault(); Settings->PreviewFeatureLevel = (int32)PreviewPlatform.PreviewFeatureLevel; - Settings->PreviewShaderFormatName = PreviewPlatform.PreviewShaderPlatformName; + Settings->PreviewShaderFormatName = PreviewPlatform.PreviewShaderFormatName; Settings->bPreviewFeatureLevelActive = PreviewPlatform.bPreviewFeatureLevelActive; Settings->PostEditChange(); } @@ -7734,13 +7755,4 @@ bool UEditorEngine::GetPreviewPlatformName(FName& PlatformName) const return false; } -void UEditorEngine::AddReferencedObjects(FReferenceCollector& Collector) -{ - if (PlayInEditorSessionInfo.IsSet()) - { - Collector.AddReferencedObject(PlayInEditorSessionInfo->OriginalRequestParams.EditorPlaySettings, this); - } -} - - #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorObject.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorObject.cpp index 813f0b6384a1..d8c981a40e15 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorObject.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorObject.cpp @@ -704,18 +704,13 @@ const TCHAR* ImportObjectProperties( FImportObjectParams& InParams ) InParams.SubobjectOuter->PreEditChange(NULL); } - FObjectInstancingGraph* CurrentInstanceGraph = InParams.InInstanceGraph; - if ( InParams.SubobjectRoot != NULL && InParams.SubobjectRoot != UObject::StaticClass()->GetDefaultObject() ) - { - if ( CurrentInstanceGraph == NULL ) - { - CurrentInstanceGraph = new FObjectInstancingGraph; - } - CurrentInstanceGraph->SetDestinationRoot(InParams.SubobjectRoot); - } + FObjectInstancingGraph TempGraph; + FObjectInstancingGraph& InstanceGraph = InParams.InInstanceGraph ? *InParams.InInstanceGraph : TempGraph; - FObjectInstancingGraph TempGraph; - FObjectInstancingGraph& InstanceGraph = CurrentInstanceGraph ? *CurrentInstanceGraph : TempGraph; + if ( InParams.SubobjectRoot && InParams.SubobjectRoot != UObject::StaticClass()->GetDefaultObject() ) + { + InstanceGraph.SetDestinationRoot(InParams.SubobjectRoot); + } // Parse the object properties. const TCHAR* NewSourceText = @@ -764,13 +759,6 @@ const TCHAR* ImportObjectProperties( FImportObjectParams& InParams ) } } - // if we created the instance graph, delete it now - if ( CurrentInstanceGraph != NULL && InParams.InInstanceGraph == NULL ) - { - delete CurrentInstanceGraph; - CurrentInstanceGraph = NULL; - } - return NewSourceText; } diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorViewportClient.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorViewportClient.cpp index 5b578a491196..deb864ee8f73 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorViewportClient.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorViewportClient.cpp @@ -1107,7 +1107,7 @@ FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, c FPlane(1, 0, 0, 0), FPlane(0, -1, 0, 0), FPlane(0, 0, -1, 0), - FPlane(0, 0, -ViewInitOptions.ViewOrigin.Z, 1)); + FPlane(0, 0, 0, 1)); } else if (EffectiveViewportType == LVT_OrthoXZ) { @@ -1115,7 +1115,7 @@ FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, c FPlane(1, 0, 0, 0), FPlane(0, 0, -1, 0), FPlane(0, 1, 0, 0), - FPlane(0, 0, -ViewInitOptions.ViewOrigin.Y, 1)); + FPlane(0, 0, 0, 1)); } else if (EffectiveViewportType == LVT_OrthoYZ) { @@ -1123,7 +1123,7 @@ FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, c FPlane(0, 0, 1, 0), FPlane(1, 0, 0, 0), FPlane(0, 1, 0, 0), - FPlane(0, 0, ViewInitOptions.ViewOrigin.X, 1)); + FPlane(0, 0, 0, 1)); } else if (EffectiveViewportType == LVT_OrthoNegativeXY) { @@ -1131,7 +1131,7 @@ FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, c FPlane(-1, 0, 0, 0), FPlane(0, -1, 0, 0), FPlane(0, 0, 1, 0), - FPlane(0, 0, -ViewInitOptions.ViewOrigin.Z, 1)); + FPlane(0, 0, 0, 1)); } else if (EffectiveViewportType == LVT_OrthoNegativeXZ) { @@ -1139,7 +1139,7 @@ FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, c FPlane(-1, 0, 0, 0), FPlane(0, 0, 1, 0), FPlane(0, 1, 0, 0), - FPlane(0, 0, -ViewInitOptions.ViewOrigin.Y, 1)); + FPlane(0, 0, 0, 1)); } else if (EffectiveViewportType == LVT_OrthoNegativeYZ) { @@ -1147,7 +1147,7 @@ FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, c FPlane(0, 0, -1, 0), FPlane(-1, 0, 0, 0), FPlane(0, 1, 0, 0), - FPlane(0, 0, ViewInitOptions.ViewOrigin.X, 1)); + FPlane(0, 0, 0, 1)); } else if (EffectiveViewportType == LVT_OrthoFreelook) { @@ -1155,7 +1155,7 @@ FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, c FPlane(0, 0, 1, 0), FPlane(1, 0, 0, 0), FPlane(0, 1, 0, 0), - FPlane(0, 0, ViewInitOptions.ViewOrigin.X, 1)); + FPlane(0, 0, 0, 1)); } else { @@ -3863,7 +3863,7 @@ void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas) } // Setup custom upscaler and screen percentage. - if (GCustomEditorStaticScreenPercentage) + if (GCustomEditorStaticScreenPercentage && ViewFamily.ViewMode == EViewModeIndex::VMI_Lit) { GCustomEditorStaticScreenPercentage->SetupEditorViewFamily(ViewFamily, PreviewResolutionFraction, bPreviewCustomTemporalUpscaler); } diff --git a/Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp b/Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp index 3ecc750e665b..0cc11d863f74 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp @@ -116,6 +116,7 @@ #include "Factories/TextureFactory.h" #include "Factories/ReimportTextureFactory.h" #include "Factories/TextureRenderTargetCubeFactoryNew.h" +#include "Factories/TextureRenderTargetVolumeFactoryNew.h" #include "Factories/TextureRenderTargetFactoryNew.h" #include "Factories/TouchInterfaceFactory.h" #include "Factories/FbxAssetImportData.h" @@ -163,12 +164,14 @@ #include "Sound/SoundCue.h" #include "Sound/SoundMix.h" #include "Engine/TextureCube.h" +#include "Engine/VolumeTexture.h" #include "Engine/Texture2DArray.h" #include "Engine/VolumeTexture.h" #include "Engine/TextureRenderTarget.h" #include "Engine/TextureRenderTarget2D.h" #include "Engine/CanvasRenderTarget2D.h" #include "Engine/TextureRenderTargetCube.h" +#include "Engine/TextureRenderTargetVolume.h" #include "GameFramework/TouchInterface.h" #include "Engine/UserDefinedEnum.h" #include "Engine/UserDefinedStruct.h" @@ -2262,6 +2265,34 @@ UObject* UTextureRenderTargetCubeFactoryNew::FactoryCreateNew(UClass* Class, UOb return (Result); } +/*----------------------------------------------------------------------------- + UTextureRenderTargetVolumeFactoryNew +-----------------------------------------------------------------------------*/ +UTextureRenderTargetVolumeFactoryNew::UTextureRenderTargetVolumeFactoryNew(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + + SupportedClass = UTextureRenderTargetVolume::StaticClass(); + bCreateNew = true; + bEditAfterNew = true; + bEditorImport = false; + + Width = 64; + Height = 64; + Depth = 64; + Format = 0; +} + +UObject* UTextureRenderTargetVolumeFactoryNew::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + // create the new object + UTextureRenderTargetVolume* Result = NewObject(InParent, Class, Name, Flags); + + // initialize the resource + Result->InitAutoFormat(Width, Height, Depth); + + return Result; +} /*----------------------------------------------------------------------------- UTextureFactory. @@ -5482,7 +5513,7 @@ EReimportResult::Type UReimportTextureFactory::Reimport( UObject* Obj ) UTexture2D* pTex2D = Cast(Obj); // Check if this texture has been modified by the paint tool. // If so, prompt the user to see if they'll continue with reimporting, returning if they decline. - if( pTex2D && pTex2D->bHasBeenPaintedInEditor && EAppReturnType::Yes != FMessageDialog::Open( EAppMsgType::YesNo, + if( pTex2D && pTex2D->bHasBeenPaintedInEditor && !IsAutomatedImport() && EAppReturnType::Yes != FMessageDialog::Open( EAppMsgType::YesNo, FText::Format(NSLOCTEXT("UnrealEd", "Import_TextureHasBeenPaintedInEditor", "The texture '{0}' has been painted on by the Mesh Paint tool.\nReimporting it will override any changes.\nWould you like to continue?"), FText::FromString(pTex2D->GetName()))) ) { @@ -5567,6 +5598,11 @@ int32 UReimportTextureFactory::GetPriority() const return ImportPriority; } +bool UReimportTextureFactory::IsAutomatedImport() const +{ + return Super::IsAutomatedImport() || IsAutomatedReimport(); +} + /*----------------------------------------------------------------------------- UReimportFbxStaticMeshFactory. @@ -5663,7 +5699,7 @@ EReimportResult::Type UReimportFbxStaticMeshFactory::Reimport( UObject* Obj ) ImportUI = NewObject(this, NAME_None, RF_Public); } //Prevent any UI for automation, unattended and commandlet - const bool IsUnattended = GIsAutomationTesting || FApp::IsUnattended() || IsRunningCommandlet() || GIsRunningUnattendedScript; + const bool IsUnattended = IsAutomatedImport() || FApp::IsUnattended() || IsRunningCommandlet() || GIsRunningUnattendedScript; const bool ShowImportDialogAtReimport = GetDefault()->bShowImportDialogAtReimport && !IsUnattended; if (ImportData == nullptr) @@ -5871,6 +5907,10 @@ int32 UReimportFbxStaticMeshFactory::GetPriority() const return ImportPriority; } +bool UReimportFbxStaticMeshFactory::IsAutomatedImport() const +{ + return Super::IsAutomatedImport() || IsAutomatedReimport(); +} /*----------------------------------------------------------------------------- @@ -5986,7 +6026,7 @@ EReimportResult::Type UReimportFbxSkeletalMeshFactory::Reimport( UObject* Obj, i bool bSuccess = false; //Prevent any UI for automation, unattended and commandlet - const bool IsUnattended = GIsAutomationTesting || FApp::IsUnattended() || IsRunningCommandlet() || GIsRunningUnattendedScript; + const bool IsUnattended = IsAutomatedImport() || FApp::IsUnattended() || IsRunningCommandlet() || GIsRunningUnattendedScript; const bool ShowImportDialogAtReimport = GetDefault()->bShowImportDialogAtReimport && !IsUnattended; if (ImportData == nullptr) @@ -6293,6 +6333,11 @@ int32 UReimportFbxSkeletalMeshFactory::GetPriority() const return ImportPriority; } +bool UReimportFbxSkeletalMeshFactory::IsAutomatedImport() const +{ + return Super::IsAutomatedImport() || IsAutomatedReimport(); +} + /*----------------------------------------------------------------------------- UReimportFbxAnimSequenceFactory -----------------------------------------------------------------------------*/ @@ -6421,7 +6466,7 @@ EReimportResult::Type UReimportFbxAnimSequenceFactory::Reimport( UObject* Obj ) if (!Skeleton) { // if it does not exist, ask for one - Skeleton = ChooseSkeleton(); + Skeleton = !IsAutomatedImport() ? ChooseSkeleton() : nullptr; if (!Skeleton) { // If skeleton wasn't found or the user canceled out of the dialog, we cannot proceed, but this reimport factory @@ -6435,7 +6480,7 @@ EReimportResult::Type UReimportFbxAnimSequenceFactory::Reimport( UObject* Obj ) AnimSequence->SetSkeleton(Skeleton); } bool bOutImportAll = false; - if ( UEditorEngine::ReimportFbxAnimation(Skeleton, AnimSequence, ImportData, *Filename, bOutImportAll, bShowOption) ) + if ( UEditorEngine::ReimportFbxAnimation(Skeleton, AnimSequence, ImportData, *Filename, bOutImportAll, bShowOption && !IsAutomatedImport()) ) { if (bOutImportAll) { @@ -6475,6 +6520,11 @@ int32 UReimportFbxAnimSequenceFactory::GetPriority() const return ImportPriority; } +bool UReimportFbxAnimSequenceFactory::IsAutomatedImport() const +{ + return Super::IsAutomatedImport() || IsAutomatedReimport(); +} + /*------------------------------------------------------------------------------ FBlueprintParentFilter implementation. diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp index f832e0ccfa8c..1c66fe1c875d 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp @@ -229,8 +229,9 @@ UObject* UFbxFactory::FactoryCreateFile //Set the new fbx source path before starting the re-import FReimportManager::Instance()->UpdateReimportPaths(ObjectToReimport, Filenames); //Do the re-import and exit - const bool bShowNotification = !(AssetImportTask && AssetImportTask->bAutomated); - FReimportManager::Instance()->ValidateAllSourceFileAndReimport(ToReimportObjects, bShowNotification); + const bool bIsAutomated = IsAutomatedImport(); + const bool bShowNotification = !bIsAutomated; + FReimportManager::Instance()->ValidateAllSourceFileAndReimport(ToReimportObjects, bShowNotification, /*SourceFileIndex= */ INDEX_NONE, /*bForceNewFile= */ false, bIsAutomated); return ObjectToReimport; } } diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp index 810796317206..b2721169628f 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp @@ -86,6 +86,8 @@ #include "Evaluation/MovieSceneEvaluationTemplateInstance.h" #include "MovieSceneSequence.h" #include "MovieSceneTimeHelpers.h" +#include "EntitySystem/Interrogation/MovieSceneInterrogationLinker.h" +#include "Systems/MovieSceneComponentTransformSystem.h" #if WITH_PHYSX #include "DynamicMeshBuilder.h" @@ -1849,8 +1851,34 @@ bool FFbxExporter::ExportLevelSequenceTracks(UMovieScene* MovieScene, IMovieScen FFrameRate DisplayRate = MovieScene->GetDisplayRate(); - const bool bSkip3DTransformTrack = SkeletalMeshComp && GetExportOptions()->MapSkeletalMotionToRoot; + bool bSkip3DTransformTrack = SkeletalMeshComp && GetExportOptions()->MapSkeletalMotionToRoot; + TArray > TransformTracks; + for ( const FMovieSceneBinding& MovieSceneBinding : MovieScene->GetBindings() ) + { + for ( TWeakObjectPtr RuntimeObject : MovieScenePlayer->FindBoundObjects(MovieSceneBinding.GetObjectGuid(), InSequenceID) ) + { + if (RuntimeObject.IsValid() && ((RuntimeObject == Actor) || (RuntimeObject == BoundObject))) + { + for (UMovieSceneTrack* Track : MovieSceneBinding.GetTracks()) + { + if (Track->IsA(UMovieScene3DTransformTrack::StaticClass())) + { + TransformTracks.Add(Cast(Track)); + } + } + } + } + } + + // If there's more than one transform track for this actor (ie. on the actor and on the root component) or if there's more than one section, evaluate through interrogation + if (TransformTracks.Num() > 1 || (TransformTracks.Num() != 0 && TransformTracks[0].Get()->GetAllSections().Num() > 1)) + { + ExportLevelSequenceInterrogated3DTransformTrack(FbxActor, MovieScenePlayer, InSequenceID, TransformTracks, BoundObject, MovieScene->GetPlaybackRange(), RootToLocalTransform); + + bSkip3DTransformTrack = true; + } + // Look for the tracks that we currently support bool bExportedAnimTrack = false; // Only export the anim track once since the evaluation is already blended for (UMovieSceneTrack* Track : Tracks) @@ -2801,13 +2829,6 @@ void FFbxExporter::ExportChannelToFbxCurve(FbxAnimCurve& InFbxCurve, const FMovi void FFbxExporter::ExportLevelSequence3DTransformTrack(FbxNode* FbxNode, IMovieScenePlayer* MovieScenePlayer, FMovieSceneSequenceIDRef InSequenceID, UMovieScene3DTransformTrack& TransformTrack, UObject* BoundObject, const TRange& InPlaybackRange, const FMovieSceneSequenceTransform& RootToLocalTransform) { - //if more than one section, we use baked version of all sections. - if (TransformTrack.GetAllSections().Num() > 1) - { - ExportLevelSequenceInterrogated3DTransformTrack(FbxNode, MovieScenePlayer, InSequenceID, TransformTrack, BoundObject, InPlaybackRange, RootToLocalTransform); - return; - } - // TODO: Support more than one section? UMovieScene3DTransformSection* TransformSection = TransformTrack.GetAllSections().Num() > 0 ? Cast(TransformTrack.GetAllSections()[0]) @@ -2941,27 +2962,22 @@ void FFbxExporter::ExportLevelSequence3DTransformTrack(FbxNode* FbxNode, IMovieS } } - -static void GetLocationAtTime(IMovieScenePlayer* MovieScenePlayer, FMovieSceneEvaluationTrack* Track, UObject* Object, FFrameTime KeyTime, FVector& KeyPos, FRotator& KeyRot, FVector& KeyScale, FFrameRate TickResolution) +void FFbxExporter::ExportLevelSequenceInterrogated3DTransformTrack(FbxNode* FbxNode, IMovieScenePlayer* MovieScenePlayer, FMovieSceneSequenceIDRef InSequenceID, TArray > TransformTracks, UObject* BoundObject, const TRange& InPlaybackRange, const FMovieSceneSequenceTransform& RootToLocalTransform) { - UE_MOVIESCENE_TODO(arodham: Interrogation) - // FMovieSceneInterrogationData InterrogationData; - // MovieScenePlayer->GetEvaluationTemplate().CopyActuators(InterrogationData.GetAccumulator()); + using namespace UE::MovieScene; - // FMovieSceneContext Context(FMovieSceneEvaluationRange(KeyTime, TickResolution)); - // Track->Interrogate(Context, InterrogationData, Object); + UMovieScene3DTransformTrack* TransformTrack = nullptr; + int32 NumSections = 0; + for (TWeakObjectPtr WeakTransformTrack : TransformTracks) + { + if (WeakTransformTrack.IsValid()) + { + TransformTrack = WeakTransformTrack.Get(); + NumSections += TransformTrack->GetAllSections().Num(); + } + } - // for (const FTransformData& Transform : InterrogationData.Iterate(UMovieScene3DTransformSection::GetInterrogationKey())) - // { - // KeyPos = Transform.Translation; - // KeyRot = Transform.Rotation; - // KeyScale = Transform.Scale; - // break; - // } -} -void FFbxExporter::ExportLevelSequenceInterrogated3DTransformTrack(FbxNode* FbxNode, IMovieScenePlayer* MovieScenePlayer, FMovieSceneSequenceIDRef InSequenceID, UMovieScene3DTransformTrack& TransformTrack, UObject* BoundObject, const TRange& InPlaybackRange, const FMovieSceneSequenceTransform& RootToLocalTransform) -{ - if (TransformTrack.GetAllSections().Num() <= 0) + if (NumSections <= 0 || !TransformTrack) { return; } @@ -2977,11 +2993,11 @@ void FFbxExporter::ExportLevelSequenceInterrogated3DTransformTrack(FbxNode* FbxN if (!FbxNode) { - FbxNode = CreateNode(TransformTrack.GetDisplayName().ToString()); + FbxNode = CreateNode(TransformTrack->GetDisplayName().ToString()); } - FFrameRate TickResolution = TransformTrack.GetTypedOuter()->GetTickResolution(); - FFrameRate DisplayRate = TransformTrack.GetTypedOuter()->GetDisplayRate(); + FFrameRate TickResolution = TransformTrack->GetTypedOuter()->GetTickResolution(); + FFrameRate DisplayRate = TransformTrack->GetTypedOuter()->GetDisplayRate(); FbxAnimCurveNode* TranslationNode = FbxNode->LclTranslation.GetCurveNode(BaseLayer, true); FbxAnimCurveNode* RotationNode = FbxNode->LclRotation.GetCurveNode(BaseLayer, true); @@ -3026,49 +3042,39 @@ void FFbxExporter::ExportLevelSequenceInterrogated3DTransformTrack(FbxNode* FbxN FbxCurveScaleZ->KeyModifyBegin(); } - int32 LocalStartFrame = FFrameRate::TransformTime(FFrameTime(UE::MovieScene::DiscreteInclusiveLower(InPlaybackRange)), TickResolution, DisplayRate).RoundToFrame().Value; - int32 StartFrame = FFrameRate::TransformTime(FFrameTime(UE::MovieScene::DiscreteInclusiveLower(InPlaybackRange) * RootToLocalTransform.InverseLinearOnly()), TickResolution, DisplayRate).RoundToFrame().Value; - int32 AnimationLength = FFrameRate::TransformTime(FFrameTime(FFrameNumber(UE::MovieScene::DiscreteSize(InPlaybackRange))), TickResolution, DisplayRate).RoundToFrame().Value; + FMovieSceneTimeTransform LocatToRootTransform = RootToLocalTransform.InverseLinearOnly(); + FSystemInterrogator Interrogator; - UE_MOVIESCENE_TODO(arodham: Interrogation) -// FMovieSceneEvaluationTemplate* Template = MovieScenePlayer->GetEvaluationTemplate().FindTemplate(InSequenceID); - FMovieSceneEvaluationTrack* EvalTrack = nullptr; - // if (Template) - // { - // EvalTrack = Template->FindTrack(TransformTrack.GetSignature()); - // } - - if (!EvalTrack) + for (TWeakObjectPtr WeakTransformTrack : TransformTracks) { - if (BoundObject) + if (WeakTransformTrack.IsValid()) { - UE_LOG(LogFbx, Warning, TEXT("Exporting 3D TransformTrack on %s failed, can not create eval track.\n\n"), *BoundObject->GetFName().ToString()); + Interrogator.ImportTrack(WeakTransformTrack.Get(), FInterrogationChannel::Default()); } - else - { - UE_LOG(LogFbx, Warning, TEXT("Exporting 3D TransformTrack on an Unbound Actor failed, can not create eval track.\n\n")); - } - return; - } - for (int32 FrameCount = 0; FrameCount <= AnimationLength; ++FrameCount) + int32 LocalStartFrame = FFrameRate::TransformTime(FFrameTime(DiscreteInclusiveLower(InPlaybackRange)), TickResolution, DisplayRate).RoundToFrame().Value; + int32 StartFrame = FFrameRate::TransformTime(FFrameTime(DiscreteInclusiveLower(InPlaybackRange) * RootToLocalTransform.InverseLinearOnly()), TickResolution, DisplayRate).RoundToFrame().Value; + int32 AnimationLength = FFrameRate::TransformTime(FFrameTime(FFrameNumber(DiscreteSize(InPlaybackRange))), TickResolution, DisplayRate).RoundToFrame().Value + 1; // Add one so that we export a key for the end frame + + for (int32 FrameNumber = StartFrame; FrameNumber < StartFrame + AnimationLength; ++FrameNumber) { - int32 LocalFrame = LocalStartFrame + FrameCount; + const FFrameTime FrameTime = FFrameRate::TransformTime(FrameNumber, DisplayRate, TickResolution); + Interrogator.AddInterrogation(FrameTime); + } - FFrameTime LocalTime = FFrameRate::TransformTime(FFrameTime(LocalFrame), DisplayRate, TickResolution); + Interrogator.Update(); - FVector Trans = FVector::ZeroVector; - FRotator Rotator; - FVector Scale; + TArray Transforms; + Interrogator.QueryLocalSpaceTransforms(FInterrogationChannel::Default(), Transforms); - GetLocationAtTime(MovieScenePlayer, EvalTrack, BoundObject, LocalTime, Trans, Rotator, Scale, TickResolution); + ensure(Transforms.Num() == AnimationLength); + for (int32 TransformIndex = 0; TransformIndex < Transforms.Num(); ++TransformIndex) + { FTransform RelativeTransform; - RelativeTransform.SetTranslation(Trans); - RelativeTransform.SetRotation(Rotator.Quaternion()); - RelativeTransform.SetScale3D(Scale); + ConvertOperationalProperty(Transforms[TransformIndex],RelativeTransform); RelativeTransform = RotationDirectionConvert * RelativeTransform; @@ -3076,8 +3082,16 @@ void FFbxExporter::ExportLevelSequenceInterrogated3DTransformTrack(FbxNode* FbxN FbxVector4 KeyRot = Converter.ConvertToFbxRot(RelativeTransform.GetRotation().Euler()); FbxVector4 KeyScale = Converter.ConvertToFbxScale(RelativeTransform.GetScale3D()); + const int32 CurrentFrame = LocalStartFrame + TransformIndex; FbxTime FbxTime; - FbxTime.SetSecondDouble(GetExportOptions()->bExportLocalTime ? DisplayRate.AsSeconds(LocalFrame) : DisplayRate.AsSeconds(StartFrame + FrameCount)); + if (GetExportOptions()->bExportLocalTime) + { + FbxTime.SetSecondDouble(DisplayRate.AsSeconds(CurrentFrame)); + } + else + { + FbxTime.SetSecondDouble(DisplayRate.AsSeconds(CurrentFrame * LocatToRootTransform)); + } FbxCurveTransX->KeySet(FbxCurveTransX->KeyAdd(FbxTime), FbxTime, KeyTrans[0]); FbxCurveTransY->KeySet(FbxCurveTransY->KeyAdd(FbxTime), FbxTime, KeyTrans[1]); diff --git a/Engine/Source/Editor/UnrealEd/Private/FbxExporter.h b/Engine/Source/Editor/UnrealEd/Private/FbxExporter.h index e49a2490c65f..5a878a48e0d4 100644 --- a/Engine/Source/Editor/UnrealEd/Private/FbxExporter.h +++ b/Engine/Source/Editor/UnrealEd/Private/FbxExporter.h @@ -459,7 +459,7 @@ private: /** * Exports a level sequence 3D transform track that's getting interrogated(sample all sections) nto the FBX animation stack. */ - void ExportLevelSequenceInterrogated3DTransformTrack(FbxNode* FbxActor, IMovieScenePlayer* MovieScenePlayer, FMovieSceneSequenceIDRef InSequenceID, UMovieScene3DTransformTrack& TransformTrack, UObject* BoundObject, const TRange& InPlaybackRange, const FMovieSceneSequenceTransform& RootToLocalTransform); + void ExportLevelSequenceInterrogated3DTransformTrack(FbxNode* FbxActor, IMovieScenePlayer* MovieScenePlayer, FMovieSceneSequenceIDRef InSequenceID, TArray > TransformTracks, UObject* BoundObject, const TRange& InPlaybackRange, const FMovieSceneSequenceTransform& RootToLocalTransform); /** * Exports a level sequence property track into the FBX animation stack. diff --git a/Engine/Source/Editor/UnrealEd/Private/FileHelpers.cpp b/Engine/Source/Editor/UnrealEd/Private/FileHelpers.cpp index 20c4aac2c524..11be10c62d41 100644 --- a/Engine/Source/Editor/UnrealEd/Private/FileHelpers.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/FileHelpers.cpp @@ -4216,9 +4216,12 @@ void FEditorFileUtils::GetDirtyWorldPackages(TArray& OutDirtyPackages { if (WorldPackage->IsDirty() && !BuiltDataPackage->IsDirty()) { - // Must become dirty because new prompts to save should not be brought up after each individual map saves - // We also cannot bring up a second prompt to save because a recursion guard blocks it - BuiltDataPackage->MarkPackageDirty(); + // Mark built data package dirty if has not been given name yet + // Otherwise SaveDirtyPackages will fail to create built data file on disk due to re-entrance guard in PromptForCheckoutAndSave preventing a second pop-up window + if (!FPackageName::IsValidLongPackageName(BuiltDataPackage->GetName(), /*bIncludeReadOnlyRoots= */ false)) + { + BuiltDataPackage->MarkPackageDirty(); + } } if (BuiltDataPackage->IsDirty()) diff --git a/Engine/Source/Editor/UnrealEd/Private/HierarchicalLOD.cpp b/Engine/Source/Editor/UnrealEd/Private/HierarchicalLOD.cpp index fa1ef89da6bd..5aaf13aecea8 100644 --- a/Engine/Source/Editor/UnrealEd/Private/HierarchicalLOD.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/HierarchicalLOD.cpp @@ -739,17 +739,17 @@ bool FHierarchicalLODBuilder::ShouldGenerateCluster(AActor* Actor, const int32 H { if (Component->bHiddenInGame) { - return false; + continue; } if (Component->bIsEditorOnly) { - return false; + continue; } if (!Component->GetStaticMesh()) { - return false; + continue; } // see if we should generate it diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp index c7de38e6a8b9..1714e7dd3f34 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp @@ -695,34 +695,40 @@ void FBlueprintEditorUtils::PreloadMembers(UObject* InObject) void FBlueprintEditorUtils::PreloadConstructionScript(UBlueprint* Blueprint) { - if ( Blueprint ) + if (Blueprint) { - FLinkerLoad* TargetLinker = Blueprint->SimpleConstructionScript ? Blueprint->SimpleConstructionScript->GetLinker() : nullptr; - if (TargetLinker) + PreloadConstructionScript(Blueprint->SimpleConstructionScript); + } +} + +void FBlueprintEditorUtils::PreloadConstructionScript(USimpleConstructionScript* SimpleConstructionScript) +{ + if (!SimpleConstructionScript) + { + return; + } + + if (FLinkerLoad* TargetLinker = SimpleConstructionScript->GetLinker()) + { + TargetLinker->Preload(SimpleConstructionScript); + + if (USCS_Node* DefaultSceneRootNode = SimpleConstructionScript->GetDefaultSceneRootNode()) { - TargetLinker->Preload(Blueprint->SimpleConstructionScript); - - if (USCS_Node* DefaultSceneRootNode = Blueprint->SimpleConstructionScript->GetDefaultSceneRootNode()) - { - DefaultSceneRootNode->PreloadChain(); - } - - const TArray& RootNodes = Blueprint->SimpleConstructionScript->GetRootNodes(); - for (int32 NodeIndex = 0; NodeIndex < RootNodes.Num(); ++NodeIndex) - { - RootNodes[NodeIndex]->PreloadChain(); - } + DefaultSceneRootNode->PreloadChain(); } - if (Blueprint->SimpleConstructionScript) + const TArray& RootNodes = SimpleConstructionScript->GetRootNodes(); + for (int32 NodeIndex = 0; NodeIndex < RootNodes.Num(); ++NodeIndex) { - for (USCS_Node* SCSNode : Blueprint->SimpleConstructionScript->GetAllNodes()) - { - if (SCSNode) - { - SCSNode->ValidateGuid(); - } - } + RootNodes[NodeIndex]->PreloadChain(); + } + } + + for (USCS_Node* SCSNode : SimpleConstructionScript->GetAllNodes()) + { + if (SCSNode) + { + SCSNode->ValidateGuid(); } } } @@ -3037,13 +3043,6 @@ bool FBlueprintEditorUtils::IsDataOnlyBlueprint(const UBlueprint* Blueprint) return false; } - // Note that the current implementation of IsChildOf will not crash when called on a nullptr, but - // I'm explicitly null checking because it seems unwise to rely on this behavior: - if (Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf(UActorComponent::StaticClass())) - { - return false; - } - // No new variables defined if (Blueprint->NewVariables.Num() > 0) { @@ -5031,19 +5030,19 @@ void FBlueprintEditorUtils::ChangeMemberVariableType(UBlueprint* Blueprint, cons } } -FName FBlueprintEditorUtils::DuplicateVariable(UBlueprint* InBlueprint, const UStruct* InScope, FName InVariableToDuplicate) +FName FBlueprintEditorUtils::DuplicateMemberVariable(UBlueprint* InFromBlueprint, UBlueprint* InToBlueprint, FName InVariableToDuplicate) { - FName DuplicatedVariableName = NAME_None; + FName DuplicatedVariableName; if (InVariableToDuplicate != NAME_None) { const FScopedTransaction Transaction(LOCTEXT("DuplicateVariable", "Duplicate Variable")); - InBlueprint->Modify(); + InToBlueprint->Modify(); FBPVariableDescription NewVar; UBlueprint* SourceBlueprint; - const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndexAndBlueprint(InBlueprint, InVariableToDuplicate, SourceBlueprint); + const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndexAndBlueprint(InFromBlueprint, InVariableToDuplicate, SourceBlueprint); if (VarIndex != INDEX_NONE) { FBPVariableDescription& Variable = SourceBlueprint->NewVariables[VarIndex]; @@ -5071,15 +5070,42 @@ FName FBlueprintEditorUtils::DuplicateVariable(UBlueprint* InBlueprint, const US } // Add the new variable - InBlueprint->NewVariables.Add(NewVar); + InToBlueprint->NewVariables.Add(NewVar); } - else + + if (NewVar.VarGuid.IsValid()) + { + DuplicatedVariableName = NewVar.VarName; + + // Potentially adjust variable names for any child blueprints + FBlueprintEditorUtils::ValidateBlueprintChildVariables(InToBlueprint, NewVar.VarName); + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InToBlueprint); + } + } + + return DuplicatedVariableName; +} + +FName FBlueprintEditorUtils::DuplicateVariable(UBlueprint* InBlueprint, const UStruct* InScope, FName InVariableToDuplicate) +{ + FName DuplicatedVariableName; + + if (InVariableToDuplicate != NAME_None) + { + const FScopedTransaction Transaction(LOCTEXT("DuplicateVariable", "Duplicate Variable")); + InBlueprint->Modify(); + + DuplicatedVariableName = FBlueprintEditorUtils::DuplicateMemberVariable(InBlueprint, InBlueprint, InVariableToDuplicate); + + if (DuplicatedVariableName == NAME_None && InScope) { // It's probably a local variable UK2Node_FunctionEntry* FunctionEntry = nullptr; FBPVariableDescription* LocalVariable = FBlueprintEditorUtils::FindLocalVariable(InBlueprint, InScope, InVariableToDuplicate, &FunctionEntry); + FBPVariableDescription NewVar; if (LocalVariable) { FunctionEntry->Modify(); @@ -5089,17 +5115,18 @@ FName FBlueprintEditorUtils::DuplicateVariable(UBlueprint* InBlueprint, const US // Add the new variable FunctionEntry->LocalVariables.Add(NewVar); } + + if (NewVar.VarGuid.IsValid()) + { + DuplicatedVariableName = NewVar.VarName; + + // Potentially adjust variable names for any child blueprints + FBlueprintEditorUtils::ValidateBlueprintChildVariables(InBlueprint, NewVar.VarName); + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint); + } } - if (NewVar.VarGuid.IsValid()) - { - DuplicatedVariableName = NewVar.VarName; - - // Potentially adjust variable names for any child blueprints - FBlueprintEditorUtils::ValidateBlueprintChildVariables(InBlueprint, NewVar.VarName); - - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint); - } } return DuplicatedVariableName; @@ -8593,26 +8620,26 @@ bool FBlueprintEditorUtils::PropertyValueFromString_Direct(const FProperty* Prop { FVector V = FVector::ZeroVector; bParseSucceeded = FDefaultValueHelper::ParseVector(StrValue, V); - Property->CopyCompleteValue(DirectValue, &V); + Property->CopySingleValue(DirectValue, &V); } else if (StructProperty->Struct == RotatorStruct) { FRotator R = FRotator::ZeroRotator; bParseSucceeded = FDefaultValueHelper::ParseRotator(StrValue, R); - Property->CopyCompleteValue(DirectValue, &R); + Property->CopySingleValue(DirectValue, &R); } else if (StructProperty->Struct == TransformStruct) { FTransform T = FTransform::Identity; bParseSucceeded = T.InitFromString(StrValue); - Property->CopyCompleteValue(DirectValue, &T); + Property->CopySingleValue(DirectValue, &T); } else if (StructProperty->Struct == LinearColorStruct) { FLinearColor Color; // Color form: "(R=%f,G=%f,B=%f,A=%f)" bParseSucceeded = Color.InitFromString(StrValue); - Property->CopyCompleteValue(DirectValue, &Color); + Property->CopySingleValue(DirectValue, &Color); } else if (StructProperty->Struct) { @@ -8652,25 +8679,25 @@ bool FBlueprintEditorUtils::PropertyValueToString_Direct(const FProperty* Proper if (StructProperty->Struct == VectorStruct) { FVector Vector; - Property->CopyCompleteValue(&Vector, DirectValue); + Property->CopySingleValue(&Vector, DirectValue); OutForm = FString::Printf(TEXT("%f,%f,%f"), Vector.X, Vector.Y, Vector.Z); } else if (StructProperty->Struct == RotatorStruct) { FRotator Rotator; - Property->CopyCompleteValue(&Rotator, DirectValue); + Property->CopySingleValue(&Rotator, DirectValue); OutForm = FString::Printf(TEXT("%f,%f,%f"), Rotator.Pitch, Rotator.Yaw, Rotator.Roll); } else if (StructProperty->Struct == TransformStruct) { FTransform Transform; - Property->CopyCompleteValue(&Transform, DirectValue); + Property->CopySingleValue(&Transform, DirectValue); OutForm = Transform.ToString(); } else if (StructProperty->Struct == LinearColorStruct) { FLinearColor Color; - Property->CopyCompleteValue(&Color, DirectValue); + Property->CopySingleValue(&Color, DirectValue); OutForm = Color.ToString(); } } diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/ChildActorComponentEditorUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/ChildActorComponentEditorUtils.cpp index 31d247aa7917..3d3cbe220431 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/ChildActorComponentEditorUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/ChildActorComponentEditorUtils.cpp @@ -130,7 +130,7 @@ EChildActorComponentTreeViewVisualizationMode FChildActorComponentEditorUtils::G return EditorProjectSettings->DefaultChildActorTreeViewMode; } -EChildActorComponentTreeViewVisualizationMode FChildActorComponentEditorUtils::GetChildActorTreeViewVisualizationMode(UChildActorComponent* ChildActorComponent) +EChildActorComponentTreeViewVisualizationMode FChildActorComponentEditorUtils::GetChildActorTreeViewVisualizationMode(UChildActorComponent* ChildActorComponent, EChildActorComponentTreeViewVisualizationMode DefaultVisOverride) { if (ChildActorComponent) { @@ -141,33 +141,33 @@ EChildActorComponentTreeViewVisualizationMode FChildActorComponentEditorUtils::G } } - return GetProjectDefaultTreeViewVisualizationMode(); + return DefaultVisOverride == EChildActorComponentTreeViewVisualizationMode::UseDefault ? GetProjectDefaultTreeViewVisualizationMode() : DefaultVisOverride; } -bool FChildActorComponentEditorUtils::ShouldExpandChildActorInTreeView(UChildActorComponent* ChildActorComponent) +bool FChildActorComponentEditorUtils::ShouldExpandChildActorInTreeView(UChildActorComponent* ChildActorComponent, EChildActorComponentTreeViewVisualizationMode DefaultVisOverride) { if (!ChildActorComponent) { return false; } - if (!IsChildActorTreeViewExpansionEnabled()) + if ((DefaultVisOverride == EChildActorComponentTreeViewVisualizationMode::UseDefault) && !IsChildActorTreeViewExpansionEnabled()) { return false; } - EChildActorComponentTreeViewVisualizationMode CurrentMode = GetChildActorTreeViewVisualizationMode(ChildActorComponent); + EChildActorComponentTreeViewVisualizationMode CurrentMode = GetChildActorTreeViewVisualizationMode(ChildActorComponent, DefaultVisOverride); return CurrentMode != EChildActorComponentTreeViewVisualizationMode::ComponentOnly; } -bool FChildActorComponentEditorUtils::ShouldShowChildActorNodeInTreeView(UChildActorComponent* ChildActorComponent) +bool FChildActorComponentEditorUtils::ShouldShowChildActorNodeInTreeView(UChildActorComponent* ChildActorComponent, EChildActorComponentTreeViewVisualizationMode DefaultVisOverride) { - if (!ShouldExpandChildActorInTreeView(ChildActorComponent)) + if (!ShouldExpandChildActorInTreeView(ChildActorComponent, DefaultVisOverride)) { return false; } - EChildActorComponentTreeViewVisualizationMode CurrentMode = GetChildActorTreeViewVisualizationMode(ChildActorComponent); + EChildActorComponentTreeViewVisualizationMode CurrentMode = GetChildActorTreeViewVisualizationMode(ChildActorComponent, DefaultVisOverride); return CurrentMode == EChildActorComponentTreeViewVisualizationMode::ComponentWithChildActor; } diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/DebuggerCommands.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/DebuggerCommands.cpp index 83e727b03258..95b0455fbe27 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/DebuggerCommands.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/DebuggerCommands.cpp @@ -955,7 +955,7 @@ TSharedRef< SWidget > FPlayWorldCommands::GeneratePlayMenuContent(TSharedRef& ScriptStack = FBlueprintContextTracker::Get().GetScriptStack(); - if(BlueprintExceptionTracker.ScriptStack.Num() > 0) + if(ScriptStack.Num() > 0) { - Data.TargetGraphStackDepth = BlueprintExceptionTracker.ScriptStack.Num(); + Data.TargetGraphStackDepth = ScriptStack.Num(); // get the current graph that we're stopped at: - const FFrame* CurrentFrame = BlueprintExceptionTracker.ScriptStack.Last(); + const FFrame* CurrentFrame = ScriptStack.Last(); if(CurrentFrame->Object) { if(UBlueprintGeneratedClass* BPGC = Cast(CurrentFrame->Object->GetClass())) @@ -187,22 +180,19 @@ void FKismetDebugUtilities::RequestStepOver() } } } -#endif // DO_BLUEPRINT_GUARD } void FKismetDebugUtilities::RequestStepOut() { -#if DO_BLUEPRINT_GUARD FKismetDebugUtilitiesData& Data = FKismetDebugUtilitiesData::Get(); - FBlueprintExceptionTracker& BlueprintExceptionTracker = FBlueprintExceptionTracker::Get(); + const TArray& ScriptStack = FBlueprintContextTracker::Get().GetScriptStack(); Data.bIsSingleStepping = false; - if (BlueprintExceptionTracker.ScriptStack.Num() > 1) + if (ScriptStack.Num() > 1) { Data.bIsSteppingOut = true; - Data.TargetGraphStackDepth = BlueprintExceptionTracker.ScriptStack.Num() - 1; + Data.TargetGraphStackDepth = ScriptStack.Num() - 1; } -#endif // DO_BLUEPRINT_GUARD } void FKismetDebugUtilities::OnScriptException(const UObject* ActiveObject, const FFrame& StackFrame, const FBlueprintExceptionInfo& Info) @@ -538,9 +528,8 @@ UEdGraphNode* FKismetDebugUtilities::FindSourceNodeForCodeLocation(const UObject void FKismetDebugUtilities::CheckBreakConditions(UEdGraphNode* NodeStoppedAt, bool bHitBreakpoint, int32 BreakpointOffset, bool& InOutBreakExecution) { -#if DO_BLUEPRINT_GUARD FKismetDebugUtilitiesData& Data = FKismetDebugUtilitiesData::Get(); - FBlueprintExceptionTracker& BlueprintExceptionTracker = FBlueprintExceptionTracker::Get(); + const TArray& ScriptStack = FBlueprintContextTracker::Get().GetScriptStack(); if (NodeStoppedAt) { @@ -553,7 +542,7 @@ void FKismetDebugUtilities::CheckBreakConditions(UEdGraphNode* NodeStoppedAt, bo // Update the TargetGraphStackDepth if we're on the same node - this handles things like // event nodes in the Event Graph, which will push another frame on to the stack: if(NodeStoppedAt == Data.MostRecentStoppedNode && - Data.MostRecentBreakpointGraphStackDepth < BlueprintExceptionTracker.ScriptStack.Num() && + Data.MostRecentBreakpointGraphStackDepth < ScriptStack.Num() && Data.TargetGraphStackDepth != INDEX_NONE) { // when we recurse, when a node increases stack depth itself we want to increase our @@ -570,7 +559,7 @@ void FKismetDebugUtilities::CheckBreakConditions(UEdGraphNode* NodeStoppedAt, bo InOutBreakExecution = NodeStoppedAt != Data.MostRecentStoppedNode || ( - Data.MostRecentBreakpointGraphStackDepth < BlueprintExceptionTracker.ScriptStack.Num() && + Data.MostRecentBreakpointGraphStackDepth < ScriptStack.Num() && Data.MostRecentBreakpointInstructionOffset >= BreakpointOffset ); @@ -578,12 +567,12 @@ void FKismetDebugUtilities::CheckBreakConditions(UEdGraphNode* NodeStoppedAt, bo // in to a collapsed graph/macro instance: if(InOutBreakExecution && Data.TargetGraphStackDepth != INDEX_NONE && !bHitBreakpoint) { - InOutBreakExecution = Data.TargetGraphStackDepth >= BlueprintExceptionTracker.ScriptStack.Num(); - if(InOutBreakExecution && Data.TargetGraphStackDepth == BlueprintExceptionTracker.ScriptStack.Num()) + InOutBreakExecution = Data.TargetGraphStackDepth >= ScriptStack.Num(); + if(InOutBreakExecution && Data.TargetGraphStackDepth == ScriptStack.Num()) { // we're at the same stack depth, don't break if we've entered a different graph, but do break if we left the // graph that we were trying to step over.. - const FFrame* CurrentFrame = BlueprintExceptionTracker.ScriptStack.Last(); + const FFrame* CurrentFrame = ScriptStack.Last(); if(CurrentFrame->Object) { if(UBlueprintGeneratedClass* BPGC = Cast(CurrentFrame->Object->GetClass())) @@ -615,7 +604,7 @@ void FKismetDebugUtilities::CheckBreakConditions(UEdGraphNode* NodeStoppedAt, bo if (InOutBreakExecution) { Data.MostRecentStoppedNode = NodeStoppedAt; - Data.MostRecentBreakpointGraphStackDepth = BlueprintExceptionTracker.ScriptStack.Num(); + Data.MostRecentBreakpointGraphStackDepth = ScriptStack.Num(); Data.MostRecentBreakpointInstructionOffset = BreakpointOffset; Data.TargetGraphStackDepth = INDEX_NONE; Data.TargetGraphNodes.Empty(); @@ -637,12 +626,10 @@ void FKismetDebugUtilities::CheckBreakConditions(UEdGraphNode* NodeStoppedAt, bo } } } -#endif // DO_BLUEPRINT_GUARD } void FKismetDebugUtilities::AttemptToBreakExecution(UBlueprint* BlueprintObj, const UObject* ActiveObject, const FFrame& StackFrame, const FBlueprintExceptionInfo& Info, UEdGraphNode* NodeStoppedAt, int32 DebugOpcodeOffset) { -#if DO_BLUEPRINT_GUARD checkSlow(BlueprintObj->GetObjectBeingDebugged() == ActiveObject); FKismetDebugUtilitiesData& Data = FKismetDebugUtilitiesData::Get(); @@ -733,14 +720,13 @@ void FKismetDebugUtilities::AttemptToBreakExecution(UBlueprint* BlueprintObj, co if (bShouldInStackDebug) { TGuardValue GuardDisablePIE(GPlayInEditorID, INDEX_NONE); - const TArray& ScriptStack = FBlueprintExceptionTracker::Get().ScriptStack; + const TArray& ScriptStack = FBlueprintContextTracker::Get().GetScriptStack(); Data.LastExceptionMessage = Info.GetDescription(); FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(NodeStoppedAt); CallStackViewer::UpdateDisplayedCallstack(ScriptStack); WatchViewer::UpdateInstancedWatchDisplay(); FSlateApplication::Get().EnterDebuggingMode(); } -#endif // DO_BLUEPRINT_GUARD } UEdGraphNode* FKismetDebugUtilities::GetCurrentInstruction() @@ -1315,7 +1301,7 @@ FKismetDebugUtilities::EWatchTextResult FKismetDebugUtilities::FindDebuggingData FStructProperty* NodeStructProperty = CastField(FKismetDebugUtilities::FindClassPropertyForNode(Blueprint, Node)); if (NodeStructProperty) { - for (const FStructPropertyPath& NodeProperty : AnimBlueprintGeneratedClass->AnimNodeProperties) + for (const FStructPropertyPath& NodeProperty : AnimBlueprintGeneratedClass->GetAnimNodeProperties()) { if (NodeProperty.Get() == NodeStructProperty) { diff --git a/Engine/Source/Editor/UnrealEd/Private/Lightmass/LightmassRender.cpp b/Engine/Source/Editor/UnrealEd/Private/Lightmass/LightmassRender.cpp index a78461150622..b946491493ba 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Lightmass/LightmassRender.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Lightmass/LightmassRender.cpp @@ -226,7 +226,7 @@ class FLightmassMaterialProxy : public FMaterial, public FMaterialRenderProxy public: FLightmassMaterialProxy(): FMaterial() { - SetQualityLevelProperties(EMaterialQualityLevel::High, false, GMaxRHIFeatureLevel); + SetQualityLevelProperties(GMaxRHIFeatureLevel); } /** Initializes the material proxy and kicks off async shader compiling. */ @@ -822,6 +822,14 @@ public: } } + virtual void GetStaticParameterSet(EShaderPlatform Platform, FStaticParameterSet& OutSet) const override + { + if (const FMaterialResource* Resource = MaterialInterface->GetMaterialResource(GMaxRHIFeatureLevel)) + { + Resource->GetStaticParameterSet(Platform, OutSet); + } + } + private: /** The material interface for this proxy */ UMaterialInterface* MaterialInterface; diff --git a/Engine/Source/Editor/UnrealEd/Private/MaterialGraph.cpp b/Engine/Source/Editor/UnrealEd/Private/MaterialGraph.cpp index 2bda32cc44fb..b438fd89e600 100644 --- a/Engine/Source/Editor/UnrealEd/Private/MaterialGraph.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/MaterialGraph.cpp @@ -52,7 +52,7 @@ void UMaterialGraph::RebuildGraph() MaterialInputs.Add( FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_SubsurfaceColor, Material), MP_SubsurfaceColor, LOCTEXT( "SubsurfaceToolTip", "Allows you to add a color to your Material to simulate shifts in color when light passes through the surface" ) ) ); MaterialInputs.Add( FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData0, Material), MP_CustomData0, FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData0, Material))); MaterialInputs.Add( FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData1, Material), MP_CustomData1, FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData1, Material))); - MaterialInputs.Add( FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_AmbientOcclusion, Material), MP_AmbientOcclusion, LOCTEXT( "AmbientOcclusionToolTip", "Simulate the self-shadowing that happens within crevices of a surface" ) ) ); + MaterialInputs.Add( FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_AmbientOcclusion, Material), MP_AmbientOcclusion, LOCTEXT( "AmbientOcclusionToolTip", "Simulate the self-shadowing that happens within crevices of a surface, or of a volume for volumetric clouds only" ) ) ); MaterialInputs.Add( FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_Refraction, Material), MP_Refraction, LOCTEXT( "RefractionToolTip", "Takes in a texture or value that simulates the index of refraction of the surface" ) ) ); for (int32 UVIndex = 0; UVIndex < UE_ARRAY_COUNT(Material->CustomizedUVs); UVIndex++) diff --git a/Engine/Source/Editor/UnrealEd/Private/ObjectTools.cpp b/Engine/Source/Editor/UnrealEd/Private/ObjectTools.cpp index ba43172c1005..41882f962498 100644 --- a/Engine/Source/Editor/UnrealEd/Private/ObjectTools.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/ObjectTools.cpp @@ -96,6 +96,8 @@ #include "UObject/ReferencerFinder.h" #include "DistanceFieldAtlas.h" #include "MeshCardRepresentation.h" +#include "Containers/Set.h" +#include "UObject/StrongObjectPtr.h" DEFINE_LOG_CATEGORY_STATIC(LogObjectTools, Log, All); @@ -193,10 +195,28 @@ namespace ObjectTools if (!CVarUseLegacyGetReferencersForDeletion.GetValueOnAnyThread()) { const UTransactor* Transactor = GEditor ? GEditor->Trans : nullptr; + bool bIsGatheringPackageRef = InObject->IsA(); // Get the cluster of objects that are going to be deleted TArray ObjectsToDelete; GetObjectsWithOuter(InObject, ObjectsToDelete); + + TSet InternalReferences; + // The old behavior of GatherObjectReferencersForDeletion will find anything that prevents + // InObject from being garbage collected, including internal sub objects. + // it does make an exception with very specific package metadata case. + for (UObject* ObjectToDelete : ObjectsToDelete) + { + if ((ObjectToDelete->HasAnyFlags(GARBAGE_COLLECTION_KEEPFLAGS) || + ObjectToDelete->HasAnyInternalFlags(EInternalObjectFlags::GarbageCollectionKeepFlags)) && + (bIsGatheringPackageRef && !ObjectToDelete->IsA())) + { + InternalReferences.Add(ObjectToDelete); + bOutIsReferenced = true; + } + } + + // Only add the main object to the list once we have finished checking sub-objects. ObjectsToDelete.Add(InObject); // If it's a blueprint, we also want to find anything with a reference to it's generated class @@ -211,7 +231,7 @@ namespace ObjectTools { if (Referencer->IsIn(InObject)) { - References.InternalReferences.Emplace(Referencer); + InternalReferences.Add(Referencer); } else { @@ -227,6 +247,8 @@ namespace ObjectTools } } + References.InternalReferences.Append(InternalReferences.Array()); + // If the object itself isn't in the transaction buffer, check to see if it's a Blueprint asset. We might have instances of the // Blueprint in the transaction buffer, in which case we also want to both alert the user and clear it prior to deleting the asset. if (!bOutIsReferencedInMemoryByUndo) @@ -1291,96 +1313,108 @@ namespace ObjectTools if (bShouldDeleteAfterConsolidate) { - // With all references to the objects to consolidate to eliminated from objects that are currently loaded, it should now be safe to delete - // the objects to be consolidated themselves, leaving behind a redirector in their place to fix up objects that were not currently loaded at the time - // of this operation. - for ( TArray::TConstIterator ConsolIter( ReplaceInfo.ReplaceableObjects ); ConsolIter; ++ConsolIter ) - { - GWarn->StatusUpdate( ConsolIter.GetIndex(), ReplaceInfo.ReplaceableObjects.Num(), NSLOCTEXT("UnrealEd", "ConsolidateAssetsUpdate_DeletingObjects", "Deleting Assets...") ); - - UObject* CurObjToConsolidate = *ConsolIter; - UObject* CurObjOuter = CurObjToConsolidate->GetOuter(); - UPackage* CurObjPackage = CurObjToConsolidate->GetOutermost(); - const FName CurObjName = CurObjToConsolidate->GetFName(); - const FString CurObjPath = CurObjToConsolidate->GetPathName(); - UBlueprint* BlueprintToConsolidate = Cast(CurObjToConsolidate); - - // Attempt to delete the object that was consolidated - if ( DeleteSingleObject( CurObjToConsolidate ) ) + // With all references to the objects to consolidate to eliminated from objects that are currently loaded, it should now be safe to delete + // the objects to be consolidated themselves, leaving behind a redirector in their place to fix up objects that were not currently loaded at the time + // of this operation. + for ( TArray::TConstIterator ConsolIter( ReplaceInfo.ReplaceableObjects ); ConsolIter; ++ConsolIter ) { - // DONT GC YET!!! we still need these objects around to notify other tools that they are gone and to create redirectors - ConsolidatedObjects.Add(CurObjToConsolidate); + GWarn->StatusUpdate( ConsolIter.GetIndex(), ReplaceInfo.ReplaceableObjects.Num(), NSLOCTEXT("UnrealEd", "ConsolidateAssetsUpdate_DeletingObjects", "Deleting Assets...") ); - if ( AlreadyMappedObjectPaths.Contains(CurObjPath) ) + UObject* CurObjToConsolidate = *ConsolIter; + UObject* CurObjOuter = CurObjToConsolidate->GetOuter(); + UPackage* CurObjPackage = CurObjToConsolidate->GetOutermost(); + const FName CurObjName = CurObjToConsolidate->GetFName(); + const FString CurObjPath = CurObjToConsolidate->GetPathName(); + UBlueprint* BlueprintToConsolidate = Cast(CurObjToConsolidate); + + // Attempt to delete the object that was consolidated + if ( DeleteSingleObject( CurObjToConsolidate ) ) { - continue; + // DONT GC YET!!! we still need these objects around to notify other tools that they are gone and to create redirectors + ConsolidatedObjects.Add(CurObjToConsolidate); + + if ( AlreadyMappedObjectPaths.Contains(CurObjPath) ) + { + continue; + } + + // Create a redirector with a unique name + // It will have the same name as the object that was consolidated after the garbage collect + UObjectRedirector* Redirector = NewObject(CurObjOuter, NAME_None, RF_Standalone | RF_Public); + check( Redirector ); + + // Set the redirector to redirect to the object to consolidate to + Redirector->DestinationObject = ObjectToConsolidateTo; + + // Keep track of the object name so we can rename the redirector later + RedirectorToObjectNameMap.Add(Redirector, CurObjName); + AlreadyMappedObjectPaths.Add(CurObjPath); + + // If consolidating blueprints, make sure redirectors are created for the consolidated blueprint class and CDO + if ( BlueprintToConsolidateTo != NULL && BlueprintToConsolidate != NULL ) + { + // One redirector for the class + UObjectRedirector* ClassRedirector = NewObject(CurObjOuter, NAME_None, RF_Standalone | RF_Public); + check( ClassRedirector ); + ClassRedirector->DestinationObject = BlueprintToConsolidateTo->GeneratedClass; + RedirectorToObjectNameMap.Add(ClassRedirector, BlueprintToConsolidate->GeneratedClass->GetFName()); + AlreadyMappedObjectPaths.Add(BlueprintToConsolidate->GeneratedClass->GetPathName()); + + // One redirector for the CDO + UObjectRedirector* CDORedirector = NewObject(CurObjOuter, NAME_None, RF_Standalone | RF_Public); + check( CDORedirector ); + CDORedirector->DestinationObject = BlueprintToConsolidateTo->GeneratedClass->GetDefaultObject(); + RedirectorToObjectNameMap.Add(CDORedirector, BlueprintToConsolidate->GeneratedClass->GetDefaultObject()->GetFName()); + AlreadyMappedObjectPaths.Add(BlueprintToConsolidate->GeneratedClass->GetDefaultObject()->GetPathName()); + } + + DirtiedPackages.AddUnique( CurObjPackage ); } - - // Create a redirector with a unique name - // It will have the same name as the object that was consolidated after the garbage collect - UObjectRedirector* Redirector = NewObject(CurObjOuter, NAME_None, RF_Standalone | RF_Public); - check( Redirector ); - - // Set the redirector to redirect to the object to consolidate to - Redirector->DestinationObject = ObjectToConsolidateTo; - - // Keep track of the object name so we can rename the redirector later - RedirectorToObjectNameMap.Add(Redirector, CurObjName); - AlreadyMappedObjectPaths.Add(CurObjPath); - - // If consolidating blueprints, make sure redirectors are created for the consolidated blueprint class and CDO - if ( BlueprintToConsolidateTo != NULL && BlueprintToConsolidate != NULL ) + // If the object couldn't be deleted, store it in the array that will be used to show the user which objects had errors + else { - // One redirector for the class - UObjectRedirector* ClassRedirector = NewObject(CurObjOuter, NAME_None, RF_Standalone | RF_Public); - check( ClassRedirector ); - ClassRedirector->DestinationObject = BlueprintToConsolidateTo->GeneratedClass; - RedirectorToObjectNameMap.Add(ClassRedirector, BlueprintToConsolidate->GeneratedClass->GetFName()); - AlreadyMappedObjectPaths.Add(BlueprintToConsolidate->GeneratedClass->GetPathName()); - - // One redirector for the CDO - UObjectRedirector* CDORedirector = NewObject(CurObjOuter, NAME_None, RF_Standalone | RF_Public); - check( CDORedirector ); - CDORedirector->DestinationObject = BlueprintToConsolidateTo->GeneratedClass->GetDefaultObject(); - RedirectorToObjectNameMap.Add(CDORedirector, BlueprintToConsolidate->GeneratedClass->GetDefaultObject()->GetFName()); - AlreadyMappedObjectPaths.Add(BlueprintToConsolidate->GeneratedClass->GetDefaultObject()->GetPathName()); + CriticalFailureObjects.Add( CurObjToConsolidate ); } - - DirtiedPackages.AddUnique( CurObjPackage ); } - // If the object couldn't be deleted, store it in the array that will be used to show the user which objects had errors - else + + // Prevent newly created redirectors from being GC'ed before we can rename them + TArray> Redirectors; + Redirectors.Reserve(RedirectorToObjectNameMap.Num()); + for (TMap::TIterator RedirectIt(RedirectorToObjectNameMap); RedirectIt; ++RedirectIt) { - CriticalFailureObjects.Add( CurObjToConsolidate ); + UObjectRedirector* Redirector = RedirectIt.Key(); + Redirectors.Add(TStrongObjectPtr(Redirector)); } - } - TArray PotentialPackagesToDelete; - for ( int32 ObjIdx = 0; ObjIdx < ConsolidatedObjects.Num(); ++ObjIdx ) - { - PotentialPackagesToDelete.AddUnique(ConsolidatedObjects[ObjIdx]->GetOutermost()); - } - - CleanupAfterSuccessfulDelete(PotentialPackagesToDelete); - - // Now that the old objects have been garbage collected, give the redirectors a proper name - for (TMap::TIterator RedirectIt(RedirectorToObjectNameMap); RedirectIt; ++RedirectIt) - { - UObjectRedirector* Redirector = RedirectIt.Key(); - const FName ObjName = RedirectIt.Value(); - - if ( Redirector->Rename(*ObjName.ToString(), NULL, REN_Test) ) + TArray PotentialPackagesToDelete; + for ( int32 ObjIdx = 0; ObjIdx < ConsolidatedObjects.Num(); ++ObjIdx ) { - Redirector->Rename(*ObjName.ToString(), NULL, REN_DontCreateRedirectors | REN_ForceNoResetLoaders | REN_NonTransactional); - FAssetRegistryModule::AssetCreated(Redirector); + PotentialPackagesToDelete.AddUnique(ConsolidatedObjects[ObjIdx]->GetOutermost()); } - else + + CleanupAfterSuccessfulDelete(PotentialPackagesToDelete); + + // Now that the old objects have been garbage collected, give the redirectors a proper name + for (TMap::TIterator RedirectIt(RedirectorToObjectNameMap); RedirectIt; ++RedirectIt) { - // Could not rename the redirector back to the original object's name. This indicates the original - // object could not be garbage collected even though DeleteSingleObject returned true. - CriticalFailureObjects.AddUnique(Redirector); + UObjectRedirector* Redirector = RedirectIt.Key(); + const FName ObjName = RedirectIt.Value(); + + if ( Redirector->Rename(*ObjName.ToString(), NULL, REN_Test) ) + { + Redirector->Rename(*ObjName.ToString(), NULL, REN_DontCreateRedirectors | REN_ForceNoResetLoaders | REN_NonTransactional); + FAssetRegistryModule::AssetCreated(Redirector); + } + else + { + // Could not rename the redirector back to the original object's name. This indicates the original + // object could not be garbage collected even though DeleteSingleObject returned true. + CriticalFailureObjects.AddUnique(Redirector); + } } - } + + Redirectors.Empty(); + } // Empty the provided array so it's not full of pointers to deleted objects @@ -4244,6 +4278,11 @@ namespace ThumbnailTools /** Renders a thumbnail for the specified object */ void RenderThumbnail( UObject* InObject, const uint32 InImageWidth, const uint32 InImageHeight, EThumbnailTextureFlushMode::Type InFlushMode, FTextureRenderTargetResource* InTextureRenderTargetResource, FObjectThumbnail* OutThumbnail ) { + if (!FApp::CanEverRender()) + { + return; + } + // Renderer must be initialized before generating thumbnails check( GIsRHIInitialized ); @@ -4430,7 +4469,11 @@ namespace ThumbnailTools SlowTask.MakeDialog(); // Block until the shader maps that we will save have finished being compiled - InMaterial->GetMaterialResource(GMaxRHIFeatureLevel)->FinishCompilation(); + FMaterialResource* CurrentResource = InMaterial->GetMaterialResource(GMaxRHIFeatureLevel); + if (CurrentResource) + { + CurrentResource->FinishCompilation(); + } } // Generate the thumbnail diff --git a/Engine/Source/Editor/UnrealEd/Private/PackageTools.cpp b/Engine/Source/Editor/UnrealEd/Private/PackageTools.cpp index 9a81df065642..7969be0e9f78 100644 --- a/Engine/Source/Editor/UnrealEd/Private/PackageTools.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/PackageTools.cpp @@ -805,7 +805,9 @@ UPackageTools::UPackageTools(const FObjectInitializer& ObjectInitializer) else if (UGameEngine* GameEngine = Cast(GEngine)) { FString LoadMapError; - GameEngine->LoadMap(GameEngine->GetWorldContextFromWorldChecked(GameEngine->GetGameWorld()), FURL(*WorldNameToReload.ToString()), nullptr, LoadMapError); + // FURL requires a package name and not an asset path + FString WorldPackage = FPackageName::ObjectPathToPackageName(WorldNameToReload.ToString()); + GameEngine->LoadMap(GameEngine->GetWorldContextFromWorldChecked(GameEngine->GetGameWorld()), FURL(*WorldPackage), nullptr, LoadMapError); } } // Update the rendering resources for the levels of the current world if their map build data has changed (we skip this if reloading the current world). diff --git a/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp b/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp index 5807b437e289..506746971618 100644 --- a/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp @@ -492,8 +492,7 @@ void UEditorEngine::EndPlayMap() const FText SystemDisplayName = LOCTEXT("RealtimeOverrideMessage_PIE", "Play in Editor"); RemoveViewportsRealtimeOverride(SystemDisplayName); - // Don't actually need to reset this delegate but doing so allows is to check invalid attempts to execute the delegate - FScopedConditionalWorldSwitcher::SwitchWorldForPIEDelegate = FOnSwitchWorldForPIE(); + EnableWorldSwitchCallbacks(false); // Set the autosave timer to have at least 10 seconds remaining before autosave const static float SecondsWarningTillAutosave = 10.0f; @@ -537,9 +536,6 @@ void UEditorEngine::EndPlayMap() PlaySettingsConfig->PostEditChange(); PlaySettingsConfig->SaveConfig(); - - // Now that we're shutting down, we'll no longer need the instance of the EditorPlaySettings. - PlayInEditorSessionInfo->OriginalRequestParams.EditorPlaySettings->RemoveFromRoot(); } PlayInEditorSessionInfo.Reset(); @@ -834,10 +830,9 @@ void UEditorEngine::RequestPlaySession(const FRequestPlaySessionParams& InParams // Now we duplicate their Editor Play Settings so that we can mutate it as part of startup // to help rule out invalid configuration combinations. FObjectDuplicationParameters DuplicationParams(PlaySessionRequest->EditorPlaySettings, GetTransientPackage()); + // Kept alive by AddReferencedObjects PlaySessionRequest->EditorPlaySettings = CastChecked(StaticDuplicateObjectEx(DuplicationParams)); - // Add the duplicated object to the root, as it's fairly likely an in-process startup will GC it. - PlaySessionRequest->EditorPlaySettings->AddToRoot(); // ToDo: Allow the CDO for the Game Instance to modify the settings after we copy them // so that they can validate user settings before attempting a launch. @@ -2014,31 +2009,29 @@ void UEditorEngine::ToggleBetweenPIEandSIE( bool bNewSession ) FEditorDelegates::OnSwitchBeginPIEAndSIE.Broadcast( bIsSimulatingInEditor ); } -int32 UEditorEngine::OnSwitchWorldForSlatePieWindow( int32 WorldID ) +int32 UEditorEngine::OnSwitchWorldForSlatePieWindow(int32 WorldID, int32 WorldPIEInstance) { static const int32 EditorWorldID = 0; static const int32 PieWorldID = 1; + // PlayWorld cannot be depended on as it only points to the first instance int32 RestoreID = -1; - if( WorldID == -1 && GWorld != PlayWorld && PlayWorld != NULL) + if (WorldID == -1 && !GIsPlayInEditorWorld) { // When we have an invalid world id we always switch to the pie world in the PIE window - const bool bSwitchToPIE = true; - OnSwitchWorldsForPIE( bSwitchToPIE ); + OnSwitchWorldsForPIEInstance(WorldPIEInstance); // The editor world was active restore it later RestoreID = EditorWorldID; } - else if( WorldID == PieWorldID && GWorld != PlayWorld) + else if(WorldID == PieWorldID && !GIsPlayInEditorWorld) { - const bool bSwitchToPIE = true; // Want to restore the PIE world and the current world is not already the pie world - OnSwitchWorldsForPIE( bSwitchToPIE ); + OnSwitchWorldsForPIEInstance(WorldPIEInstance); } - else if( WorldID == EditorWorldID && GWorld != EditorWorld) + else if(WorldID == EditorWorldID && GWorld != EditorWorld) { - const bool bSwitchToPIE = false; // Want to restore the editor world and the current world is not already the editor world - OnSwitchWorldsForPIE( bSwitchToPIE ); + OnSwitchWorldsForPIEInstance(-1); } else { @@ -2060,6 +2053,85 @@ void UEditorEngine::OnSwitchWorldsForPIE( bool bSwitchToPieWorld, UWorld* Overri } } +void UEditorEngine::OnSwitchWorldsForPIEInstance(int32 WorldPIEInstance) +{ + if (WorldPIEInstance < 0) + { + RestoreEditorWorld(EditorWorld); + } + else + { + FWorldContext* PIEContext = GetPIEWorldContext(WorldPIEInstance); + if (PIEContext && PIEContext->World()) + { + SetPlayInEditorWorld(PIEContext->World()); + } + } +} + +void UEditorEngine::EnableWorldSwitchCallbacks(bool bEnable) +{ + if (bEnable) + { + // Set up a delegate to be called in Slate when GWorld needs to change. Slate does not have direct access to the playworld to switch itself + FScopedConditionalWorldSwitcher::SwitchWorldForPIEDelegate = FOnSwitchWorldForPIE::CreateUObject(this, &UEditorEngine::OnSwitchWorldsForPIE); + ScriptExecutionStartHandle = FBlueprintContextTracker::OnEnterScriptContext.AddUObject(this, &UEditorEngine::OnScriptExecutionStart); + ScriptExecutionEndHandle = FBlueprintContextTracker::OnExitScriptContext.AddUObject(this, &UEditorEngine::OnScriptExecutionEnd); + } + else + { + // Don't actually need to reset this delegate but doing so allows is to check invalid attempts to execute the delegate + FScopedConditionalWorldSwitcher::SwitchWorldForPIEDelegate = FOnSwitchWorldForPIE(); + + // There should never be an active function context when pie is ending! + check(!FunctionStackWorldSwitcher); + + FBlueprintContextTracker::OnEnterScriptContext.Remove(ScriptExecutionStartHandle); + FBlueprintContextTracker::OnExitScriptContext.Remove(ScriptExecutionEndHandle); + } +} + +void UEditorEngine::OnScriptExecutionStart(const FBlueprintContextTracker& ContextTracker, const UObject* ContextObject, const UFunction* ContextFunction) +{ + // Only do world switching for game thread callbacks, this is only bound at all in PIE so no need to check GIsEditor + if (IsInGameThread()) + { + // See if we should create a world switcher, which is true if we don't have one and our PIE info is missing + if (!FunctionStackWorldSwitcher && (!GIsPlayInEditorWorld || GPlayInEditorID == -1)) + { + check(FunctionStackWorldSwitcherTag == -1); + UWorld* ContextWorld = GetWorldFromContextObject(ContextObject, EGetWorldErrorMode::ReturnNull); + + if (ContextWorld && ContextWorld->WorldType == EWorldType::PIE) + { + FunctionStackWorldSwitcher = new FScopedConditionalWorldSwitcher(ContextWorld); + FunctionStackWorldSwitcherTag = ContextTracker.GetScriptEntryTag(); + } + } + } +} + +void UEditorEngine::OnScriptExecutionEnd(const struct FBlueprintContextTracker& ContextTracker) +{ + if (IsInGameThread()) + { + if (FunctionStackWorldSwitcher) + { + int32 CurrentScriptEntryTag = ContextTracker.GetScriptEntryTag(); + + // Tag starts at 1 for first function on stack + check(CurrentScriptEntryTag >= 1 && FunctionStackWorldSwitcherTag >= 1); + + if (CurrentScriptEntryTag == FunctionStackWorldSwitcherTag) + { + FunctionStackWorldSwitcherTag = -1; + delete FunctionStackWorldSwitcher; + FunctionStackWorldSwitcher = nullptr; + } + } + } +} + UWorld* UEditorEngine::CreatePIEWorldByDuplication(FWorldContext &WorldContext, UWorld* InWorld, FString &PlayWorldMapName) { double StartTime = FPlatformTime::Seconds(); @@ -2844,8 +2916,7 @@ UGameInstance* UEditorEngine::CreateInnerProcessPIEGameInstance(FRequestPlaySess return nullptr; } - // Set up a delegate to be called in Slate when GWorld needs to change. Slate does not have direct access to the playworld to switch itself - FScopedConditionalWorldSwitcher::SwitchWorldForPIEDelegate = FOnSwitchWorldForPIE::CreateUObject(this, &UEditorEngine::OnSwitchWorldsForPIE); + EnableWorldSwitchCallbacks(true); if (PIEViewport.IsValid()) { @@ -2876,7 +2947,7 @@ FText GeneratePIEViewportWindowTitle(const EPlayNetMode InNetMode, const ERHIFea if (InNetMode == PIE_Client) { - Args.Add(TEXT("NetMode"), FText::FromString(FString::Printf(TEXT("Client %d"), ClientIndex + 1))); + Args.Add(TEXT("NetMode"), FText::FromString(FString::Printf(TEXT("Client %d"), ClientIndex))); } else if (InNetMode == PIE_ListenServer) { @@ -2973,7 +3044,7 @@ TSharedRef UEditorEngine::GeneratePIEViewportWindow(const FRequest // If they haven't provided a Slate Window (common), we will create one. if (!bHasCustomWindow) { - FText ViewportName = GeneratePIEViewportWindowTitle(InNetMode, PreviewPlatform.GetEffectivePreviewFeatureLevel(), InSessionParams,InViewportIndex, InWorldContext.PIEFixedTickSeconds); + FText ViewportName = GeneratePIEViewportWindowTitle(InNetMode, PreviewPlatform.GetEffectivePreviewFeatureLevel(), InSessionParams, InWorldContext.PIEInstance, InWorldContext.PIEFixedTickSeconds); PieWindow = SNew(SWindow) .Title(ViewportName) .ScreenPosition(FVector2D(WindowPosition.X, WindowPosition.Y)) @@ -2988,7 +3059,7 @@ TSharedRef UEditorEngine::GeneratePIEViewportWindow(const FRequest // Setup a delegate for switching to the play world on slate input events, drawing and ticking - FOnSwitchWorldHack OnWorldSwitch = FOnSwitchWorldHack::CreateUObject(this, &UEditorEngine::OnSwitchWorldForSlatePieWindow); + FOnSwitchWorldHack OnWorldSwitch = FOnSwitchWorldHack::CreateUObject(this, &UEditorEngine::OnSwitchWorldForSlatePieWindow, InWorldContext.PIEInstance); PieWindow->SetOnWorldSwitchHack(OnWorldSwitch); if (!bHasCustomWindow) diff --git a/Engine/Source/Editor/UnrealEd/Private/Settings/SettingsClasses.cpp b/Engine/Source/Editor/UnrealEd/Private/Settings/SettingsClasses.cpp index d1e01307f2df..0a1c3be6b783 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Settings/SettingsClasses.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Settings/SettingsClasses.cpp @@ -426,6 +426,7 @@ ULevelEditorMiscSettings::ULevelEditorMiscSettings( const FObjectInitializer& Ob MinimumBoundsForCheckingSize = FVector(500.0f, 500.0f, 50.0f); bCreateNewAudioDeviceForPlayInEditor = true; bEnableLegacyMeshPaintMode = false; + bAvoidRelabelOnPasteSelected = false; } void ULevelEditorMiscSettings::PostEditChangeProperty( struct FPropertyChangedEvent& PropertyChangedEvent ) @@ -892,6 +893,10 @@ ULevelEditorViewportSettings::ULevelEditorViewportSettings( const FObjectInitial bLevelStreamingVolumePrevis = false; BillboardScale = 1.0f; TransformWidgetSizeAdjustment = 0.0f; + SelectedSplinePointSizeAdjustment = 0.0f; + SplineLineThicknessAdjustment = 0.0f; + SplineTangentHandleSizeAdjustment = 0.0f; + SplineTangentScale = 1.0f; MeasuringToolUnits = MeasureUnits_Centimeters; bAllowArcballRotate = false; bAllowScreenRotate = false; diff --git a/Engine/Source/Editor/UnrealEd/Private/ThumbnailManager.cpp b/Engine/Source/Editor/UnrealEd/Private/ThumbnailManager.cpp index 5836afcd7238..273ff45262d9 100644 --- a/Engine/Source/Editor/UnrealEd/Private/ThumbnailManager.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/ThumbnailManager.cpp @@ -190,7 +190,14 @@ void UThumbnailManager::RegisterCustomRenderer(UClass* Class, TSubclassOf(GetTransientPackage(), RendererClass); + if (FApp::CanEverRender()) + { + Info.Renderer = NewObject(GetTransientPackage(), RendererClass); + } + else + { + Info.Renderer = nullptr; + } Info.RendererClassName = RendererClass->GetPathName(); bMapNeedsUpdate = true; diff --git a/Engine/Source/Editor/UnrealEd/Private/UnrealEdMisc.cpp b/Engine/Source/Editor/UnrealEd/Private/UnrealEdMisc.cpp index 99cc6558b7dd..8261b23ef807 100644 --- a/Engine/Source/Editor/UnrealEd/Private/UnrealEdMisc.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/UnrealEdMisc.cpp @@ -258,7 +258,7 @@ void FUnrealEdMisc::OnInit() /** Delegate that gets called when a script exception occurs */ FBlueprintCoreDelegates::OnScriptException.AddStatic(&FKismetDebugUtilities::OnScriptException); - FBlueprintCoreDelegates::OnScriptExecutionEnd.AddStatic(&FKismetDebugUtilities::EndOfScriptExecution); + FBlueprintContextTracker::OnExitScriptContext.AddStatic(&FKismetDebugUtilities::EndOfScriptExecution); FEditorDelegates::ChangeEditorMode.AddRaw(this, &FUnrealEdMisc::OnEditorChangeMode); FCoreDelegates::PreModal.AddRaw(this, &FUnrealEdMisc::OnEditorPreModal); @@ -558,12 +558,17 @@ void FUnrealEdMisc::InitEngineAnalytics() FGameProjectGenerationModule& GameProjectModule = FModuleManager::LoadModuleChecked(TEXT("GameProjectGeneration")); - int32 SourceFileCount = 0; - int64 SourceFileDirectorySize = 0; - GameProjectModule.Get().GetProjectSourceDirectoryInfo(SourceFileCount, SourceFileDirectorySize); + bool bShouldIncludeSourceFileCountAndSize = true; + GConfig->GetBool(TEXT("EngineAnalytics"), TEXT("IncludeSourceFileCountAndSize"), bShouldIncludeSourceFileCountAndSize, GEditorIni); + if (bShouldIncludeSourceFileCountAndSize) + { + int32 SourceFileCount = 0; + int64 SourceFileDirectorySize = 0; + GameProjectModule.Get().GetProjectSourceDirectoryInfo(SourceFileCount, SourceFileDirectorySize); - ProjectAttributes.Add( FAnalyticsEventAttribute( FString( "SourceFileCount" ), SourceFileCount )); - ProjectAttributes.Add(FAnalyticsEventAttribute(FString("SourceFileDirectorySize"), SourceFileDirectorySize)); + ProjectAttributes.Add( FAnalyticsEventAttribute( FString( "SourceFileCount" ), SourceFileCount )); + ProjectAttributes.Add(FAnalyticsEventAttribute(FString("SourceFileDirectorySize"), SourceFileDirectorySize)); + } ProjectAttributes.Add( FAnalyticsEventAttribute( FString( "ModuleCount" ), FModuleManager::Get().GetModuleCount() )); // UObject class count diff --git a/Engine/Source/Editor/UnrealEd/Public/EditorReimportHandler.h b/Engine/Source/Editor/UnrealEd/Public/EditorReimportHandler.h index 7b4fb2f7ff04..31768c54a0b3 100644 --- a/Engine/Source/Editor/UnrealEd/Public/EditorReimportHandler.h +++ b/Engine/Source/Editor/UnrealEd/Public/EditorReimportHandler.h @@ -52,10 +52,11 @@ public: * @param bShowNotification True to show a notification when complete, false otherwise * @param PreferredReimportFile if not empty, will be use in case the original file is missing and bAskForNewFileIfMissing is set to false * @param SourceFileIndex which source file index you want to reimport default is INDEX_NONE(the factory will choose) + * @param bAutomated True to skip dialog prompts * * @return true if the object was handled by one of the reimport handlers; false otherwise */ - UNREALED_API virtual bool Reimport( UObject* Obj, bool bAskForNewFileIfMissing = false, bool bShowNotification = true, FString PreferredReimportFile = TEXT(""), FReimportHandler* SpecifiedReimportHandler = nullptr, int32 SourceFileIndex = INDEX_NONE, bool bForceNewFile = false); + UNREALED_API virtual bool Reimport( UObject* Obj, bool bAskForNewFileIfMissing = false, bool bShowNotification = true, FString PreferredReimportFile = TEXT(""), FReimportHandler* SpecifiedReimportHandler = nullptr, int32 SourceFileIndex = INDEX_NONE, bool bForceNewFile = false, bool bAutomated = false); /** * Attemp to reimport all specified objects. This function will verify that all source file exist and ask the user @@ -68,8 +69,9 @@ public: * * @param ToImportObjects Objects to try reimporting * * @param bShowNotification True to show a notification when complete, false otherwise * * @param SourceFileIndex which source file index you want to reimport default is INDEX_NONE(the factory will chooseP) + * * @param bAutomated True to skip dialog prompts */ - UNREALED_API virtual void ValidateAllSourceFileAndReimport(TArray &ToImportObjects, bool bShowNotification = true, int32 SourceFileIndex = INDEX_NONE, bool bForceNewFile = false); + UNREALED_API virtual void ValidateAllSourceFileAndReimport(TArray &ToImportObjects, bool bShowNotification = true, int32 SourceFileIndex = INDEX_NONE, bool bForceNewFile = false, bool bAutomated = false); /** * Attempt to reimport multiple objects from its source by giving registered reimport @@ -80,10 +82,11 @@ public: * @param bShowNotification True to show a notification when complete, false otherwise * @param PreferredReimportFile if not empty, will be use in case the original file is missing and bAskForNewFileIfMissing is set to false * @param SourceFileIndex which source file index you want to reimport default is INDEX_NONE(the factory will choose) + * @param bAutomated True to skip dialog prompts * * @return true if the objects all imported successfully, for more granular success reporting use FReimportManager::Reimport */ - UNREALED_API virtual bool ReimportMultiple( TArrayView Objects, bool bAskForNewFileIfMissing = false, bool bShowNotification = true, FString PreferredReimportFile = TEXT(""), FReimportHandler* SpecifiedReimportHandler = nullptr, int32 SourceFileIndex = INDEX_NONE ); + UNREALED_API virtual bool ReimportMultiple( TArrayView Objects, bool bAskForNewFileIfMissing = false, bool bShowNotification = true, FString PreferredReimportFile = TEXT(""), FReimportHandler* SpecifiedReimportHandler = nullptr, int32 SourceFileIndex = INDEX_NONE, bool bForceNewFile = false, bool bAutomated = false); /** * Update the reimport paths for the specified object @@ -173,7 +176,7 @@ class FReimportHandler { public: /** Constructor. Add self to manager */ - FReimportHandler(){ FReimportManager::Instance()->RegisterHandler( *this ); } + FReimportHandler() : bAutomatedReimport(false) { FReimportManager::Instance()->RegisterHandler( *this ); } /** Destructor. Remove self from manager */ virtual ~FReimportHandler(){ FReimportManager::Instance()->UnregisterHandler( *this ); } @@ -252,6 +255,19 @@ public: */ void SetPreferredReimportPath(const FString& Path) { PreferredReimportPath = Path; } + /** Sets automated, when True dialog prompts should not appear. */ + virtual void SetAutomatedReimport(const bool InAutomatedReimport) + { + bAutomatedReimport = InAutomatedReimport; + } + + /** When True dialog prompts should not appear. */ + virtual bool IsAutomatedReimport() const + { + return bAutomatedReimport; + } + protected: FString PreferredReimportPath; + bool bAutomatedReimport; }; diff --git a/Engine/Source/Editor/UnrealEd/Public/IPropertyAccessCompiler.h b/Engine/Source/Editor/UnrealEd/Public/IPropertyAccessCompiler.h new file mode 100644 index 000000000000..486dab81a2cd --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Public/IPropertyAccessCompiler.h @@ -0,0 +1,43 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Containers/ArrayView.h" + +class UObject; +struct FPropertyAccessLibrary; + +// The various batching of property copy +UENUM() +enum class EPropertyAccessBatchType : uint8 +{ + // Copies designed to be called one at a time via ProcessCopy + Unbatched, + + // Copies designed to be processed in one call to ProcessCopies + Batched, +}; + +// A helper used to compile a property access library +class IPropertyAccessLibraryCompiler +{ +public: + virtual ~IPropertyAccessLibraryCompiler() {} + + // Begin compilation - reset the library to its default state + virtual void BeginCompilation(UClass* InClass) = 0; + + // Add a copy to the property access library we are compiling + // @return an integer handle to the pending copy. This can be resolved to a true copy index by calling MapCopyIndex + virtual int32 AddCopy(TArrayView InSourcePath, TArrayView InDestPath, EPropertyAccessBatchType InBatchType, UObject* InAssociatedObject = nullptr) = 0; + + // Post-process the library to finish compilation. @return true if compilation succeeded. + virtual bool FinishCompilation() = 0; + + // Iterate any errors we have with compilation + virtual void IterateErrors(TFunctionRef InFunction) const = 0; + + // Maps the initial integer copy handle to a true handle, post compilation + virtual int32 MapCopyIndex(int32 InIndex) const = 0; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/UnrealEd/Public/IPropertyAccessEditor.h b/Engine/Source/Editor/UnrealEd/Public/IPropertyAccessEditor.h new file mode 100644 index 000000000000..0bf0fef28160 --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Public/IPropertyAccessEditor.h @@ -0,0 +1,207 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "UObject/Field.h" +#include "Misc/Attribute.h" +#include "Features/IModularFeature.h" +#include "UObject/UnrealType.h" + +class UBlueprint; +class IPropertyHandle; +class UEdGraph; +class FExtender; +class SWidget; +struct FSlateBrush; +struct FEdGraphPinType; + +/** An element in a binding chain */ +struct FBindingChainElement +{ + FBindingChainElement(FProperty* InProperty, int32 InArrayIndex = INDEX_NONE) + : Field(InProperty) + , ArrayIndex(InArrayIndex) + {} + + FBindingChainElement(UFunction* InFunction) + : Field(InFunction) + , ArrayIndex(INDEX_NONE) + {} + + /** Field that this this chain element refers to */ + FFieldVariant Field; + + /** Optional array index if this element refers to an array */ + int32 ArrayIndex = INDEX_NONE; +}; + +/** + * Info about a redirector binding. + * Redirector bindings allow + */ +struct FRedirectorBindingInfo +{ + FRedirectorBindingInfo(FName InName, const FText& InDescription, UStruct* InStruct) + : Name(InName) + , Description(InDescription) + , Struct(InStruct) + {} + + /** The name of the binding */ + FName Name = NAME_None; + + /** Description of the binding, used as tooltip text */ + FText Description; + + /** The struct that the binding will output */ + UStruct* Struct = nullptr; +}; + +/** Delegate used to generate a new binding function's name */ +DECLARE_DELEGATE_RetVal(FString, FOnGenerateBindingName); + +/** Delegate used to open a binding (e.g. a function) */ +DECLARE_DELEGATE_RetVal_OneParam(bool, FOnGotoBinding, FName /*InPropertyName*/); + +/** Delegate used to se if we can open a binding (e.g. a function) */ +DECLARE_DELEGATE_RetVal_OneParam(bool, FOnCanGotoBinding, FName /*InPropertyName*/); + +/** Delegate used to check whether a property can be bound to the property in question */ +DECLARE_DELEGATE_RetVal_OneParam(bool, FOnCanBindProperty, FProperty* /*InProperty*/); + +/** Delegate used to check whether a function can be bound to the property in question */ +DECLARE_DELEGATE_RetVal_OneParam(bool, FOnCanBindFunction, UFunction* /*InFunction*/); + +/** Delegate called to see if a class can be bound to */ +DECLARE_DELEGATE_RetVal_OneParam(bool, FOnCanBindToClass, UClass* /*InClass*/); + +/** Delegate called to see if a subobject can be bound to */ +DECLARE_DELEGATE_RetVal_OneParam(bool, FOnCanBindToSubObjectClass, UClass* /*InSubObjectClass*/); + +/** Delegate called to add a binding */ +DECLARE_DELEGATE_TwoParams(FOnAddBinding, FName /*InPropertyName*/, const TArray& /*InBindingChain*/); + +/** Delegate called to remove a binding */ +DECLARE_DELEGATE_OneParam(FOnRemoveBinding, FName /*InPropertyName*/); + +/** Delegate called to see if we can remove remove a binding (ie. if it exists) */ +DECLARE_DELEGATE_RetVal_OneParam(bool, FOnCanRemoveBinding, FName /*InPropertyName*/); + +/** Setup arguments structure for a property binding widget */ +struct FPropertyBindingWidgetArgs +{ + /** An optional bindable property */ + FProperty* Property = nullptr; + + /** An optional signature to use to match binding functions */ + UFunction* BindableSignature = nullptr; + + /** Delegate used to generate a new binding function's name */ + FOnGenerateBindingName OnGenerateBindingName; + + /** Delegate used to open a bound generated function */ + FOnGotoBinding OnGotoBinding; + + /** Delegate used to see if we can open a binding (e.g. a function) */ + FOnCanGotoBinding OnCanGotoBinding; + + /** Delegate used to check whether a property can be bound to the property in question */ + FOnCanBindProperty OnCanBindProperty; + + /** Delegate used to check whether a function can be bound to the property in question */ + FOnCanBindFunction OnCanBindFunction; + + /** Delegate called to see if a class can be bound to */ + FOnCanBindToClass OnCanBindToClass; + + /** Delegate called to see if a subobject can be bound to */ + FOnCanBindToSubObjectClass OnCanBindToSubObjectClass; + + /** Delegate called to add a binding */ + FOnAddBinding OnAddBinding; + + /** Delegate called to remove a binding */ + FOnRemoveBinding OnRemoveBinding; + + /** Delegate called to see if we can remove remove a binding (ie. if it exists) */ + FOnCanRemoveBinding OnCanRemoveBinding; + + /** The current binding's text label */ + TAttribute CurrentBindingText; + + /** The current binding's image */ + TAttribute CurrentBindingImage; + + /** The current binding's color */ + TAttribute CurrentBindingColor; + + /** Menu extender */ + TSharedPtr MenuExtender; + + /** Whether to generate pure bindings */ + bool bGeneratePureBindings = true; + + /** Whether to allow array element bindings */ + bool bAllowArrayElementBindings = false; + + /** Whether to allow new bindings to be made from within the widget's UI */ + bool bAllowNewBindings = true; + + /** Whether to allow UObject functions as non-leaf nodes */ + bool bAllowUObjectFunctions = false; +}; + +/** Enum describing the result of ResolveLeafProperty */ +enum class EPropertyAccessResolveResult +{ + /** Resolution of the path failed */ + Failed, + + /** Resolution of the path failed and the property is internal to the initial context */ + SucceededInternal, + + /** Resolution of the path failed and the property is external to the initial context (i.e. uses an object/redirector indirection) */ + SucceededExternal, +}; + +/** Enum describing property compatibility */ +enum class EPropertyAccessCompatibility +{ + // Properties are incompatible + Incompatible, + + // Properties are directly compatible + Compatible, + + // Properties can be copied with a simple type promotion + Promotable, +}; + +/** Editor support for property access system */ +class IPropertyAccessEditor : public IModularFeature +{ +public: + virtual ~IPropertyAccessEditor() {} + + /** + * Make a property binding widget. + * @param InBlueprint The blueprint that the binding will exist within + * @param InArgs Optional arguments for the widget + * @return a new binding widget + */ + virtual TSharedRef MakePropertyBindingWidget(UBlueprint* InBlueprint, const FPropertyBindingWidgetArgs& InArgs = FPropertyBindingWidgetArgs()) const = 0; + + /** Resolve a property path to a structure, returning the leaf property and array index if any. @return true if resolution succeeded */ + virtual EPropertyAccessResolveResult ResolveLeafProperty(UStruct* InStruct, TArrayView InPath, FProperty*& OutProperty, int32& OutArrayIndex) const = 0; + + // Get the compatibility of the two supplied properties. Ordering matters for promotion (A->B). + virtual EPropertyAccessCompatibility GetPropertyCompatibility(const FProperty* InPropertyA, const FProperty* InPropertyB) const = 0; + + // Get the compatibility of the two supplied pin types. Ordering matters for promotion (A->B). + virtual EPropertyAccessCompatibility GetPinTypeCompatibility(const FEdGraphPinType& InPinTypeA, const FEdGraphPinType& InPinTypeB) const = 0; + + // Makes a string path from a binding chain + virtual void MakeStringPath(const TArray& InBindingChain, TArray& OutStringPath) const = 0; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h b/Engine/Source/Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h index e6a6dc757255..6d5ffb198fdb 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h +++ b/Engine/Source/Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h @@ -22,6 +22,7 @@ class FCompilerResultsLog; class INameValidatorInterface; class UActorComponent; class UBlueprintGeneratedClass; +class USimpleConstructionScript; class UK2Node_Event; class UK2Node_Variable; class ULevelScriptBlueprint; @@ -180,10 +181,15 @@ public: static void PreloadMembers(UObject* InObject); /** - * Preloads the construction script, and all templates therein + * Preloads the construction script, and all templates therein, for the given Blueprint object */ static void PreloadConstructionScript(UBlueprint* Blueprint); + /** + * Preloads the given construction script, and all templates therein + */ + static void PreloadConstructionScript(USimpleConstructionScript* SimpleConstructionScript); + /** * Helper function to patch the new CDO into the linker where the old one existed */ @@ -820,6 +826,17 @@ public: */ static bool AddMemberVariable(UBlueprint* Blueprint, const FName& NewVarName, const FEdGraphPinType& NewVarType, const FString& DefaultValue = FString()); + /** + * Duplicates a variable from one Blueprint to another blueprint + * + * @param InFromBlueprint The Blueprint the variable can be found in + * @param InToBlueprint The Blueprint the new variable should be added to (can be the same blueprint) + * @param InVariableToDuplicate Variable name to be found and duplicated + * + * @return Returns the name of the new variable or NAME_None if failed to duplicate + */ + static FName DuplicateMemberVariable(UBlueprint* InFromBlueprint, UBlueprint* InToBlueprint, FName InVariableToDuplicate); + /** * Duplicates a variable given its name and Blueprint * @@ -827,7 +844,7 @@ public: * @paramInScope Local variable's scope * @param InVariableToDuplicate Variable name to be found and duplicated * - * @return Returns the name of the new variable or NAME_None is failed to duplicate + * @return Returns the name of the new variable or NAME_None if failed to duplicate */ static FName DuplicateVariable(UBlueprint* InBlueprint, const UStruct* InScope, FName InVariableToDuplicate); @@ -1247,16 +1264,28 @@ public: /** Indicates if the variable is used on any graphs in this Blueprint*/ static bool IsVariableUsed(const UBlueprint* Blueprint, const FName& Name, UEdGraph* LocalGraphScope = nullptr); - /** Copies the value from the passed in string into a property. ContainerMem points to the Struct or Class containing Property */ + /** + * Copies the value from the passed in string into a property. ContainerMem points to the Struct or Class containing Property + * NOTE: This function does not work correctly with static arrays. + */ static bool PropertyValueFromString(const FProperty* Property, const FString& StrValue, uint8* Container, UObject* OwningObject = nullptr); - /** Copies the value from the passed in string into a property. DirectValue is the raw memory address of the property value */ + /** + * Copies the value from the passed in string into a property. DirectValue is the raw memory address of the property value + * NOTE: This function does not work correctly with static arrays. + */ static bool PropertyValueFromString_Direct(const FProperty* Property, const FString& StrValue, uint8* DirectValue, UObject* OwningObject = nullptr); - /** Copies the value from a property into the string OutForm. ContainerMem points to the Struct or Class containing Property */ + /** + * Copies the value from a property into the string OutForm. ContainerMem points to the Struct or Class containing Property + * NOTE: This function does not work correctly with static arrays. + */ static bool PropertyValueToString(const FProperty* Property, const uint8* Container, FString& OutForm, UObject* OwningObject = nullptr); - /** Copies the value from a property into the string OutForm. DirectValue is the raw memory address of the property value */ + /** + * Copies the value from a property into the string OutForm. DirectValue is the raw memory address of the property value + * NOTE: This function does not work correctly with static arrays. + */ static bool PropertyValueToString_Direct(const FProperty* Property, const uint8* DirectValue, FString& OutForm, UObject* OwningObject = nullptr); /** Call PostEditChange() on all Actors based on the given Blueprint */ diff --git a/Engine/Source/Editor/UnrealEd/Public/Kismet2/ChildActorComponentEditorUtils.h b/Engine/Source/Editor/UnrealEd/Public/Kismet2/ChildActorComponentEditorUtils.h index c0a48054a23e..db4a37f60b19 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Kismet2/ChildActorComponentEditorUtils.h +++ b/Engine/Source/Editor/UnrealEd/Public/Kismet2/ChildActorComponentEditorUtils.h @@ -30,14 +30,23 @@ public: /** Returns the default visualization mode for child actors in a component tree view */ static EChildActorComponentTreeViewVisualizationMode GetProjectDefaultTreeViewVisualizationMode(); - /** Returns the component-specific visualization mode for the given child actor component */ - static EChildActorComponentTreeViewVisualizationMode GetChildActorTreeViewVisualizationMode(UChildActorComponent* ChildActorComponent); + /** + * Returns the component-specific visualization mode for the given child actor component + * @param DefaultVisOverride If different than UseDefault, overrides the default ChildActorComponentTreeViewVisualizationMode from the project settings. + */ + static EChildActorComponentTreeViewVisualizationMode GetChildActorTreeViewVisualizationMode(UChildActorComponent* ChildActorComponent, EChildActorComponentTreeViewVisualizationMode DefaultVisOverride = EChildActorComponentTreeViewVisualizationMode::UseDefault); - /** Whether to expand the given child actor component in a component tree view */ - static bool ShouldExpandChildActorInTreeView(UChildActorComponent* ChildActorComponent); + /** + * Whether to expand the given child actor component in a component tree view + * @param DefaultVisOverride If different than UseDefault, overrides the default ChildActorComponentTreeViewVisualizationMode from the project settings and forces child actor tree view expansion to be enabled. + */ + static bool ShouldExpandChildActorInTreeView(UChildActorComponent* ChildActorComponent, EChildActorComponentTreeViewVisualizationMode DefaultVisOverride = EChildActorComponentTreeViewVisualizationMode::UseDefault); - /** Whether the Child Actor should be shown in a component tree view for the given component */ - static bool ShouldShowChildActorNodeInTreeView(UChildActorComponent* ChildActorComponent); + /** + * Whether the Child Actor should be shown in a component tree view for the given component + * @param DefaultVisOverride If different than UseDefault, overrides the default ChildActorComponentTreeViewVisualizationMode from the project settings and forces child actor tree view expansion to be enabled. + */ + static bool ShouldShowChildActorNodeInTreeView(UChildActorComponent* ChildActorComponent, EChildActorComponentTreeViewVisualizationMode DefaultVisOverride = EChildActorComponentTreeViewVisualizationMode::UseDefault); /** Populates the given menu with options for the given Child Actor component */ static void FillComponentContextMenuOptions(UToolMenu* Menu, UChildActorComponent* ChildActorComponent); diff --git a/Engine/Source/Editor/UnrealEd/Public/Kismet2/KismetDebugUtilities.h b/Engine/Source/Editor/UnrealEd/Public/Kismet2/KismetDebugUtilities.h index 31b58ceb2a1b..709a993076e2 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Kismet2/KismetDebugUtilities.h +++ b/Engine/Source/Editor/UnrealEd/Public/Kismet2/KismetDebugUtilities.h @@ -7,6 +7,8 @@ #include "UObject/Class.h" #include "Engine/Selection.h" +static_assert(DO_BLUEPRINT_GUARD, "KismetDebugUtilities assumes BP exception tracking is enabled"); + class UBlueprint; class UBreakpoint; template class TSimpleRingBuffer; @@ -137,7 +139,7 @@ public: static void RequestStepOut(); /** Called on terminatation of the current script execution so we can reset any break conditions */ - static void EndOfScriptExecution(); + static void EndOfScriptExecution(const FBlueprintContextTracker& BlueprintContext); // The maximum number of trace samples to gather before overwriting old ones enum { MAX_TRACE_STACK_SAMPLES = 1024 }; diff --git a/Engine/Source/Editor/UnrealEd/Public/MouseDeltaTracker.h b/Engine/Source/Editor/UnrealEd/Public/MouseDeltaTracker.h index f07ee50534f2..3ea03a137d97 100644 --- a/Engine/Source/Editor/UnrealEd/Public/MouseDeltaTracker.h +++ b/Engine/Source/Editor/UnrealEd/Public/MouseDeltaTracker.h @@ -16,7 +16,7 @@ struct FInputEventState; /** * Keeps track of mouse movement deltas in the viewports. */ -class FMouseDeltaTracker +class UNREALED_API FMouseDeltaTracker { public: @@ -26,28 +26,28 @@ public: /** * Begin tracking at the specified location for the specified viewport. */ - void UNREALED_API StartTracking(FEditorViewportClient* InViewportClient, const int32 InX, const int32 InY, const FInputEventState& InInputState, bool bNudge = false, bool bResetDragToolState = true); + void StartTracking(FEditorViewportClient* InViewportClient, const int32 InX, const int32 InY, const FInputEventState& InInputState, bool bNudge = false, bool bResetDragToolState = true); /** * Called when a mouse button has been released. If there are no other * mouse buttons being held down, the internal information is reset. */ - bool UNREALED_API EndTracking(FEditorViewportClient* InViewportClient); + bool EndTracking(FEditorViewportClient* InViewportClient); /** * Adds delta movement into the tracker. */ - void UNREALED_API AddDelta(FEditorViewportClient* InViewportClient, FKey InKey, const int32 InDelta, bool InNudge); + void AddDelta(FEditorViewportClient* InViewportClient, FKey InKey, const int32 InDelta, bool InNudge); /** * Returns the current delta. */ - const FVector UNREALED_API GetDelta() const; + const FVector GetDelta() const; /** * Returns the current snapped delta. */ - const FVector UNREALED_API GetDeltaSnapped() const; + const FVector GetDeltaSnapped() const; /** * Returns the absolute delta since dragging started. @@ -87,7 +87,7 @@ public: /** * Converts the delta movement to drag/rotation/scale based on the viewport type or widget axis. */ - void UNREALED_API ConvertMovementDeltaToDragRot(FSceneView* InView, FEditorViewportClient* InViewportClient, FVector& InDragDelta, FVector& OutDrag, FRotator& OutRotation, FVector& OutScale) const; + void ConvertMovementDeltaToDragRot(FSceneView* InView, FEditorViewportClient* InViewportClient, FVector& InDragDelta, FVector& OutDrag, FRotator& OutRotation, FVector& OutScale) const; /** * Absolute Translation conversion from mouse position on the screen to widget axis movement/rotation. */ @@ -96,7 +96,7 @@ public: /** * Subtracts the specified value from End and EndSnapped. */ - void UNREALED_API ReduceBy(const FVector& In); + void ReduceBy(const FVector& In); /** * @return true if a drag tool is being used by the tracker, false otherwise. diff --git a/Engine/Source/Editor/UnrealEd/Public/Settings/EditorProjectSettings.h b/Engine/Source/Editor/UnrealEd/Public/Settings/EditorProjectSettings.h index a964b6146288..d79ba54014a1 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Settings/EditorProjectSettings.h +++ b/Engine/Source/Editor/UnrealEd/Public/Settings/EditorProjectSettings.h @@ -183,12 +183,6 @@ public: UPROPERTY(EditAnywhere, config, Category = Experimental) uint8 bEnableChildActorExpansionInTreeView : 1; - /** - * If enabled, the "Add Component" button will not be accessible in the Components panel, and new components cannot be added to the Blueprint. - */ - UPROPERTY(config) - uint8 bDisallowAddingNewComponents : 1; - /** * List of compiler messages that have been suppressed outside of full, interactive editor sessions for * the current project - useful for silencing warnings that were added to the engine after @@ -215,12 +209,6 @@ public: UPROPERTY(EditAnywhere, config, Category=Experimental, meta=(EditCondition="bEnableChildActorExpansionInTreeView")) EChildActorComponentTreeViewVisualizationMode DefaultChildActorTreeViewMode; - /** - * If not null, component nodes displayed in the tree view will always be filtered by this type, even if other component types exist in the hierarchy. - */ - UPROPERTY(config) - TSubclassOf DefaultComponentsTreeViewTypeFilter; - // UObject interface virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; // End of UObject interface diff --git a/Engine/Source/Editor/UnrealEd/UnrealEd.Build.cs b/Engine/Source/Editor/UnrealEd/UnrealEd.Build.cs index 702c4f6e523a..49383ce92800 100644 --- a/Engine/Source/Editor/UnrealEd/UnrealEd.Build.cs +++ b/Engine/Source/Editor/UnrealEd/UnrealEd.Build.cs @@ -172,6 +172,7 @@ public class UnrealEd : ModuleRules "PixelInspectorModule", "MovieScene", "MovieSceneTracks", + "Sequencer", "ViewportInteraction", "VREditor", "ClothingSystemEditor", diff --git a/Engine/Source/Editor/VirtualTexturingEditor/Classes/RuntimeVirtualTextureDetailsCustomization.h b/Engine/Source/Editor/VirtualTexturingEditor/Classes/RuntimeVirtualTextureDetailsCustomization.h index e4dbd90818e0..37c0a9c30e85 100644 --- a/Engine/Source/Editor/VirtualTexturingEditor/Classes/RuntimeVirtualTextureDetailsCustomization.h +++ b/Engine/Source/Editor/VirtualTexturingEditor/Classes/RuntimeVirtualTextureDetailsCustomization.h @@ -45,6 +45,8 @@ public: protected: FRuntimeVirtualTextureComponentDetailsCustomization(); + /** Returns true if SetBounds button is enabled */ + bool IsSetBoundsEnabled() const; /** Callback for Set Bounds button */ FReply SetBounds(); diff --git a/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureBuildMinMaxHeight.cpp b/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureBuildMinMaxHeight.cpp index bc448eb59cf3..c2cd509113b7 100644 --- a/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureBuildMinMaxHeight.cpp +++ b/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureBuildMinMaxHeight.cpp @@ -3,6 +3,7 @@ #include "RuntimeVirtualTextureBuildMinMaxHeight.h" #include "Components/RuntimeVirtualTextureComponent.h" +#include "ContentStreaming.h" #include "Engine/Texture2D.h" #include "Misc/ScopedSlowTask.h" #include "RendererInterface.h" @@ -136,7 +137,7 @@ namespace RuntimeVirtualTexture BeginInitResource(&RenderTileResources); // Spin up slow task UI - const float TaskWorkRender = NumTilesY; + const float TaskWorkRender = NumTilesX * NumTilesY; const float TaskWorkDownsample = 2; const float TaskWorkBuildBulkData = 2; FScopedSlowTask Task(TaskWorkRender + TaskWorkDownsample + TaskWorkBuildBulkData, FText::AsCultureInvariant(VirtualTexture->GetName())); @@ -147,17 +148,24 @@ namespace RuntimeVirtualTexture FinalPixels.SetNumUninitialized(RenderTileResources.GetNumFinalTexels() * 4); // Iterate over all mip0 tiles and downsample/store each one to the final image - for (int32 TileY = 0; TileY < NumTilesX && !Task.ShouldCancel(); TileY++) + for (int32 TileY = 0; TileY < NumTilesY && !Task.ShouldCancel(); TileY++) { - Task.EnterProgressFrame(); - - for (int32 TileX = 0; TileX < NumTilesY; TileX++) + for (int32 TileX = 0; TileX < NumTilesX; TileX++) { // Render tile + Task.EnterProgressFrame(); + const FBox2D UVRange = FBox2D( FVector2D((float)TileX / (float)NumTilesX, (float)TileY / (float)NumTilesY), FVector2D((float)(TileX + 1) / (float)NumTilesX, (float)(TileY + 1) / (float)NumTilesY)); + // Stream textures for this tile. This triggers a render flush internally. + //todo[vt]: Batch groups of streaming locations and render commands to reduce number of flushes. + const FVector StreamingWorldPos = Transform.TransformPosition(FVector(UVRange.GetCenter(), 0.5f)); + IStreamingManager::Get().Tick(0.f); + IStreamingManager::Get().AddViewSlaveLocation(StreamingWorldPos); + IStreamingManager::Get().StreamAllResources(0); + ENQUEUE_RENDER_COMMAND(MinMaxTextureTileCommand)([ Scene, VirtualTextureSceneIndex, &RenderTileResources, @@ -203,9 +211,6 @@ namespace RuntimeVirtualTexture GraphBuilder.Execute(); }); } - - // (Increment FScopedSlowTask) and flush every row to keep progress UI responsive - FlushRenderingCommands(); } // Downsample and copy to staging @@ -265,6 +270,7 @@ namespace RuntimeVirtualTexture } BeginReleaseResource(&RenderTileResources); + FlushRenderingCommands(); if (Task.ShouldCancel()) { diff --git a/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureBuildStreamingMips.cpp b/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureBuildStreamingMips.cpp index 42bf13e9cb0f..1d930fbe893b 100644 --- a/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureBuildStreamingMips.cpp +++ b/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureBuildStreamingMips.cpp @@ -3,6 +3,7 @@ #include "RuntimeVirtualTextureBuildStreamingMips.h" #include "Components/RuntimeVirtualTextureComponent.h" +#include "ContentStreaming.h" #include "Engine/TextureRenderTarget2D.h" #include "Misc/ScopedSlowTask.h" #include "RendererInterface.h" @@ -171,8 +172,9 @@ namespace RuntimeVirtualTexture } // Spin up slow task UI - const float TaskWorkRender = NumTilesY; - const float TaskWorkBuildBulkData = NumTilesY; + const float TaskWorkRender = NumTilesX * NumTilesY; + const float TextureBuildTaskMultiplier = InComponent->IsCrunchCompressed() ? 3.f : .25f; // Crunch compression is slow. + const float TaskWorkBuildBulkData = TaskWorkRender * TextureBuildTaskMultiplier; FScopedSlowTask Task(TaskWorkRender + TaskWorkBuildBulkData, FText::AsCultureInvariant(InComponent->GetStreamingTexture()->GetName())); Task.MakeDialog(true); @@ -187,15 +189,22 @@ namespace RuntimeVirtualTexture // Iterate over all tiles and render/store each one to the final image for (int32 TileY = 0; TileY < NumTilesY && !Task.ShouldCancel(); TileY++) { - Task.EnterProgressFrame(); - for (int32 TileX = 0; TileX < NumTilesX; TileX++) { // Render tile + Task.EnterProgressFrame(); + const FBox2D UVRange = FBox2D( FVector2D((float)TileX / (float)NumTilesX, (float)TileY / (float)NumTilesY), FVector2D((float)(TileX + 1) / (float)NumTilesX, (float)(TileY + 1) / (float)NumTilesY)); + // Stream textures for this tile. This triggers a render flush internally. + //todo[vt]: Batch groups of streaming locations and render commands to reduce number of flushes. + const FVector StreamingWorldPos = Transform.TransformPosition(FVector(UVRange.GetCenter(), 0.5f)); + IStreamingManager::Get().Tick(0.f); + IStreamingManager::Get().AddViewSlaveLocation(StreamingWorldPos); + IStreamingManager::Get().StreamAllResources(0); + ENQUEUE_RENDER_COMMAND(BakeStreamingTextureTileCommand)([ Scene, VirtualTextureSceneIndex, &RenderTileResources, @@ -273,12 +282,10 @@ namespace RuntimeVirtualTexture } }); } - - // (Increment FScopedSlowTask) and flush every row to keep progress UI responsive - FlushRenderingCommands(); } BeginReleaseResource(&RenderTileResources); + FlushRenderingCommands(); if (Task.ShouldCancel()) { diff --git a/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureDetailsCustomization.cpp b/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureDetailsCustomization.cpp index 8433d75bdc69..f86866741912 100644 --- a/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureDetailsCustomization.cpp +++ b/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureDetailsCustomization.cpp @@ -216,6 +216,7 @@ void FRuntimeVirtualTextureComponentDetailsCustomization::CustomizeDetails(IDeta .ContentPadding(2) .Text(LOCTEXT("Button_SetBounds", "Set Bounds")) .OnClicked(this, &FRuntimeVirtualTextureComponentDetailsCustomization::SetBounds) + .IsEnabled(this, &FRuntimeVirtualTextureComponentDetailsCustomization::IsSetBoundsEnabled) ]; // VirtualTextureBuild buttons. @@ -283,11 +284,20 @@ void FRuntimeVirtualTextureComponentDetailsCustomization::CustomizeDetails(IDeta ]; } +bool FRuntimeVirtualTextureComponentDetailsCustomization::IsSetBoundsEnabled() const +{ + return RuntimeVirtualTextureComponent->GetVirtualTexture() != nullptr; +} + FReply FRuntimeVirtualTextureComponentDetailsCustomization::SetBounds() { - const FScopedTransaction Transaction(LOCTEXT("Transaction_SetBounds", "Set RuntimeVirtualTextureComponent Bounds")); - RuntimeVirtualTexture::SetBounds(RuntimeVirtualTextureComponent); - return FReply::Handled(); + if (RuntimeVirtualTextureComponent->GetVirtualTexture() != nullptr) + { + const FScopedTransaction Transaction(LOCTEXT("Transaction_SetBounds", "Set RuntimeVirtualTextureComponent Bounds")); + RuntimeVirtualTexture::SetBounds(RuntimeVirtualTextureComponent); + return FReply::Handled(); + } + return FReply::Unhandled(); } FReply FRuntimeVirtualTextureComponentDetailsCustomization::BuildStreamedMips() diff --git a/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureSetBounds.cpp b/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureSetBounds.cpp index f60fa348f4bf..6a79f7d0a078 100644 --- a/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureSetBounds.cpp +++ b/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureSetBounds.cpp @@ -15,6 +15,7 @@ namespace RuntimeVirtualTexture void SetBounds(URuntimeVirtualTextureComponent* InComponent) { URuntimeVirtualTexture const* VirtualTexture = InComponent->GetVirtualTexture(); + check(VirtualTexture != nullptr); // Calculate bounds in our desired local space. AActor* Owner = InComponent->GetOwner(); diff --git a/Engine/Source/Editor/WorldBrowser/Private/SWorldDetails.cpp b/Engine/Source/Editor/WorldBrowser/Private/SWorldDetails.cpp index 2f78767752a1..11ed955c836f 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/SWorldDetails.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/SWorldDetails.cpp @@ -73,7 +73,7 @@ void SWorldDetails::OnBrowseWorld(UWorld* InWorld) WorldDetailsView = PropertyModule.CreateDetailView(Args); ChildSlot [ - SNew(SVerticalBox) + SAssignNew(VerticalBox, SVerticalBox) // Inspect level box +SVerticalBox::Slot() @@ -162,18 +162,6 @@ void SWorldDetails::OnBrowseWorld(UWorld* InWorld) DetailsView.ToSharedRef() ] ] - - // World details - +SVerticalBox::Slot() - .FillHeight(1.f) - .Padding(0,4,0,0) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush(TEXT("ToolPanel.GroupBorder"))) - [ - WorldDetailsView.ToSharedRef() - ] - ] ]; WorldModel->RegisterDetailsCustomization(PropertyModule, DetailsView); @@ -201,6 +189,12 @@ void SWorldDetails::OnSelectionChanged() DetailsView->SetObjects(TileProperties, true); + if (VerticalBoxBorder.IsValid()) + { + VerticalBox->RemoveSlot(VerticalBoxBorder->AsShared()); + VerticalBoxBorder.Reset(); + } + if (SelectedLevels.Num() == 0 || SelectedLevels.Num() > 1) { // Clear ComboBox selection in case we have multiple selection @@ -211,7 +205,23 @@ void SWorldDetails::OnSelectionChanged() { SubLevelsComboBox->SetSelectedItem(SelectedLevels[0]); ULevel* LevelObject = SelectedLevels[0]->GetLevelObject(); - WorldDetailsView->SetObject(Cast(LevelObject ? LevelObject->GetLevelPartition() : nullptr)); + UObject* LevelPartition = Cast(LevelObject ? LevelObject->GetLevelPartition() : nullptr); + + if (LevelPartition) + { + VerticalBox->AddSlot() + .FillHeight(1.f) + .Padding(0,4,0,0) + [ + SAssignNew(VerticalBoxBorder, SBorder) + .BorderImage(FEditorStyle::GetBrush(TEXT("ToolPanel.GroupBorder"))) + [ + WorldDetailsView.ToSharedRef() + ] + ]; + } + + WorldDetailsView->SetObject(LevelPartition); } bUpdatingSelection = false; diff --git a/Engine/Source/Editor/WorldBrowser/Private/SWorldDetails.h b/Engine/Source/Editor/WorldBrowser/Private/SWorldDetails.h index d93b7b80b07e..4f37474fec70 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/SWorldDetails.h +++ b/Engine/Source/Editor/WorldBrowser/Private/SWorldDetails.h @@ -59,6 +59,8 @@ private: TSharedPtr WorldModel; TSharedPtr DetailsView; TSharedPtr WorldDetailsView; + TSharedPtr VerticalBox; + TSharedPtr VerticalBoxBorder; TSharedPtr>> SubLevelsComboBox; bool bUpdatingSelection; }; diff --git a/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileCollectionModel.cpp b/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileCollectionModel.cpp index ef8d68124cf1..a8a485e889ea 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileCollectionModel.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileCollectionModel.cpp @@ -2263,6 +2263,11 @@ bool FWorldTileCollectionModel::GenerateLODLevels(FLevelModelList InLevelList, i // Destroy the new world we created and collect the garbage LODWorld->ClearFlags(RF_Public | RF_Standalone); LODWorld->DestroyWorld(false); + // Also, make sure to release generated assets + for (UObject* Asset : GeneratedAssets) + { + Asset->ClearFlags(RF_Standalone); + } CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); } } diff --git a/Engine/Source/Programs/AutomationTool/AutomationUtils/BundleUtils.cs b/Engine/Source/Programs/AutomationTool/AutomationUtils/BundleUtils.cs index 496723a9e0c3..a5b0693cf73f 100644 --- a/Engine/Source/Programs/AutomationTool/AutomationUtils/BundleUtils.cs +++ b/Engine/Source/Programs/AutomationTool/AutomationUtils/BundleUtils.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Text.RegularExpressions; using Tools.DotNETCommon; using AutomationTool; using UnrealBuildTool; @@ -98,5 +99,23 @@ namespace AutomationUtils.Automation // Use OrderBy and not Sort because OrderBy is stable Bundles = Bundles.OrderBy(Bundle => Bundle.Order).ToList(); } + + public static TPlatformBundleSettings MatchBundleSettings( + String FileName, List InstallBundles) where TPlatformBundleSettings : BundleSettings + { + // Try to find a matching chunk regex + foreach (var Bundle in InstallBundles) + { + foreach (string RegexString in Bundle.FileRegex) + { + if (Regex.Match(FileName, RegexString, RegexOptions.IgnoreCase).Success) + { + return Bundle; + } + } + } + + return null; + } } } diff --git a/Engine/Source/Programs/AutomationTool/AutomationUtils/DeviceReservation.cs b/Engine/Source/Programs/AutomationTool/AutomationUtils/DeviceReservation.cs index 8337e54fb7a3..f63b378ba97d 100644 --- a/Engine/Source/Programs/AutomationTool/AutomationUtils/DeviceReservation.cs +++ b/Engine/Source/Programs/AutomationTool/AutomationUtils/DeviceReservation.cs @@ -273,8 +273,7 @@ namespace AutomationTool.DeviceReservation }); } catch (WebException WebEx) - { - Utils.Log(String.Format("WebException on reservation request: {0} : {1}", WebEx.Message, WebEx.Status)); + { if (RetryCount == RetryMax) { @@ -293,6 +292,16 @@ namespace AutomationTool.DeviceReservation { Message = String.Format("No devices currently available, {0}", RetryMessage); } + else + { + using (HttpWebResponse Response = (HttpWebResponse)WebEx.Response) + { + using (StreamReader Reader = new StreamReader(Response.GetResponseStream())) + { + Message = String.Format("WebException on reservation request: {0} : {1} : {2}", WebEx.Message, WebEx.Status, Reader.ReadToEnd()); + } + } + } Console.WriteLine(Message); RetryCount++; diff --git a/Engine/Source/Programs/AutomationTool/AutomationUtils/MCPPublic.cs b/Engine/Source/Programs/AutomationTool/AutomationUtils/MCPPublic.cs index 2ae748d089c1..4430da60a5d4 100644 --- a/Engine/Source/Programs/AutomationTool/AutomationUtils/MCPPublic.cs +++ b/Engine/Source/Programs/AutomationTool/AutomationUtils/MCPPublic.cs @@ -136,6 +136,11 @@ namespace EpicGames.MCP.Automation /// PS4, + /// + /// PS5 platform + /// + PS5, + /// /// Switch platform /// @@ -146,7 +151,15 @@ namespace EpicGames.MCP.Automation /// XboxOne, + /// + /// Xbox One with GDK Platform + /// + XboxOneGDK, + /// + /// XSX platform + /// + XSX, } /// @@ -276,10 +289,22 @@ namespace EpicGames.MCP.Automation { return MCPPlatform.PS4; } + else if (TargetPlatform.ToString() == "PS5") + { + return MCPPlatform.PS5; + } else if (TargetPlatform == UnrealTargetPlatform.XboxOne) { return MCPPlatform.XboxOne; } + else if (TargetPlatform.ToString() == "XboxOneGDK") + { + return MCPPlatform.XboxOneGDK; + } + else if (TargetPlatform.ToString() == "XSX") + { + return MCPPlatform.XSX; + } else if (TargetPlatform == UnrealTargetPlatform.Switch) { return MCPPlatform.Switch; @@ -328,6 +353,24 @@ namespace EpicGames.MCP.Automation { return UnrealTargetPlatform.Switch; } + else if (TargetPlatform == MCPPlatform.XboxOneGDK) + { + UnrealTargetPlatform ReturnValue; + UnrealTargetPlatform.TryParse("XboxOneGDK", out ReturnValue); + return ReturnValue; + } + else if (TargetPlatform == MCPPlatform.XSX) + { + UnrealTargetPlatform ReturnValue; + UnrealTargetPlatform.TryParse("XSX", out ReturnValue); + return ReturnValue; + } + else if (TargetPlatform == MCPPlatform.PS5) + { + UnrealTargetPlatform ReturnValue; + UnrealTargetPlatform.TryParse("PS5", out ReturnValue); + return ReturnValue; + } throw new AutomationException("Platform {0} is not properly supported by the MCP backend yet", TargetPlatform); } diff --git a/Engine/Source/Programs/AutomationTool/AutomationUtils/ProjectParams.cs b/Engine/Source/Programs/AutomationTool/AutomationUtils/ProjectParams.cs index 997447cf7280..97a4b16e3a8f 100644 --- a/Engine/Source/Programs/AutomationTool/AutomationUtils/ProjectParams.cs +++ b/Engine/Source/Programs/AutomationTool/AutomationUtils/ProjectParams.cs @@ -347,6 +347,7 @@ namespace AutomationTool this.NumClients = InParams.NumClients; this.Compressed = InParams.Compressed; this.AdditionalPakOptions = InParams.AdditionalPakOptions; + this.AdditionalIoStoreOptions = InParams.AdditionalIoStoreOptions; this.Archive = InParams.Archive; this.ArchiveDirectoryParam = InParams.ArchiveDirectoryParam; this.ArchiveMetaData = InParams.ArchiveMetaData; @@ -408,6 +409,7 @@ namespace AutomationTool bool? Clean = null, bool? Compressed = null, string AdditionalPakOptions = null, + string AdditionalIoStoreOptions = null, bool? IterativeCooking = null, string IterateSharedCookedBuild = null, bool? IterateSharedBuildUsePrecompiledExe = null, @@ -635,7 +637,7 @@ namespace AutomationTool this.Pak = GetParamValueIfNotSpecified(Command, Pak, this.Pak, "pak"); this.IgnorePaksFromDifferentCookSource = GetParamValueIfNotSpecified(Command, IgnorePaksFromDifferentCookSource, this.IgnorePaksFromDifferentCookSource, "IgnorePaksFromDifferentCookSource"); this.IoStore = GetParamValueIfNotSpecified(Command, IoStore, this.IoStore, "iostore"); - this.SkipIoStore = GetParamValueIfNotSpecified(Command, IoStore, this.SkipIoStore, "skipiostore"); + this.SkipIoStore = GetParamValueIfNotSpecified(Command, SkipIoStore, this.SkipIoStore, "skipiostore"); this.SkipPak = GetParamValueIfNotSpecified(Command, SkipPak, this.SkipPak, "skippak"); if (this.SkipPak) { @@ -666,6 +668,7 @@ namespace AutomationTool } this.Compressed = GetParamValueIfNotSpecified(Command, Compressed, this.Compressed, "compressed"); this.AdditionalPakOptions = ParseParamValueIfNotSpecified(Command, AdditionalPakOptions, "AdditionalPakOptions"); + this.AdditionalIoStoreOptions = ParseParamValueIfNotSpecified(Command, AdditionalIoStoreOptions, "AdditionalIoStoreOptions"); this.IterativeCooking = GetParamValueIfNotSpecified(Command, IterativeCooking, this.IterativeCooking, new string[] { "iterativecooking", "iterate" }); this.IterateSharedCookedBuild = GetParamValueIfNotSpecified(Command, false, false, "iteratesharedcookedbuild") ? "usesyncedbuild" : null; this.IterateSharedCookedBuild = ParseParamValueIfNotSpecified(Command, IterateSharedCookedBuild, "IterateSharedCookedBuild", String.Empty); @@ -1620,6 +1623,11 @@ namespace AutomationTool /// public string AdditionalPakOptions; + /// + /// Additional parameters when generating iostore container files + /// + public string AdditionalIoStoreOptions; + /// /// Cook: Do not include a version number in the cooked content /// @@ -2454,11 +2462,21 @@ namespace AutomationTool private Dictionary ProjectExePaths; + /// + /// Override for the computed based on release version path + /// + public string BasedOnReleaseVersionPathOverride = null; + /// /// Get the path to the directory of the version we are basing a diff or a patch on. /// public String GetBasedOnReleaseVersionPath(DeploymentContext SC, bool bIsClientOnly) { + if (!string.IsNullOrEmpty(BasedOnReleaseVersionPathOverride)) + { + return BasedOnReleaseVersionPathOverride; + } + String BasePath = BasedOnReleaseVersionBasePath; String Platform = SC.StageTargetPlatform.GetCookPlatform(SC.DedicatedServer, bIsClientOnly); if (String.IsNullOrEmpty(BasePath)) @@ -2786,6 +2804,7 @@ namespace AutomationTool CommandUtils.LogLog("ClientTargetPlatform={0}", string.Join(",", ClientTargetPlatforms)); CommandUtils.LogLog("Compressed={0}", Compressed); CommandUtils.LogLog("AdditionalPakOptions={0}", AdditionalPakOptions); + CommandUtils.LogLog("AdditionalIoStoreOptions={0}", AdditionalIoStoreOptions); CommandUtils.LogLog("CookOnTheFly={0}", CookOnTheFly); CommandUtils.LogLog("CookOnTheFlyStreaming={0}", CookOnTheFlyStreaming); CommandUtils.LogLog("UnversionedCookedContent={0}", UnversionedCookedContent); diff --git a/Engine/Source/Programs/AutomationTool/AutomationUtils/Utils.cs b/Engine/Source/Programs/AutomationTool/AutomationUtils/Utils.cs index aff3c973f16f..ef3def1d99cd 100644 --- a/Engine/Source/Programs/AutomationTool/AutomationUtils/Utils.cs +++ b/Engine/Source/Programs/AutomationTool/AutomationUtils/Utils.cs @@ -379,7 +379,7 @@ namespace AutomationTool } else { - Log.TraceWarning("Skip copying file {0} because it doesn't exist.", SourceName); + Log.TraceInformation("Skip copying file {0} because it doesn't exist.", SourceName); } } Retry = !File.Exists(TargetName); @@ -403,7 +403,7 @@ namespace AutomationTool } catch (Exception Ex) { - Log.TraceWarning("SafeCopyFile Exception was {0}", LogUtils.FormatException(Ex)); + Log.TraceInformation("SafeCopyFile Exception was {0}", LogUtils.FormatException(Ex)); Retry = true; } @@ -420,7 +420,7 @@ namespace AutomationTool } else { - Log.TraceWarning("Failed to copy {0} to {1}", SourceName, TargetName); + Log.TraceError("Failed to copy {0} to {1}", SourceName, TargetName); } Result = false; } diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Base/Gauntlet.BaseTestNode.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Base/Gauntlet.BaseTestNode.cs index 4e364dbd9c3a..18c0443a3838 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Base/Gauntlet.BaseTestNode.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Base/Gauntlet.BaseTestNode.cs @@ -19,6 +19,11 @@ namespace Gauntlet /// public abstract float MaxDuration { get; protected set; } + /// + /// What the test result should be treated as if we reach max duration. + /// + public virtual EMaxDurationReachedResult MaxDurationReachedResult { get; set; } + /// /// Override this to set the priority of this test /// diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Base/Gauntlet.TestNode.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Base/Gauntlet.TestNode.cs index 8e1baca98838..08fa183435dc 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Base/Gauntlet.TestNode.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Base/Gauntlet.TestNode.cs @@ -67,6 +67,11 @@ namespace Gauntlet /// float MaxDuration { get; } + /// + /// What the test result should be treated as if we reach max duration. + /// + EMaxDurationReachedResult MaxDurationReachedResult { get; } + /// /// Priority of this test in relation to any others that are running /// diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Devices/Gauntlet.DevicePool.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Devices/Gauntlet.DevicePool.cs index 977bfb04d30f..b11a0736bcaa 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Devices/Gauntlet.DevicePool.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Devices/Gauntlet.DevicePool.cs @@ -534,14 +534,19 @@ namespace Gauntlet return false; } - Dictionary DeviceMap = new Dictionary() + Dictionary DeviceMap = new Dictionary(); + + foreach (string Platform in UnrealTargetPlatform.GetValidPlatformNames()) { - // todo: add other platforms and externalize this mapping - { UnrealTargetPlatform.PS4 , "PS4-DevKit" }, - { UnrealTargetPlatform.XboxOne , "XboxOne-DevKit" }, - { UnrealTargetPlatform.Android , "Android" }, - { UnrealTargetPlatform.Switch , "Switch" } - }; + if (Platform == "PS4" || Platform == "XboxOne") + { + DeviceMap.Add(UnrealTargetPlatform.Parse(Platform), string.Format("{0}-DevKit", Platform)); + } + else + { + DeviceMap.Add(UnrealTargetPlatform.Parse(Platform), Platform); + } + } List Devices = new List(); @@ -1002,7 +1007,7 @@ namespace Gauntlet } var Devices = TooFewTotalDevices.Concat(TooFewCurrentDevices); - var UnsupportedPlatforms = Devices.Where(D => !ServicePlatforms.Contains(D.Platform.Value)); + var UnsupportedPlatforms = Devices.Where(D => !ServicePlatforms.Contains(D.Platform.Value) && !D.Platform.Value.ToString().StartsWith("XboxOne")); // Request devices from the service if we need them if (UseServiceDevices && !String.IsNullOrEmpty(DeviceURL) && UnsupportedPlatforms.Count() == 0 && (TooFewTotalDevices.Count() > 0 || TooFewCurrentDevices.Count() > 0)) diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Gauntlet.TestExecutor.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Gauntlet.TestExecutor.cs index c42c3cbdf512..5ac5696baea8 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Gauntlet.TestExecutor.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Gauntlet.TestExecutor.cs @@ -623,12 +623,21 @@ namespace Gauntlet bool TestIsRunning = TestInfo.TestNode.GetTestStatus() == TestStatus.InProgress; TimeSpan RunningTime = DateTime.Now - TestInfo.PostStartTime; - + if (TestIsRunning && RunningTime.TotalSeconds > TestInfo.TestNode.MaxDuration && !Options.NoTimeout) { - TestInfo.CancellationReason = string.Format("Terminating Test {0} due to maximum duration of {1} seconds. ", TestInfo.TestNode, TestInfo.TestNode.MaxDuration); - TestInfo.FinalResult = TestResult.TimedOut; - Log.Info("{0}", TestInfo.CancellationReason); + if (TestInfo.TestNode.MaxDurationReachedResult == EMaxDurationReachedResult.Failure) + { + TestInfo.CancellationReason = string.Format("Terminating Test {0} due to maximum duration of {1} seconds. ", TestInfo.TestNode, TestInfo.TestNode.MaxDuration); + TestInfo.FinalResult = TestResult.TimedOut; + Log.Info("{0}", TestInfo.CancellationReason); + } + else if (TestInfo.TestNode.MaxDurationReachedResult == EMaxDurationReachedResult.Success) + { + TestInfo.FinalResult = TestResult.Passed; + TestIsRunning = false; + Log.Info(string.Format("Test {0} successfully reached maximum duration of {1} seconds. ", TestInfo.TestNode, TestInfo.TestNode.MaxDuration)); + } } if (IsCancelled) diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/SelfTest/Framework/Gauntlet.SelfTest.OrderOfOpsTest.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/SelfTest/Framework/Gauntlet.SelfTest.OrderOfOpsTest.cs index 36cd1ab4284d..2136905893ea 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/SelfTest/Framework/Gauntlet.SelfTest.OrderOfOpsTest.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/SelfTest/Framework/Gauntlet.SelfTest.OrderOfOpsTest.cs @@ -38,6 +38,17 @@ namespace Gauntlet.SelfTest get { return 0; } } + /// + /// What the test result should be treated as if we reach max duration. + /// + public virtual EMaxDurationReachedResult MaxDurationReachedResult + { + get + { + return EMaxDurationReachedResult.Failure; + } + } + public TestPriority Priority { get { return TestPriority.Normal; } } public string Name diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/SelfTest/Gauntlet.SelfTest.BaseNode.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/SelfTest/Gauntlet.SelfTest.BaseNode.cs index 5cf07901c379..4a746e9fc02b 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/SelfTest/Gauntlet.SelfTest.BaseNode.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/SelfTest/Gauntlet.SelfTest.BaseNode.cs @@ -19,7 +19,20 @@ namespace Gauntlet.SelfTest return 300; } } - + + + /// + /// What the test result should be treated as if we reach max duration. + /// + public virtual EMaxDurationReachedResult MaxDurationReachedResult + { + get + { + return EMaxDurationReachedResult.Failure; + } + } + + public TestPriority Priority { get { return TestPriority.Normal; } } public string Name diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealPGONode.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealPGONode.cs index ebca775dd3e5..24637c99fa2a 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealPGONode.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealPGONode.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using AutomationTool; using UnrealBuildTool; using System.Diagnostics; +using System.Diagnostics.Eventing.Reader; +using Gauntlet.Utils; namespace Gauntlet { @@ -143,33 +145,46 @@ namespace Gauntlet { ScreenshotTime = DateTime.Now; - try + if (!File.Exists(Path.Combine(ScreenshotDirectory, ImageFilename))) { - TimeSpan ImageTimestamp = DateTime.UtcNow - ScreenshotStartTime; - string ImageOutputPath = Path.Combine(ScreenshotDirectory, ImageTimestamp.ToString().Replace(':', '-') + ".jpg"); - ImageUtils.ResaveImageAsJpgWithScaleAndQuality(Path.Combine(ScreenshotDirectory, ImageFilename), ImageOutputPath, ScreenshotScale, ScreenshotQuality); + Log.Info("PGOPlatform.TakeScreenshot returned true, but output image {0} does not exist! skipping", ImageFilename); + } + else if(new FileInfo(Path.Combine(ScreenshotDirectory, ImageFilename)).Length <= 0) + { + Log.Info("PGOPlatform.TakeScreenshot returned true, but output image {0} is size 0! skipping", ImageFilename); + } + else + { + try + { + TimeSpan ImageTimestamp = DateTime.UtcNow - ScreenshotStartTime; + string ImageOutputPath = Path.Combine(ScreenshotDirectory, ImageTimestamp.ToString().Replace(':', '-') + ".jpg"); + ImageUtils.ResaveImageAsJpgWithScaleAndQuality(Path.Combine(ScreenshotDirectory, ImageFilename), ImageOutputPath, ScreenshotScale, ScreenshotQuality); - // Delete the temporary image file - try { File.Delete(Path.Combine(ScreenshotDirectory, ImageFilename)); } + // Delete the temporary image file + try { File.Delete(Path.Combine(ScreenshotDirectory, ImageFilename)); } + catch (Exception e) + { + Log.Warning("Got Exception Deleting temp iamge: {0}", e.ToString()); + } + } catch (Exception e) { - Log.Warning("Got Exception Deleting temp iamge: {0}", e.ToString()); + Log.Info("Got Exception Renaming PGO image {0}: {1}", ImageFilename, e.ToString()); + + TimeSpan ImageTimestamp = DateTime.UtcNow - ScreenshotStartTime; + string CopyFileName = Path.Combine(ScreenshotDirectory, ImageTimestamp.ToString().Replace(':', '-') + ".bmp"); + Log.Info("Copying unconverted image {0} to {1}", ImageFilename, CopyFileName); + try + { + File.Copy(Path.Combine(ScreenshotDirectory, ImageFilename), CopyFileName); + } + catch (Exception e2) + { + Log.Warning("Got Exception copying un-converted screenshot image: {0}", e2.ToString()); + } } } - catch(Exception e) - { - Log.Warning("Got Exception Renaming PGO image {0}: {1}", ImageFilename, e.ToString()); - Process proc = Process.GetCurrentProcess(); - Log.Info("Memory Usage: Private: {0}", proc.PrivateMemorySize64); - Log.Info("Memory Usage: Virtual: {0}", proc.VirtualMemorySize64); - Log.Info("Memory Usage: Peak Virtual: {0}", proc.PeakVirtualMemorySize64); - Log.Info("Memory Usage: Paged: {0}", proc.PagedMemorySize64); - Log.Info("Memory Usage: System Paged: {0}", proc.PagedSystemMemorySize64); - Log.Info("Memory Usage: System NonPaged: {0}", proc.NonpagedSystemMemorySize64); - Log.Info("Memory Usage: Working Set: {0}", proc.WorkingSet64); - Log.Info("Memory Usage: Peak Working Set: {0}", proc.PeakWorkingSet64); - proc.Dispose(); - } } } diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealTestConfiguration.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealTestConfiguration.cs index 7783b50e5b7d..0b560f061ddf 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealTestConfiguration.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealTestConfiguration.cs @@ -260,6 +260,15 @@ namespace Gauntlet Saved, Platform } + + /// + /// What reaching the max duration of this test signifies. + /// + public enum EMaxDurationReachedResult + { + Failure, + Success + } /// /// Delegate for role device configuration @@ -497,6 +506,11 @@ namespace Gauntlet [AutoParam(600.0f)] public float MaxDuration { get; set; } + /// + /// What the test result should be treated as if we reach max duration. + /// + public EMaxDurationReachedResult MaxDurationReachedResult { get; set; } + /// /// Whether ensures are considered a failure /// @@ -549,6 +563,8 @@ namespace Gauntlet RequiredRoles = new Dictionary>(); HeartbeatOptions = new UnrealHeartbeatOptions(); + + MaxDurationReachedResult = EMaxDurationReachedResult.Failure; } /// diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealTestNode.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealTestNode.cs index 9fea49c2745c..f9844898ae51 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealTestNode.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealTestNode.cs @@ -94,6 +94,8 @@ namespace Gauntlet /// public override float MaxDuration { get; protected set; } + + /// /// Priority of this test /// @@ -600,6 +602,7 @@ namespace Gauntlet { // Update these for the executor MaxDuration = Config.MaxDuration; + MaxDurationReachedResult = Config.MaxDurationReachedResult; UnrealTestResult = TestResult.Invalid; MarkTestStarted(); } diff --git a/Engine/Source/Programs/AutomationTool/Scripts/BuildPhysX.Automation.cs b/Engine/Source/Programs/AutomationTool/Scripts/BuildPhysX.Automation.cs index 53510d1bf66b..638bd039e2e9 100644 --- a/Engine/Source/Programs/AutomationTool/Scripts/BuildPhysX.Automation.cs +++ b/Engine/Source/Programs/AutomationTool/Scripts/BuildPhysX.Automation.cs @@ -288,6 +288,8 @@ public sealed class BuildPhysX : BuildCommand } public abstract void BuildTargetLib(PhysXTargetLib TargetLib, string TargetConfiguration); + + public virtual void Cleanup() {} } public abstract class MSBuildTargetPlatform : TargetPlatform @@ -426,8 +428,8 @@ public sealed class BuildPhysX : BuildCommand } RunAndLog(CmdEnv, "/usr/bin/xcodebuild", string.Format("-project \"{0}\" -target=\"ALL_BUILD\" -configuration {1} -quiet", ProjectFile, TargetConfiguration)); - } } + } // Apex libs that do not have an APEX prefix in their name private static string[] APEXSpecialLibs = { "NvParameterized", "RenderDebug" }; @@ -477,14 +479,14 @@ public sealed class BuildPhysX : BuildCommand // Grab all the non-abstract subclasses of TargetPlatform from the executing assembly. var AvailablePlatformTypes = from Assembly in AppDomain.CurrentDomain.GetAssemblies() from Type in Assembly.GetTypes() - where !Type.IsAbstract && Type.IsSubclassOf(typeof(TargetPlatform)) + where !Type.IsAbstract && Type.IsSubclassOf(typeof(TargetPlatform)) && !Type.IsAbstract select Type; var PlatformTypeMap = new Dictionary(); foreach (var Type in AvailablePlatformTypes) { - int Index = Type.Name.LastIndexOf('_'); + int Index = Type.Name.IndexOf('_'); if (Index == -1) { throw new BuildException("Invalid PhysX target platform type found: {0}", Type); @@ -681,7 +683,9 @@ public sealed class BuildPhysX : BuildCommand // Parse out the libs we want to build List TargetLibs = GetTargetLibs(); - if (bBuildSolutions) + // Only generate solutions upfront if we aren't building libraries, otherwise we will generate them + // just before building (this is largely for xcode where the same project file is used for x64 and arm) + if (bBuildSolutions && !bBuildLibraries) { foreach (PhysXTargetLib TargetLib in TargetLibs) { @@ -711,29 +715,34 @@ public sealed class BuildPhysX : BuildCommand { foreach (TargetPlatform Platform in TargetPlatforms.Where(P => P.SupportsTargetLib(TargetLib))) { + if (!Platform.SeparateProjectPerConfig) + { + Platform.SetupTargetLib(TargetLib, null); + } + foreach (string TargetConfiguration in TargetConfigurations) { + if (Platform.SeparateProjectPerConfig) + { + Platform.SetupTargetLib(TargetLib, TargetConfiguration); + } + foreach (FileReference FileToDelete in Platform.EnumerateOutputFiles(TargetLib, TargetConfiguration).Distinct()) { FilesToReconcile.Add(FileToDelete); - + // Also clean the output files InternalUtils.SafeDeleteFile(FileToDelete.FullName); } + + Platform.BuildTargetLib(TargetLib, TargetConfiguration); } } } - // Build each target lib, for each config and platform - foreach (PhysXTargetLib TargetLib in TargetLibs) + foreach (TargetPlatform Platform in TargetPlatforms) { - foreach (TargetPlatform Platform in TargetPlatforms.Where(P => P.SupportsTargetLib(TargetLib))) - { - foreach (string TargetConfiguration in TargetConfigurations) - { - Platform.BuildTargetLib(TargetLib, TargetConfiguration); - } - } + Platform.Cleanup(); } } @@ -1133,7 +1142,9 @@ class BuildPhysX_Linux : BuildPhysX.MakefileTargetPlatform } } -class BuildPhysX_Mac : BuildPhysX.XcodeTargetPlatform +// the factory code that creates these based on arguments uses the name not the properties so +// this should only ever be instantiated by the real Mac class below +abstract class BuildPhysX_MacBase : BuildPhysX.XcodeTargetPlatform { public override UnrealTargetPlatform Platform => UnrealTargetPlatform.Mac; public override bool HasBinaries => true; @@ -1144,6 +1155,13 @@ class BuildPhysX_Mac : BuildPhysX.XcodeTargetPlatform public override bool UseResponseFiles => false; public override string TargetBuildPlatform => "mac"; + string Arch; + + public BuildPhysX_MacBase(string InArch) + { + Arch = InArch; + } + public override bool SupportsTargetLib(BuildPhysX.PhysXTargetLib Library) { switch (Library) @@ -1154,6 +1172,145 @@ class BuildPhysX_Mac : BuildPhysX.XcodeTargetPlatform default: return false; } } + + public override string GetAdditionalCMakeArguments(BuildPhysX.PhysXTargetLib TargetLib, string TargetConfiguration) + { + return string.Format(" -DCMAKE_OSX_ARCHITECTURES=\"{0}\"", Arch); + } +} + +class BuildPhysX_Mac_x86_64 : BuildPhysX_MacBase +{ + public BuildPhysX_Mac_x86_64() : base("x86_64") + { + } +} + +class BuildPhysX_Mac_arm64: BuildPhysX_MacBase +{ + public BuildPhysX_Mac_arm64() : base("arm64") + { + } +} + +// Wrapper class that calls the base mac class to build for different architectures. The libs are +// saved off and then lipo'd into a universal binary +class BuildPhysX_Mac : BuildPhysX.TargetPlatform +{ + public override UnrealTargetPlatform Platform => UnrealTargetPlatform.Mac; + public override bool HasBinaries => true; + public override string DebugDatabaseExtension => null; + public override string DynamicLibraryExtension => "dylib"; + public override string StaticLibraryExtension => "a"; + public override bool IsPlatformExtension => false; + public override bool UseResponseFiles => false; + public override string TargetBuildPlatform => "mac"; + + public override bool SeparateProjectPerConfig => false; + + public override string CMakeGeneratorName => x86Build.CMakeGeneratorName; + + BuildPhysX_MacBase x86Build = new BuildPhysX_Mac_x86_64(); + BuildPhysX_MacBase ArmBuild = new BuildPhysX_Mac_arm64(); + + List x86Slices = new List(); + List ArmSlices = new List(); + + public override bool SupportsTargetLib(BuildPhysX.PhysXTargetLib Library) + { + switch (Library) + { + case BuildPhysX.PhysXTargetLib.APEX: return true; + case BuildPhysX.PhysXTargetLib.NvCloth: return true; + case BuildPhysX.PhysXTargetLib.PhysX: return true; + default: + return false; + } + } + + public override void SetupTargetLib(BuildPhysX.PhysXTargetLib TargetLib, string TargetConfiguration) + { + // do nothing. We'll set things up just before we build them. + } + + public override void BuildTargetLib(BuildPhysX.PhysXTargetLib TargetLib, string TargetConfiguration) + { + // build for x86 + x86Build.SetupTargetLib(TargetLib, TargetConfiguration); + LogInformation("Building x86_64 lib slice"); + x86Build.BuildTargetLib(TargetLib, TargetConfiguration); + + IEnumerable x86Libs = x86Build.EnumerateOutputFiles(TargetLib, TargetConfiguration).Distinct(); + + // move x86 files to temp versions + foreach (FileReference LibFile in x86Libs) + { + string Extension = LibFile.GetExtension(); + FileReference x86File = LibFile.ChangeExtension(Extension + "_x86_64"); + LogInformation("Moving {0} to {1}", LibFile, x86File); + FileReference.Delete(x86File); + FileReference.Move(LibFile, x86File); + + x86Slices.Add(x86File); + } + + // build for arm + ArmBuild.SetupTargetLib(TargetLib, TargetConfiguration); + LogInformation("Building arm64 lib slice"); + ArmBuild.BuildTargetLib(TargetLib, TargetConfiguration); + + IEnumerable ArmLibs = ArmBuild.EnumerateOutputFiles(TargetLib, TargetConfiguration).Distinct(); + + // move arm files to temp versions and lipo the + foreach (FileReference LibFile in ArmLibs) + { + string Extension = LibFile.GetExtension(); + FileReference x86File = LibFile.ChangeExtension(Extension + "_x86_64"); + FileReference ArmFile = LibFile.ChangeExtension(Extension + "_arm"); + LogInformation("Moving {0} to {1}", LibFile, ArmFile); + FileReference.Delete(ArmFile); + FileReference.Move(LibFile, ArmFile); + + ArmSlices.Add(ArmFile); + } + } + + public override void Cleanup() + { + x86Slices = x86Slices.Distinct().ToList(); + ArmSlices = ArmSlices.Distinct().ToList(); + + LogInformation("x86_64 slices generated: {0}", string.Join(", ", x86Slices)); + LogInformation("arm64 slices generated: {0}", string.Join(", ", ArmSlices)); + + foreach (FileReference LibFile in x86Slices) + { + // from foo.a_x84_64 (or foo.dylib_x86_64) deduce the names of the arm and final libs + FileReference x86File = LibFile; + string x86Extension = LibFile.GetExtension(); + string ArmExtension = x86Extension.Replace("_x86_64", "_arm"); + string OutputExtension = x86Extension.Replace("_x86_64", ""); + FileReference ArmFile = LibFile.ChangeExtension(ArmExtension); + + FileReference OutputFile = LibFile.ChangeExtension(OutputExtension); + + ProcessStartInfo StartInfo = new ProcessStartInfo(); + StartInfo.FileName = "lipo"; + StartInfo.Arguments = string.Format("-create {0} {1} -output {2}", ArmFile, x86File, OutputFile); + StartInfo.RedirectStandardError = true; + + LogInformation("Running: 'lipo {0}'", StartInfo.Arguments); + if (Utils.RunLocalProcessAndLogOutput(StartInfo) != 0) + { + LogError("Failed to create universal binary for {0}", LibFile); + } + else + { + FileReference.Delete(x86File); + FileReference.Delete(ArmFile); + } + } + } } class BuildPhysX_TVOS : BuildPhysX.XcodeTargetPlatform diff --git a/Engine/Source/Programs/AutomationTool/Scripts/CleanAutomationReports.cs b/Engine/Source/Programs/AutomationTool/Scripts/CleanAutomationReports.cs index d7c8655a29b2..68ea91d4b7fb 100644 --- a/Engine/Source/Programs/AutomationTool/Scripts/CleanAutomationReports.cs +++ b/Engine/Source/Programs/AutomationTool/Scripts/CleanAutomationReports.cs @@ -17,6 +17,7 @@ namespace AutomationTool [Help("Removes folders in an automation report directory that are older than a certain time.")] [Help("ReportDir=", "Path to the root report directory")] [Help("Days=", "Number of days to keep reports for")] + [Help("Depth=", "How many subdirectories deep to clean, defaults to 0 (top level cleaning).")] class CleanAutomationReports : BuildCommand { /// @@ -24,60 +25,80 @@ namespace AutomationTool /// public override void ExecuteBuild() { - string ReportDir = ParseParamValue("ReportDir", null); - if (ReportDir == null) - { - throw new AutomationException("Missing -ReportDir parameter"); - } - - string Days = ParseParamValue("Days", null); - if (Days == null) - { - throw new AutomationException("Missing -Days parameter"); - } + string ReportDir = ParseRequiredStringParam("ReportDir"); + string Days = ParseRequiredStringParam("Days"); double DaysValue; - if (!Double.TryParse(Days, out DaysValue)) + if(!Double.TryParse(Days, out DaysValue)) { throw new AutomationException("'{0}' is not a valid value for the -Days parameter", Days); } - DateTime RetainTime = DateTime.UtcNow - TimeSpan.FromDays(DaysValue); + string Depth = ParseOptionalStringParam("Depth"); + int TargetDepth; + if (!int.TryParse(Depth, out TargetDepth)) + { + TargetDepth = 0; + } - // Enumerate all the build directories - CommandUtils.LogInformation("Scanning {0}...", ReportDir); - int NumFolders = 0; - List FoldersToDelete = new List(); - foreach (DirectoryInfo BuildDirectory in new DirectoryInfo(ReportDir).EnumerateDirectories()) + DirectoryInfo ReportDirInfo = new DirectoryInfo(ReportDir); + if (!ReportDirInfo.Exists) + { + throw new AutomationException("Report directory '{0}' does not exists.", ReportDirInfo.FullName); + } + + DateTime RetainTime = DateTime.UtcNow - TimeSpan.FromDays(DaysValue); + List DirectoriesToDelete = new List(); + int DirsScanned = CleanDirectories(ReportDirInfo, RetainTime, DirectoriesToDelete, TargetDepth); + + // Delete old folders. + CommandUtils.LogInformation("Found {0} builds; {1} to delete.", DirsScanned, DirectoriesToDelete.Count); + for(int Idx = 0; Idx < DirectoriesToDelete.Count; Idx++) { try { - if(!BuildDirectory.EnumerateFiles("*", SearchOption.AllDirectories).Any(x => x.LastWriteTimeUtc > RetainTime)) - { - FoldersToDelete.Add(BuildDirectory); - } - NumFolders++; + CommandUtils.LogInformation("[{0}/{1}] Deleting {2}...", Idx + 1, DirectoriesToDelete.Count, DirectoriesToDelete[Idx].FullName); + DirectoriesToDelete[Idx].Delete(true); } catch(Exception Ex) { - CommandUtils.LogWarning("Unable to enumerate {0}: {1}", BuildDirectory.FullName, Ex.ToString()); + CommandUtils.LogWarning("Failed to delete folder; will try one file at a time: {0}", Ex); + CommandUtils.DeleteDirectory_NoExceptions(true, DirectoriesToDelete[Idx].FullName); } } - CommandUtils.LogInformation("Found {0} builds; {1} to delete.", NumFolders, FoldersToDelete.Count); + } - // Delete them all - for (int Idx = 0; Idx < FoldersToDelete.Count; Idx++) + private int CleanDirectories(DirectoryInfo Directory, DateTime RetainTime, List DirectoriesToDelete, int TargetDepth, int CurrentDepth = 0) + { + // Go deeper if we need to. + if(TargetDepth > CurrentDepth) { - try + int DirsFound = 0; + foreach(DirectoryInfo SubDirectory in Directory.EnumerateDirectories()) { - CommandUtils.LogInformation("[{0}/{1}] Deleting {2}...", Idx + 1, FoldersToDelete.Count, FoldersToDelete[Idx].FullName); - FoldersToDelete[Idx].Delete(true); + DirsFound += CleanDirectories(SubDirectory, RetainTime, DirectoriesToDelete, TargetDepth, CurrentDepth + 1); } - catch (Exception Ex) + return DirsFound; + } + else + { + CommandUtils.LogInformation("Scanning {0}...", Directory); + IEnumerable DirsToScan = Directory.EnumerateDirectories(); + foreach(DirectoryInfo BuildDirectory in DirsToScan) { - CommandUtils.LogWarning("Failed to delete folder; will try one file at a time: {0}", Ex); - CommandUtils.DeleteDirectory_NoExceptions(true, FoldersToDelete[Idx].FullName); + try + { + if(!BuildDirectory.EnumerateFiles("*", SearchOption.AllDirectories).Any(x => x.LastWriteTimeUtc > RetainTime)) + { + DirectoriesToDelete.Add(BuildDirectory); + } + } + catch(Exception Ex) + { + CommandUtils.LogWarning("Unable to enumerate {0}: {1}", BuildDirectory.FullName, Ex.ToString()); + } } + return DirsToScan.Count(); } } } diff --git a/Engine/Source/Programs/AutomationTool/Scripts/CopyBuildToStagingDirectory.Automation.cs b/Engine/Source/Programs/AutomationTool/Scripts/CopyBuildToStagingDirectory.Automation.cs index bc3a7a3dcaab..10a507ac7566 100644 --- a/Engine/Source/Programs/AutomationTool/Scripts/CopyBuildToStagingDirectory.Automation.cs +++ b/Engine/Source/Programs/AutomationTool/Scripts/CopyBuildToStagingDirectory.Automation.cs @@ -171,12 +171,13 @@ public partial class Project : CommandUtils EncryptionAndSigning.CryptoSettings CryptoSettings, string EncryptionKeyGuid, string PatchSourceContentPath, - bool bGenerateDiffPatch) + bool bGenerateDiffPatch, + bool bIsDLC) { StringBuilder CmdLine = new StringBuilder(); CmdLine.AppendFormat("-Output={0}", MakePathSafeToUseWithCommandLine(Path.ChangeExtension(PakOutputLocation.FullName, ".utoc"))); CmdLine.AppendFormat("-ContainerName={0}", ContainerName); - if (!String.IsNullOrEmpty(PatchSourceContentPath)) + if (!bIsDLC && !String.IsNullOrEmpty(PatchSourceContentPath)) { CmdLine.AppendFormat(" -PatchSource={0}", CommandUtils.MakePathSafeToUseWithCommandLine(PatchSourceContentPath)); } @@ -2406,7 +2407,8 @@ public partial class Project : CommandUtils CryptoSettings, PakParams.EncryptionKeyGuid, ContainerPatchSourcePath, - bGenerateDiffPatch)); + bGenerateDiffPatch, + Params.HasDLCName)); } Commands.Add(GetUnrealPakArguments( @@ -2491,6 +2493,8 @@ public partial class Project : CommandUtils } } + AdditionalArgs += " " + Params.AdditionalIoStoreOptions; + RunIoStore(Params, SC, IoStoreCommandsFileName, GameOpenOrderFileLocation, CookerOpenOrderFileLocation, AdditionalArgs); } @@ -2662,9 +2666,44 @@ public partial class Project : CommandUtils GlobalContainerOutputRelativeLocation = SC.StageTargetPlatform.Remap(GlobalContainerOutputRelativeLocation); FileReference GlobalContainerOutputLocation = FileReference.Combine(SC.RuntimeRootDir, GlobalContainerOutputRelativeLocation.Name); - string CommandletParams = Params.HasDLCName - ? String.Format("-DLCFile={0}", MakePathSafeToUseWithCommandLine(Params.DLCFile.FullName)) - : String.Format("-CreateGlobalContainer={0}", MakePathSafeToUseWithCommandLine(GlobalContainerOutputLocation.FullName)); + string CommandletParams = String.Empty; + + if (Params.HasDLCName) + { + CommandletParams += String.Format("-DLCFile={0}", MakePathSafeToUseWithCommandLine(Params.DLCFile.FullName)); + + DirectoryReference DLCRoot = Params.DLCFile.Directory; + string DLCName = Params.DLCFile.GetFileNameWithoutExtension(); + + //TODO: Find a better way. Create Plugin ConfigType and interate all ini files in plugin? + bool bRemapPluginContentToGame = false; + FileReference PluginConfigFile = FileReference.Combine(DLCRoot, "Config", String.Format("Default{0}.ini", DLCName)); + if (FileReference.Exists(PluginConfigFile)) + { + ConfigFile File = new ConfigFile(PluginConfigFile); + ConfigFileSection PluginSettings; + if (File.TryGetSection("PluginSettings", out PluginSettings)) + { + foreach (ConfigLine Line in PluginSettings.Lines) + { + if (Line.Key == "RemapPluginContentToGame") + { + bool.TryParse(Line.Value, out bRemapPluginContentToGame); + break; + } + } + } + } + + if (bRemapPluginContentToGame) + { + CommandletParams += " -RemapPluginContentToGame"; + } + } + else + { + CommandletParams += String.Format("-CreateGlobalContainer={0}", MakePathSafeToUseWithCommandLine(GlobalContainerOutputLocation.FullName)); + } CommandletParams += String.Format(" -CookedDirectory={0} -Commands={1}", MakePathSafeToUseWithCommandLine(SC.PlatformCookDir.ToString()), MakePathSafeToUseWithCommandLine(CommandsFileName)); if (GameOpenOrderFileLocation != null) @@ -2682,6 +2721,11 @@ public partial class Project : CommandUtils CommandletParams += String.Format(" -TargetPlatform={0}", SC.StageTargetPlatform.GetCookPlatform(Params.DedicatedServer, Params.Client)); + if (Params.HasBasedOnReleaseVersion) + { + CommandletParams += String.Format(" -BasedOnReleaseVersionPath={0}", Params.GetBasedOnReleaseVersionPath(SC, Params.Client)); + } + LogInformation("Running IoStore commandlet with arguments: {0}", CommandletParams); RunCommandlet(SC.RawProjectPath, Params.UE4Exe, "IoStore", CommandletParams); } @@ -2909,6 +2953,7 @@ public partial class Project : CommandUtils var ChunkListFilename = GetChunkPakManifestListFilename(Params, SC); List ChunkList = new List(ReadAllLines(ChunkListFilename)); + Log.TraceInformation("Reading chunk list file {0} which contains {1} entries", ChunkListFilename, ChunkList.Count); for (int Index = 0; Index < ChunkList.Count; ++Index) { @@ -2941,14 +2986,15 @@ public partial class Project : CommandUtils } } CD.Manifest = ReadPakChunkManifest(ChunkManifestFilename); + Log.TraceInformation("Reading chunk manifest {0} which contains {1} entries", ChunkManifestFilename, CD.Manifest.Count); ChunkDefinitions.Add(CD); } const string OptionalBulkDataFileExtension = ".uptnl"; - Dictionary OptionalChunks = new Dictionary(); + Dictionary OptionalChunks = new Dictionary(StringComparer.InvariantCultureIgnoreCase); ChunkDefinition DefaultChunk = ChunkDefinitions[DefaultChunkIndex]; - Dictionary> FileNameToChunks = new Dictionary>(); + Dictionary> FileNameToChunks = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); foreach (ChunkDefinition Chunk in ChunkDefinitions) { foreach (string FileName in Chunk.Manifest) @@ -2963,7 +3009,7 @@ public partial class Project : CommandUtils } } - Dictionary ChunkNameToDefinition = new Dictionary(); + Dictionary ChunkNameToDefinition = new Dictionary(StringComparer.InvariantCultureIgnoreCase); foreach (ChunkDefinition Chunk in ChunkDefinitions) { ChunkNameToDefinition.Add(Chunk.ChunkName, Chunk); diff --git a/Engine/Source/Programs/AutomationTool/Scripts/RunProjectCommand.Automation.cs b/Engine/Source/Programs/AutomationTool/Scripts/RunProjectCommand.Automation.cs index b28dbafc8646..2f603736449f 100644 --- a/Engine/Source/Programs/AutomationTool/Scripts/RunProjectCommand.Automation.cs +++ b/Engine/Source/Programs/AutomationTool/Scripts/RunProjectCommand.Automation.cs @@ -185,11 +185,7 @@ public partial class Project : CommandUtils string LookFor = "Bringing up level for play took"; bool bCommandlet = false; - if (Params.RunAutomationTest != "") - { - LookFor = "Automation Test Succeeded"; - } - else if (Params.RunAutomationTests) + if (Params.RunAutomationTest != "" || Params.RunAutomationTests) { LookFor = "Automation Test Queue Empty"; } @@ -392,11 +388,7 @@ public partial class Project : CommandUtils { LookFor = "Welcomed by server"; } - else if (Params.RunAutomationTest != "") - { - LookFor = "Automation Test Succeeded"; - } - else if (Params.RunAutomationTests) + else if (Params.RunAutomationTest != "" || Params.RunAutomationTests) { LookFor = "Automation Test Queue Empty"; } diff --git a/Engine/Source/Programs/AutomationToolLauncher/Launcher.cs b/Engine/Source/Programs/AutomationToolLauncher/Launcher.cs index f69b22be7f98..a1b12e6ded52 100644 --- a/Engine/Source/Programs/AutomationToolLauncher/Launcher.cs +++ b/Engine/Source/Programs/AutomationToolLauncher/Launcher.cs @@ -1,28 +1,39 @@ // Copyright Epic Games, Inc. All Rights Reserved. using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Reflection; using System.IO; +using System.Linq; +using System.Diagnostics; +using System.Dynamic; namespace AutomationToolLauncher { class Launcher { static int Main(string[] Arguments) + { + if (Arguments.Contains("-compile", StringComparer.OrdinalIgnoreCase)) + { + return RunInAppDomain(Arguments); + } + + return Run(Arguments); + + } + + static int RunInAppDomain(string[] Arguments) { // Create application domain setup information. - var Domaininfo = new AppDomainSetup(); + AppDomainSetup Domaininfo = new AppDomainSetup(); Domaininfo.ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); Domaininfo.ShadowCopyFiles = "true"; // Create the application domain. - var Domain = AppDomain.CreateDomain("AutomationTool", AppDomain.CurrentDomain.Evidence, Domaininfo); + AppDomain Domain = AppDomain.CreateDomain("AutomationTool", AppDomain.CurrentDomain.Evidence, Domaininfo); // Execute assembly and pass through command line - var UATExecutable = Path.Combine(Domaininfo.ApplicationBase, "AutomationTool.exe"); + string UATExecutable = Path.Combine(Domaininfo.ApplicationBase, "AutomationTool.exe"); // Default exit code in case UAT does not even start, otherwise we always return UAT's exit code. - var ExitCode = 193; + int ExitCode = 193; try { @@ -34,8 +45,39 @@ namespace AutomationToolLauncher { Console.WriteLine(Ex.Message); Console.WriteLine(Ex.StackTrace); + + // We want to terminate the launcher process regardless of any crash dialogs, threads, etc + Environment.Exit(ExitCode); } + return ExitCode; } + + static int Run(string[] Arguments) + { + + string ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + string UATExecutable = Path.Combine(ApplicationBase, "AutomationTool.exe"); + + if (!File.Exists(UATExecutable)) + { + Console.WriteLine(string.Format("AutomationTool does not exist at: {0}", UATExecutable)); + return -1; + } + + try + { + Assembly UAT = Assembly.LoadFile(UATExecutable); + Environment.Exit((int) UAT.EntryPoint.Invoke(null, new object[] { Arguments })); + } + catch (Exception Ex) + { + Console.WriteLine(Ex.Message); + Console.WriteLine(Ex.StackTrace); + } + + return -1; + + } } } diff --git a/Engine/Source/Programs/BuildAgent/BuildAgent.csproj b/Engine/Source/Programs/BuildAgent/BuildAgent.csproj index 415061d6c308..5f085ab08549 100644 --- a/Engine/Source/Programs/BuildAgent/BuildAgent.csproj +++ b/Engine/Source/Programs/BuildAgent/BuildAgent.csproj @@ -56,6 +56,7 @@ + diff --git a/Engine/Source/Programs/BuildAgent/Issues/DumpIssuesMode.cs b/Engine/Source/Programs/BuildAgent/Issues/DumpIssuesMode.cs new file mode 100644 index 000000000000..0e478877367b --- /dev/null +++ b/Engine/Source/Programs/BuildAgent/Issues/DumpIssuesMode.cs @@ -0,0 +1,83 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Web.Script.Serialization; +using Tools.DotNETCommon; + +namespace BuildAgent.Issues +{ + [ProgramMode("DumpIssues", "Updates the UGS build health system with output from completed builds")] + class DumpIssuesMode : ProgramMode + { + class IssueResponse + { + public int Id { get; set; } + public string Summary { get; set; } + public string Owner { get; set; } + public int FixChange { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? ResolvedAt { get; set; } + public DateTime? AcknowledgedAt { get; set; } + } + + [CommandLine("-Server=", Required = true)] + [Description("Url of the UGS metadata service")] + string ServerUrl = null; + + [CommandLine("-Output=", Required = true)] + [Description("Output CSV file to write")] + string OutputFile = null; + + public override int Execute() + { + HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(ServerUrl + "/api/issues?includeresolved=true"); + Request.ContentType = "application/json"; + Request.Method = "GET"; + + List Issues; + using (HttpWebResponse Response = (HttpWebResponse)Request.GetResponse()) + { + using (StreamReader ResponseReader = new StreamReader(Response.GetResponseStream(), Encoding.Default)) + { + string ResponseContent = ResponseReader.ReadToEnd(); + Issues = new JavaScriptSerializer() { MaxJsonLength = int.MaxValue }.Deserialize>(ResponseContent); + } + } + + FileReference OutputLocation = new FileReference(OutputFile); + Log.TraceInformation("Writing {0}...", OutputLocation.FullName); + + using (StreamWriter Writer = new StreamWriter(OutputLocation.FullName)) + { + Writer.WriteLine("Id,Summary,Owner,FixChange,CreatedAt,ResolvedAt,AcknowledgedAt"); + foreach(IssueResponse Issue in Issues) + { + Writer.WriteLine("{0},{1},{2},{3},{4},{5},{6}", Issue.Id, EscapeText(Issue.Summary), EscapeText(Issue.Owner), Issue.FixChange, Issue.CreatedAt, Issue.ResolvedAt, Issue.AcknowledgedAt); + } + } + + return 0; + } + + static string EscapeText(string Text) + { + if(Text == null) + { + return Text; + } + + Text = Text.Replace("\\", "\\\\"); + Text = Text.Replace(",", "\\,"); + Text = Text.Replace("'", "\\'"); + Text = Text.Replace("\"", "\\\""); + return String.Format("\"{0}\"", Text); + } + } +} diff --git a/Engine/Source/Programs/BuildAgent/Issues/Matcher.cs b/Engine/Source/Programs/BuildAgent/Issues/Matcher.cs index 8d57345fe116..404777a6e470 100644 --- a/Engine/Source/Programs/BuildAgent/Issues/Matcher.cs +++ b/Engine/Source/Programs/BuildAgent/Issues/Matcher.cs @@ -213,7 +213,13 @@ namespace BuildAgent.Issues protected string GetNormalizedFileName(string FileName, string BaseDirectory) { string NormalizedFileName = FileName.Replace('\\', '/'); - if (!String.IsNullOrEmpty(BaseDirectory)) + + const string StandardEnginePrefix = "../../../"; + if (NormalizedFileName.StartsWith(StandardEnginePrefix)) + { + NormalizedFileName = NormalizedFileName.Substring(StandardEnginePrefix.Length); + } + else if (!String.IsNullOrEmpty(BaseDirectory)) { // Normalize the expected base directory for errors in this build, and attempt to strip it from the file name string NormalizedBaseDirectory = BaseDirectory; diff --git a/Engine/Source/Programs/BuildAgent/Issues/Matchers/ContentIssueMatcher.cs b/Engine/Source/Programs/BuildAgent/Issues/Matchers/ContentIssueMatcher.cs index 09072e884e65..cb50305d7588 100644 --- a/Engine/Source/Programs/BuildAgent/Issues/Matchers/ContentIssueMatcher.cs +++ b/Engine/Source/Programs/BuildAgent/Issues/Matchers/ContentIssueMatcher.cs @@ -17,7 +17,7 @@ namespace BuildAgent.Issues.Matchers public override bool TryMatch(InputJob Job, InputJobStep JobStep, InputDiagnostic Diagnostic, List Issues) { HashSet FileNames = new HashSet(); - foreach(Match Match in Regex.Matches(Diagnostic.Message, @"^\s*Log[a-zA-Z0-9]+:\s+(?:Error:|Warning:)\s+((?:[a-zA-Z]:)?[^:]+(?:.uasset|.umap)):\s*(.*)")) + foreach(Match Match in Regex.Matches(Diagnostic.Message, @"^\s*[a-zA-Z0-9]+:\s+(?:Error:|Warning:)\s+(?:\[AssetLog\] )?((?:[a-zA-Z]:)?[^:]+(?:.uasset|.umap)):\s*(.*)")) { FileNames.Add(GetNormalizedFileName(Match.Groups[1].Value, JobStep.BaseDirectory)); } diff --git a/Engine/Source/Programs/BuildAgent/Run/LineBuffer.cs b/Engine/Source/Programs/BuildAgent/Run/LineBuffer.cs index cec686ec6bd7..1b1c70988934 100644 --- a/Engine/Source/Programs/BuildAgent/Run/LineBuffer.cs +++ b/Engine/Source/Programs/BuildAgent/Run/LineBuffer.cs @@ -75,7 +75,7 @@ namespace BuildAgent.Run NextLines.Add(ReadLine()); } NextLine = NextLines[Offset]; - return true; + return NextLine != null; } else if (Offset >= -HistoryCount) { diff --git a/Engine/Source/Programs/BuildAgent/Run/RunMode.cs b/Engine/Source/Programs/BuildAgent/Run/RunMode.cs index 5eccd5890727..7aeb61dd7f39 100644 --- a/Engine/Source/Programs/BuildAgent/Run/RunMode.cs +++ b/Engine/Source/Programs/BuildAgent/Run/RunMode.cs @@ -269,7 +269,8 @@ namespace BuildAgent.Run LineBuffer Buffer = new LineBuffer(ReadLine, 50); ReadOnlyLineBuffer ReadOnlyBuffer = new ReadOnlyLineBuffer(Buffer); - while (Buffer[0] != null) + string FirstLine; + while (Buffer.TryGetLine(0, out FirstLine)) { // Try to match an error ErrorMatch Error = null; diff --git a/Engine/Source/Programs/BuildAgent/Workspace/Common/Repository.cs b/Engine/Source/Programs/BuildAgent/Workspace/Common/Repository.cs index 0a76dc062e7c..94644a667d41 100644 --- a/Engine/Source/Programs/BuildAgent/Workspace/Common/Repository.cs +++ b/Engine/Source/Programs/BuildAgent/Workspace/Common/Repository.cs @@ -919,12 +919,8 @@ namespace BuildAgent.Workspace.Common Client.Root = WorkspaceDir.FullName; Client.Type = "partitioned"; - PerforceResponse Response = Perforce.CreateClient(Client); - if(!Response.Succeeded) - { - Perforce.DeleteClient(DeleteClientOptions.None, ClientName); - Perforce.CreateClient(Client).RequireSuccess(); - } + Perforce.DeleteClient(DeleteClientOptions.None, ClientName); + Perforce.CreateClient(Client).RequireSuccess(); Status.SetProgress("({0:0.0}s)", Timer.Elapsed.TotalSeconds); } diff --git a/Engine/Source/Programs/BuildAgent/Workspace/PopulateCacheMode.cs b/Engine/Source/Programs/BuildAgent/Workspace/PopulateCacheMode.cs index bf925d9a28bf..3addde644745 100644 --- a/Engine/Source/Programs/BuildAgent/Workspace/PopulateCacheMode.cs +++ b/Engine/Source/Programs/BuildAgent/Workspace/PopulateCacheMode.cs @@ -4,6 +4,7 @@ using BuildAgent.Workspace.Common; using System; using System.Collections.Generic; using System.ComponentModel; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -22,6 +23,10 @@ namespace BuildAgent.Workspace [Description("Filters for the files to sync, in P4 syntax (eg. /Engine/...)")] List Filters = new List(); + [CommandLine("-FilterFile=")] + [Description("A file containing a list of paths to filter when syncing, in P4 syntax (eg. /Engine/...)")] + FileReference FilterFile; + [CommandLine("-FakeSync")] [Description("Simulates the sync without actually fetching any files")] bool bFakeSync = false; @@ -29,9 +34,19 @@ namespace BuildAgent.Workspace protected override void Execute(Repository Repo) { List ExpandedFilters = ExpandFilters(Filters); + if (FilterFile != null) + { + if (!FileReference.Exists(FilterFile)) + { + throw new FileNotFoundException(string.Format("Filter file '{0}' could not be found!", FilterFile), FilterFile.FullName); + } + + ExpandedFilters.AddRange(FileReference.ReadAllLines(FilterFile)); + } List> ClientAndStreams = ParseClientAndStreams(ClientAndStreamParams); - Repo.Populate(ClientAndStreams, Filters, bFakeSync); + List DistinctFilters = ExpandedFilters.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList(); + Repo.Populate(ClientAndStreams, DistinctFilters, bFakeSync); } } } diff --git a/Engine/Source/Programs/CSVTools/CsvStats/CsvStats.cs b/Engine/Source/Programs/CSVTools/CsvStats/CsvStats.cs index 8e81090dbfa1..fc7e31f4178d 100644 --- a/Engine/Source/Programs/CSVTools/CsvStats/CsvStats.cs +++ b/Engine/Source/Programs/CSVTools/CsvStats/CsvStats.cs @@ -334,7 +334,7 @@ namespace CSVStats { localTotal += (double)samples[i]; } - return (float)(total / (double)(maxSample - minSample)); + return (float)(localTotal / (double)(maxSample - minSample)); } public void ComputeAverageAndTotal(int minSample = 0, int maxSample = -1) diff --git a/Engine/Source/Programs/DotNETCommon/BuildUtilities/UEBuildPlatformSDK.cs b/Engine/Source/Programs/DotNETCommon/BuildUtilities/UEBuildPlatformSDK.cs index 7de7d2a7158a..319c48c3aedb 100644 --- a/Engine/Source/Programs/DotNETCommon/BuildUtilities/UEBuildPlatformSDK.cs +++ b/Engine/Source/Programs/DotNETCommon/BuildUtilities/UEBuildPlatformSDK.cs @@ -367,7 +367,7 @@ namespace Tools.DotNETCommon /// Returns SDK string as required by the platform /// /// Valid SDK string - protected virtual string GetRequiredSDKString() + public virtual string GetRequiredSDKString() { return ""; } diff --git a/Engine/Source/Programs/DotNETCommon/DotNETUtilities/BinaryArchiveWriter.cs b/Engine/Source/Programs/DotNETCommon/DotNETUtilities/BinaryArchiveWriter.cs index 8f1d6b502d9d..52e4c0c5b902 100644 --- a/Engine/Source/Programs/DotNETCommon/DotNETUtilities/BinaryArchiveWriter.cs +++ b/Engine/Source/Programs/DotNETCommon/DotNETUtilities/BinaryArchiveWriter.cs @@ -424,7 +424,7 @@ namespace Tools.DotNETCommon /// The dictionary to write /// Delegate used to read a single key /// Delegate used to read a single value - public void WriteDictionary(Dictionary Dictionary, Action WriteKey, Action WriteValue) + public void WriteDictionary(IDictionary Dictionary, Action WriteKey, Action WriteValue) { if(Dictionary == null) { diff --git a/Engine/Source/Programs/DotNETCommon/DotNETUtilities/DirectoryReference.cs b/Engine/Source/Programs/DotNETCommon/DotNETUtilities/DirectoryReference.cs index 380a989737d1..469c4f554808 100644 --- a/Engine/Source/Programs/DotNETCommon/DotNETUtilities/DirectoryReference.cs +++ b/Engine/Source/Programs/DotNETCommon/DotNETUtilities/DirectoryReference.cs @@ -105,6 +105,12 @@ namespace Tools.DotNETCommon ParentLength++; } + if (ParentLength == 0 && FullName[0] == Path.DirectorySeparatorChar) + { + // we have reached the root + ParentLength = 1; + } + return new DirectoryReference(FullName.Substring(0, ParentLength), Sanitize.None); } } @@ -114,6 +120,7 @@ namespace Tools.DotNETCommon /// /// The file to get directory for /// The full directory name containing the given file + [Obsolete("Replace with call to FileReference.ParentDirectory instead.")] public static DirectoryReference GetParentDirectory(FileReference File) { int ParentLength = File.FullName.LastIndexOf(Path.DirectorySeparatorChar); @@ -121,6 +128,7 @@ namespace Tools.DotNETCommon { ParentLength++; } + return new DirectoryReference(File.FullName.Substring(0, ParentLength), Sanitize.None); } diff --git a/Engine/Source/Programs/DotNETCommon/DotNETUtilities/FileReference.cs b/Engine/Source/Programs/DotNETCommon/DotNETUtilities/FileReference.cs index 96b953a7445e..8b087f7839af 100644 --- a/Engine/Source/Programs/DotNETCommon/DotNETUtilities/FileReference.cs +++ b/Engine/Source/Programs/DotNETCommon/DotNETUtilities/FileReference.cs @@ -135,7 +135,24 @@ namespace Tools.DotNETCommon /// A new directory object representing the directory containing this object public DirectoryReference Directory { - get { return DirectoryReference.GetParentDirectory(this); } + get + { + int ParentLength = FullName.LastIndexOf(Path.DirectorySeparatorChar); + + if (ParentLength == 2 && FullName[1] == ':') + { + // windows root detected (C:) + ParentLength++; + } + + if (ParentLength == 0 && FullName[0] == Path.DirectorySeparatorChar) + { + // nix style root (/) detected + ParentLength = 1; + } + + return new DirectoryReference(FullName.Substring(0, ParentLength), DirectoryReference.Sanitize.None); + } } /// diff --git a/Engine/Source/Programs/Enterprise/Datasmith/DatasmithCADWorker/DatasmithCADWorker.Target.cs b/Engine/Source/Programs/Enterprise/Datasmith/DatasmithCADWorker/DatasmithCADWorker.Target.cs index 7bebb801421b..c454387a116a 100644 --- a/Engine/Source/Programs/Enterprise/Datasmith/DatasmithCADWorker/DatasmithCADWorker.Target.cs +++ b/Engine/Source/Programs/Enterprise/Datasmith/DatasmithCADWorker/DatasmithCADWorker.Target.cs @@ -3,7 +3,7 @@ using UnrealBuildTool; using System.Collections.Generic; -[SupportedPlatforms(UnrealPlatformClass.Desktop)] +[SupportedPlatforms("Win64")] public class DatasmithCADWorkerTarget : TargetRules { public DatasmithCADWorkerTarget(TargetInfo Target) : base(Target) diff --git a/Engine/Source/Programs/Enterprise/Datasmith/DatasmithFacadeCSharp/DatasmithFacadeCSharp.Target.cs b/Engine/Source/Programs/Enterprise/Datasmith/DatasmithFacadeCSharp/DatasmithFacadeCSharp.Target.cs index 5db084a97a59..70ac43b43ebf 100644 --- a/Engine/Source/Programs/Enterprise/Datasmith/DatasmithFacadeCSharp/DatasmithFacadeCSharp.Target.cs +++ b/Engine/Source/Programs/Enterprise/Datasmith/DatasmithFacadeCSharp/DatasmithFacadeCSharp.Target.cs @@ -4,6 +4,7 @@ using UnrealBuildTool; using System; using System.IO; +[SupportedPlatforms("Win64")] public class DatasmithFacadeCSharpTarget : TargetRules { public DatasmithFacadeCSharpTarget(TargetInfo Target) diff --git a/Engine/Source/Programs/Enterprise/Datasmith/DatasmithMaxExporter/DatasmithMax2017.Target.cs b/Engine/Source/Programs/Enterprise/Datasmith/DatasmithMaxExporter/DatasmithMax2017.Target.cs index ca41d947b6db..ff95baecee8b 100644 --- a/Engine/Source/Programs/Enterprise/Datasmith/DatasmithMaxExporter/DatasmithMax2017.Target.cs +++ b/Engine/Source/Programs/Enterprise/Datasmith/DatasmithMaxExporter/DatasmithMax2017.Target.cs @@ -2,6 +2,7 @@ using UnrealBuildTool; +[SupportedPlatforms("Win64")] public abstract class DatasmithMaxBaseTarget : TargetRules { public DatasmithMaxBaseTarget(TargetInfo Target) diff --git a/Engine/Source/Programs/Enterprise/Datasmith/DatasmithRevitExporter/DatasmithRevit2018.Target.cs b/Engine/Source/Programs/Enterprise/Datasmith/DatasmithRevitExporter/DatasmithRevit2018.Target.cs index affdbf833d56..722276cc7d22 100644 --- a/Engine/Source/Programs/Enterprise/Datasmith/DatasmithRevitExporter/DatasmithRevit2018.Target.cs +++ b/Engine/Source/Programs/Enterprise/Datasmith/DatasmithRevitExporter/DatasmithRevit2018.Target.cs @@ -3,6 +3,7 @@ using UnrealBuildTool; using System.IO; +[SupportedPlatforms("Win64")] public abstract class DatasmithRevitBaseTarget : TargetRules { public DatasmithRevitBaseTarget(TargetInfo Target) diff --git a/Engine/Source/Programs/Enterprise/Datasmith/DatasmithSketchUpExporter/DatasmithSketchUp2017.Target.cs b/Engine/Source/Programs/Enterprise/Datasmith/DatasmithSketchUpExporter/DatasmithSketchUp2017.Target.cs index 4f9038f2807f..888f60d475f1 100644 --- a/Engine/Source/Programs/Enterprise/Datasmith/DatasmithSketchUpExporter/DatasmithSketchUp2017.Target.cs +++ b/Engine/Source/Programs/Enterprise/Datasmith/DatasmithSketchUpExporter/DatasmithSketchUp2017.Target.cs @@ -2,6 +2,7 @@ using UnrealBuildTool; +[SupportedPlatforms("Win64")] public abstract class DatasmithSketchUpBaseTarget : TargetRules { public DatasmithSketchUpBaseTarget(TargetInfo Target) diff --git a/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestClustering.cpp b/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestClustering.cpp index 9a53fffcd74d..d99309dc0c18 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestClustering.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestClustering.cpp @@ -36,7 +36,7 @@ namespace GeometryCollectionTest { using namespace ChaosTest; - bool ClusterMapContains(const Chaos::TPBDRigidClustering::FClusterMap& ClusterMap, TPBDRigidParticleHandle* Key, TArray*> Elements) + bool ClusterMapContains(const Chaos::TPBDRigidClustering::FClusterMap& ClusterMap, const TPBDRigidParticleHandle* Key, TArray*> Elements) { if (ClusterMap.Num()) { @@ -618,10 +618,14 @@ namespace GeometryCollectionTest TArray*>& Collection1Handles = Collection1->PhysObject->GetSolverParticleHandles(); const auto& SovlerParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); - EXPECT_EQ(SovlerParticleHandles.Size(), 4); - EXPECT_EQ(ClusterMap.Num(), 2); - EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1Handles[2], { Collection1Handles[1],Collection1Handles[0] })); - EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1Handles[3], { Collection1Handles[2] })); + UnitTest.Solver->RegisterSimOneShotCallback([&]() + { + EXPECT_EQ(SovlerParticleHandles.Size(),4); + EXPECT_EQ(ClusterMap.Num(),2); + EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection1Handles[2],{Collection1Handles[1],Collection1Handles[0]})); + EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection1Handles[3],{Collection1Handles[2]})); + }); + UnitTest.Advance(); @@ -697,7 +701,7 @@ namespace GeometryCollectionTest Params.CollisionType = ECollisionTypeEnum::Chaos_Volumetric; Params.Simulating = true; Params.EnableClustering = true; - Params.DamageThreshold = { 50.0, 50.0, 50.0, FLT_MAX }; + Params.DamageThreshold = { 30.0, 30.0, 30.0, FLT_MAX }; TGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As>(); UnitTest.AddSimulationObject(Collection); @@ -1450,19 +1454,22 @@ namespace GeometryCollectionTest TManagedArray& Transform = DynamicCollection->Transform; TManagedArray& Transform2 = DynamicCollection2->Transform; - auto& Clustering = UnitTest.Solver->GetEvolution()->GetRigidClustering(); const auto& ClusterMap = Clustering.GetChildrenMap(); const auto& ParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); - EXPECT_EQ(ClusterMap.Num(), 2); - EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles.Handle(2)->CastToRigidParticle(), { ParticleHandles.Handle(1)->CastToRigidParticle(),ParticleHandles.Handle(0)->CastToRigidParticle() })); - EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles.Handle(5)->CastToRigidParticle(), { ParticleHandles.Handle(4)->CastToRigidParticle(),ParticleHandles.Handle(3)->CastToRigidParticle() })); + UnitTest.Solver->RegisterSimOneShotCallback([&]() + { + EXPECT_EQ(ClusterMap.Num(),2); + EXPECT_TRUE(ClusterMapContains(ClusterMap,ParticleHandles.Handle(2)->CastToRigidParticle(),{ParticleHandles.Handle(1)->CastToRigidParticle(),ParticleHandles.Handle(0)->CastToRigidParticle()})); + EXPECT_TRUE(ClusterMapContains(ClusterMap,ParticleHandles.Handle(5)->CastToRigidParticle(),{ParticleHandles.Handle(4)->CastToRigidParticle(),ParticleHandles.Handle(3)->CastToRigidParticle()})); + }); for (int Frame = 0; Frame < 100; Frame++) { UnitTest.Advance(); + if (Frame == 0) { TArray GlobalTransform; @@ -1560,9 +1567,12 @@ namespace GeometryCollectionTest const auto& ClusterMap = Clustering.GetChildrenMap(); const auto& ParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); - EXPECT_EQ(ClusterMap.Num(), 2); - EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles.Handle(2)->CastToRigidParticle(), { ParticleHandles.Handle(1)->CastToRigidParticle(),ParticleHandles.Handle(0)->CastToRigidParticle() })); - EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles.Handle(5)->CastToRigidParticle(), { ParticleHandles.Handle(4)->CastToRigidParticle(),ParticleHandles.Handle(3)->CastToRigidParticle() })); + UnitTest.Solver->RegisterSimOneShotCallback([&Clustering,&ClusterMap,&ParticleHandles]() + { + EXPECT_EQ(ClusterMap.Num(),2); + EXPECT_TRUE(ClusterMapContains(ClusterMap,ParticleHandles.Handle(2)->CastToRigidParticle(),{ParticleHandles.Handle(1)->CastToRigidParticle(),ParticleHandles.Handle(0)->CastToRigidParticle()})); + EXPECT_TRUE(ClusterMapContains(ClusterMap,ParticleHandles.Handle(5)->CastToRigidParticle(),{ParticleHandles.Handle(4)->CastToRigidParticle(),ParticleHandles.Handle(3)->CastToRigidParticle()})); + }); TArray PrevGlobalTransform; GeometryCollectionAlgo::GlobalMatrices(DynamicCollection->Transform, DynamicCollection->Parent, PrevGlobalTransform); @@ -1666,9 +1676,17 @@ namespace GeometryCollectionTest //FilterData.Word3 = 0xFFFF; //ParticleHandles.Handle(6)->ShapesArray()[0]->SetQueryData(FilterData); - EXPECT_EQ(ClusterMap.Num(), 2); - EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles.Handle(2)->CastToRigidParticle(), { ParticleHandles.Handle(1)->CastToRigidParticle(),ParticleHandles.Handle(0)->CastToRigidParticle() })); - EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles.Handle(5)->CastToRigidParticle(), { ParticleHandles.Handle(4)->CastToRigidParticle(),ParticleHandles.Handle(3)->CastToRigidParticle() })); + UnitTest.Solver->RegisterSimOneShotCallback([&]() + { + EXPECT_EQ(ClusterMap.Num(),2); + const auto& CollectionParticles = Collection->PhysObject->GetSolverParticleHandles(); + EXPECT_EQ(CollectionParticles.Num(),3); + EXPECT_TRUE(ClusterMapContains(ClusterMap,CollectionParticles[2],{CollectionParticles[1],CollectionParticles[0]})); + + const auto& CollectionParticles2 = Collection2->PhysObject->GetSolverParticleHandles(); + EXPECT_EQ(CollectionParticles2.Num(),3); + EXPECT_TRUE(ClusterMapContains(ClusterMap,CollectionParticles2[2],{CollectionParticles2[1],CollectionParticles2[0]})); + }); for (int Frame = 0; Frame < 100; Frame++) { @@ -1680,8 +1698,15 @@ namespace GeometryCollectionTest TArray GlobalTransform2; GeometryCollectionAlgo::GlobalMatrices(DynamicCollection2->Transform, DynamicCollection2->Parent, GlobalTransform2); + const auto& CollectionParticles = Collection->PhysObject->GetSolverParticleHandles(); + EXPECT_EQ(CollectionParticles.Num(),3); + + const auto& CollectionParticles2 = Collection2->PhysObject->GetSolverParticleHandles(); + + const auto* Root = CollectionParticles[0]->ClusterIds().Id; + EXPECT_EQ(ClusterMap.Num(), 1); - EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles.Handle(7)->CastToRigidParticle(), { ParticleHandles.Handle(1)->CastToRigidParticle(),ParticleHandles.Handle(0)->CastToRigidParticle(),ParticleHandles.Handle(3)->CastToRigidParticle(),ParticleHandles.Handle(4)->CastToRigidParticle() })); + EXPECT_TRUE(ClusterMapContains(ClusterMap, Root, { CollectionParticles[0],CollectionParticles[1], CollectionParticles2[0], CollectionParticles2[1] })); EXPECT_TRUE(DynamicCollection->Parent[0] == INDEX_NONE); EXPECT_TRUE(DynamicCollection->Parent[1] == INDEX_NONE); @@ -1752,10 +1777,14 @@ namespace GeometryCollectionTest TArray*>& Collection2Handles = Collection2->PhysObject->GetSolverParticleHandles(); const auto& SovlerParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); - EXPECT_EQ(SovlerParticleHandles.Size(), 6); - EXPECT_EQ(ClusterMap.Num(), 2); - EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1Handles[2], { Collection1Handles[1],Collection1Handles[0]})); - EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection2Handles[2], { Collection2Handles[1],Collection2Handles[0]})); + + UnitTest.Solver->RegisterSimOneShotCallback([&]() + { + EXPECT_EQ(SovlerParticleHandles.Size(), 6); + EXPECT_EQ(ClusterMap.Num(),2); + EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection1Handles[2],{Collection1Handles[1],Collection1Handles[0]})); + EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection2Handles[2],{Collection2Handles[1],Collection2Handles[0]})); + }); UnitTest.Advance(); @@ -1845,10 +1874,13 @@ namespace GeometryCollectionTest TArray*>& Collection2Handles = Collection2->PhysObject->GetSolverParticleHandles(); const auto& SovlerParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); - EXPECT_EQ(SovlerParticleHandles.Size(), 6); - EXPECT_EQ(ClusterMap.Num(), 2); - EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1Handles[2], { Collection1Handles[1],Collection1Handles[0] })); - EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection2Handles[2], { Collection2Handles[1],Collection2Handles[0] })); + UnitTest.Solver->RegisterSimOneShotCallback([&]() + { + EXPECT_EQ(SovlerParticleHandles.Size(),6); + EXPECT_EQ(ClusterMap.Num(),2); + EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection1Handles[2],{Collection1Handles[1],Collection1Handles[0]})); + EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection2Handles[2],{Collection2Handles[1],Collection2Handles[0]})); + }); UnitTest.Advance(); @@ -1928,10 +1960,13 @@ namespace GeometryCollectionTest TArray*>& Collection1Handles = Collection1->PhysObject->GetSolverParticleHandles(); const auto& SovlerParticleHandles = UnitTest.Solver->GetParticles().GetParticleHandles(); - EXPECT_EQ(SovlerParticleHandles.Size(), 4); - EXPECT_EQ(ClusterMap.Num(), 2); - EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1Handles[2], { Collection1Handles[1],Collection1Handles[0] })); - EXPECT_TRUE(ClusterMapContains(ClusterMap, Collection1Handles[3], { Collection1Handles[2] })); + UnitTest.Solver->RegisterSimOneShotCallback([&]() + { + EXPECT_EQ(SovlerParticleHandles.Size(),4); + EXPECT_EQ(ClusterMap.Num(),2); + EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection1Handles[2],{Collection1Handles[1],Collection1Handles[0]})); + EXPECT_TRUE(ClusterMapContains(ClusterMap,Collection1Handles[3],{Collection1Handles[2]})); + }); UnitTest.Advance(); @@ -2012,9 +2047,12 @@ namespace GeometryCollectionTest const FClustering::FClusterMap& ClusterMap = Clustering.GetChildrenMap(); const Chaos::TArrayCollectionArray& ClusterIdsArray = Clustering.GetClusterIdsArray(); - EXPECT_EQ(ClusterMap.Num(), 2); - EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[2], { ParticleHandles[0], ParticleHandles[1] })); - EXPECT_TRUE(ClusterMapContains(ClusterMap, ParticleHandles[4], { ParticleHandles[2] })); + UnitTest.Solver->RegisterSimOneShotCallback([&]() + { + EXPECT_EQ(ClusterMap.Num(),2); + EXPECT_TRUE(ClusterMapContains(ClusterMap,ParticleHandles[2],{ParticleHandles[0],ParticleHandles[1]})); + EXPECT_TRUE(ClusterMapContains(ClusterMap,ParticleHandles[4],{ParticleHandles[2]})); + }); // Test releasing a specific unioned cluster // We end up with the following cluster tree diff --git a/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestCollisionResolution.cpp b/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestCollisionResolution.cpp index 5492d5b7bc4e..cdfef30f8d10 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestCollisionResolution.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestCollisionResolution.cpp @@ -52,13 +52,17 @@ namespace GeometryCollectionTest { using Traits = TypeParam; TFramework UnitTest; + const FReal Radius = 100.0f; // cm TGeometryCollectionWrapper* Collection = nullptr; { - FVector GlobalTranslation(0, 0, 10); FQuat GlobalRotation = FQuat::MakeFromEuler(FVector(0)); + + FVector GlobalTranslation(0, 0, Radius + 10); FQuat GlobalRotation = FQuat::MakeFromEuler(FVector(0)); CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.EnableClustering = false; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Sphere; Params.SimplicialType = ESimplicialType::Chaos_Simplicial_Sphere; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.RootTransform = FTransform(GlobalRotation, GlobalTranslation); Params.NestedTransforms = { FTransform::Identity, FTransform::Identity, FTransform::Identity }; + FVector Scale(Radius); + Params.GeomTransform.SetScale3D(Scale); // Sphere radius Collection = TNewSimulationObject::Init(Params)->template As>(); EXPECT_EQ(Collection->DynamicCollection->Parent[0], 1); // is a child of index one EXPECT_TRUE(Collection->DynamicCollection->MassToLocal[0].Equals(FTransform::Identity)); // we are not testing MassToLocal in this test @@ -70,7 +74,7 @@ namespace GeometryCollectionTest UnitTest.AddSimulationObject(Floor); UnitTest.Initialize(); - for (int i = 0; i < 10000; i++) + for (int i = 0; i < 1000; i++) { UnitTest.Advance(); } @@ -81,10 +85,10 @@ namespace GeometryCollectionTest EXPECT_TRUE(UnitTest.Solver->GetParticles().GetGeometryCollectionParticles().CollisionParticles(0)!=nullptr); EXPECT_TRUE(Collection->DynamicCollection->Simplicials[0]->Size() == UnitTest.Solver->GetParticles().GetGeometryCollectionParticles().CollisionParticles(0)->Size()); EXPECT_TRUE(Collection->DynamicCollection->Simplicials[0]->Size() != 0); - int32 Size = Collection->DynamicCollection->Simplicials[0]->Size(); - EXPECT_LT(FMath::Abs(Collection->RestCollection->Transform[0].GetTranslation().Z) - 10.f, KINDA_SMALL_NUMBER); - EXPECT_LT(FMath::Abs(Collection->DynamicCollection->Transform[0].GetTranslation().Z - 1.0), 0.1); // ball settles close to 1.0 + EXPECT_LT(FMath::Abs(Collection->RestCollection->Transform[0].GetTranslation().Z - (Radius + 10.f)), KINDA_SMALL_NUMBER); + // ball settles within 10% of radius (The ball will sink deeper than expected due to contact position averaging within CullDistance) + EXPECT_LT(FMath::Abs(Collection->DynamicCollection->Transform[0].GetTranslation().Z - Radius), Radius * 0.1f); } } @@ -328,7 +332,7 @@ namespace GeometryCollectionTest EXPECT_LT(FMath::Abs(Collection->RestCollection->Transform[0].GetTranslation().Z) - 10.f, KINDA_SMALL_NUMBER); EXPECT_TRUE(FMath::Abs(Collection->DynamicCollection->Transform[0].GetTranslation().X) < 0.001); // No deflection EXPECT_TRUE(FMath::Abs(Collection->DynamicCollection->Transform[0].GetTranslation().Y) < 0.001); // No deflection - EXPECT_LT(Collection->DynamicCollection->Transform[0].GetTranslation().Z, 1.1f); // ball fell + EXPECT_LT(Collection->DynamicCollection->Transform[0].GetTranslation().Z, 2.1f); // ball fell } } @@ -343,7 +347,8 @@ namespace GeometryCollectionTest CreationParameters Params; Params.EnableClustering = false; - FVector Scale(1.f); + FReal Radius = 100.0f; + FVector Scale(Radius); Params.GeomTransform.SetScale3D(Scale); // Sphere radius Params.EnableClustering = false; @@ -351,7 +356,7 @@ namespace GeometryCollectionTest Params.SimplicialType = ESimplicialType::Chaos_Simplicial_Sphere; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Sphere; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; - Params.RootTransform =FTransform(FQuat::MakeFromEuler(FVector(0)), FVector(0, 0, 3.0)); + Params.RootTransform =FTransform(FQuat::MakeFromEuler(FVector(0)), FVector(0, 0, 2.0f * Radius + 1.0)); TGeometryCollectionWrapper* SimplicialSphereCollection = TNewSimulationObject::Init(Params)->template As>(); UnitTest.AddSimulationObject(SimplicialSphereCollection); @@ -376,7 +381,7 @@ namespace GeometryCollectionTest } UnitTest.Initialize(); - EXPECT_EQ(SimplicialSphereCollection->DynamicCollection->Transform[0].GetTranslation().Z, ImplicitSphereCollection->DynamicCollection->Transform[0].GetTranslation().Z + 3); + EXPECT_EQ(SimplicialSphereCollection->DynamicCollection->Transform[0].GetTranslation().Z, ImplicitSphereCollection->DynamicCollection->Transform[0].GetTranslation().Z + 2.0f * Radius + 1.0f); const FVector FirstX = SimplicialSphereCollection->DynamicCollection->Transform[0].GetTranslation(); FVector PrevX = FirstX; @@ -395,7 +400,7 @@ namespace GeometryCollectionTest // We expect the simplical sphere to drop by 0.1 in Z and come to rest // on top of the implicit sphere. const FVector& CurrX = SimplicialSphereCollection->DynamicCollection->Transform[0].GetTranslation(); - EXPECT_LE(FMath::Abs(CurrX.Z - 2.0f), 0.1); + EXPECT_LE(FMath::Abs(CurrX.Z - 2.0f * Radius), 0.1 * Radius); } } @@ -410,8 +415,8 @@ namespace GeometryCollectionTest CreationParameters Params; Params.EnableClustering = false; - - FVector Scale(1.f, 1.f, 1.f); + FReal Length = 100.0f; + FVector Scale(Length, Length, Length); Params.GeomTransform.SetScale3D(Scale); // Box dimensions Params.EnableClustering = false; @@ -420,7 +425,7 @@ namespace GeometryCollectionTest Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; - Params.RootTransform = FTransform(FQuat::MakeFromEuler(FVector(0)), FVector(0, 0, 3.0)); + Params.RootTransform = FTransform(FQuat::MakeFromEuler(FVector(0)), FVector(0, 0, Length + 2.0f)); TGeometryCollectionWrapper* BoxCollection0 = TNewSimulationObject::Init(Params)->template As>(); UnitTest.AddSimulationObject(BoxCollection0); @@ -454,7 +459,7 @@ namespace GeometryCollectionTest UnitTest.Initialize(); EXPECT_EQ( BoxCollection0->DynamicCollection->Transform[0].GetTranslation().Z, - BoxCollection1->DynamicCollection->Transform[0].GetTranslation().Z + 3); + BoxCollection1->DynamicCollection->Transform[0].GetTranslation().Z + Length + 2.0f); const FVector FirstX = BoxCollection0->DynamicCollection->Transform[0].GetTranslation(); FVector PrevX = FirstX; @@ -471,10 +476,10 @@ namespace GeometryCollectionTest } { - // We expect the simplical sphere to drop by 0.1 in Z and come to rest - // on top of the implicit sphere. + // We expect the simplical cube to drop in Z direction and come to rest + // on top of the implicit cube. const FVector& CurrX = BoxCollection0->DynamicCollection->Transform[0].GetTranslation(); - EXPECT_LE(CurrX.Z - 2.0, 0.2); // Relative large fudge factor accounts for aliasing? + EXPECT_LE(FMath::Abs(CurrX.Z - Length), 0.2 * Length); // Relative large fudge factor accounts for spatial aliasing and contact location averaging. } } @@ -487,15 +492,19 @@ namespace GeometryCollectionTest using Traits = TypeParam; TFramework UnitTest; + FReal Scale = 100.0f; + TGeometryCollectionWrapper* Collection = nullptr; { - FVector GlobalTranslation(0, 0, 10); FQuat GlobalRotation = FQuat::MakeFromEuler(FVector(0)); + FVector GlobalTranslation(0, 0, Scale + 10); FQuat GlobalRotation = FQuat::MakeFromEuler(FVector(0)); CreationParameters Params; Params.DynamicState = EObjectStateTypeEnum::Chaos_Object_Dynamic; Params.EnableClustering = false; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_LevelSet; Params.SimplicialType = ESimplicialType::Chaos_Simplicial_Tetrahedron; Params.CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; Params.GeomTransform = FTransform(GlobalRotation, GlobalTranslation); + FVector TetraHedronScale(Scale); + Params.GeomTransform.SetScale3D(TetraHedronScale); // Tetrahedron dimensions Collection = TNewSimulationObject::Init(Params)->template As>(); EXPECT_EQ(Collection->DynamicCollection->Parent[0], -1); // is a child of index one - EXPECT_NEAR((Collection->DynamicCollection->MassToLocal[0].GetTranslation()-FVector(0,0,10)).Size(),0,KINDA_SMALL_NUMBER); + EXPECT_NEAR((Collection->DynamicCollection->MassToLocal[0].GetTranslation()-FVector(0,0,Scale + 10)).Size(),0,KINDA_SMALL_NUMBER); UnitTest.AddSimulationObject(Collection); } @@ -510,11 +519,11 @@ namespace GeometryCollectionTest UnitTest.Advance(); } { - // validate the ball collides and moved away from the static ball + // validate the tetahedron collides and moved away from the static floor EXPECT_EQ(Collection->RestCollection->Transform[0].GetTranslation().Z, 0.f); EXPECT_NEAR(FMath::Abs(Collection->DynamicCollection->Transform[0].GetTranslation().X), 0.f, 0.01); EXPECT_NEAR(FMath::Abs(Collection->DynamicCollection->Transform[0].GetTranslation().Y), 0.f, 0.01); - EXPECT_NEAR(Collection->DynamicCollection->Transform[0].GetTranslation().Z, -9.f, 0.1); + EXPECT_NEAR(Collection->DynamicCollection->Transform[0].GetTranslation().Z, -10.f, 0.1); } } diff --git a/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestFramework.cpp b/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestFramework.cpp index aa3c87f8d351..b2732d8fc249 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestFramework.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestFramework.cpp @@ -277,11 +277,11 @@ namespace GeometryCollectionTest { if (TGeometryCollectionWrapper* GCW = Object->As>()) { - delete GCW->PhysObject; + Solver->UnregisterObject(GCW->PhysObject); } else if (RigidBodyWrapper* BCW = Object->As()) { - delete BCW->Particle; + Solver->UnregisterObject(BCW->Particle); } delete Object; } diff --git a/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestSimulation.cpp b/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestSimulation.cpp index 477247cbf472..6b6434ba2753 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestSimulation.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestSimulation.cpp @@ -50,7 +50,9 @@ namespace GeometryCollectionTest TYPED_TEST(AllTraits, GeometryCollection_RigidBodies_SingleBodyCollidingWithGroundPlane) { using Traits = TypeParam; + FReal Scale = 100.0f; CreationParameters Params; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.SimplicialType = ESimplicialType::Chaos_Simplicial_Box; + FVector BoxScale(Scale); Params.GeomTransform.SetScale3D(BoxScale); // Box dimensions TGeometryCollectionWrapper* Collection = TNewSimulationObject::Init(Params)->template As>(); RigidBodyWrapper* Floor = TNewSimulationObject::Init()->template As(); @@ -63,7 +65,7 @@ namespace GeometryCollectionTest { EXPECT_LT(FMath::Abs(Collection->RestCollection->Transform[0].GetTranslation().Z), SMALL_THRESHOLD); EXPECT_EQ(Collection->DynamicCollection->Transform.Num(), 1); - EXPECT_LT(FMath::Abs(Collection->DynamicCollection->Transform[0].GetTranslation().Z - 1.0), SMALL_THRESHOLD); + EXPECT_LT(FMath::Abs(Collection->DynamicCollection->Transform[0].GetTranslation().Z - Scale), SMALL_THRESHOLD); } } @@ -98,7 +100,7 @@ namespace GeometryCollectionTest TYPED_TEST(AllTraits, GeometryCollection_RigidBodies_SingleCubeIntersectingWithSolverFloor) { using Traits = TypeParam; - FVector Scale(2.f); + FVector Scale(100.0f); CreationParameters Params; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box; Params.SimplicialType = ESimplicialType::Chaos_Simplicial_Box; Params.GeomTransform.SetScale3D(Scale); // Sphere radius @@ -114,7 +116,7 @@ namespace GeometryCollectionTest { EXPECT_LT(FMath::Abs(Collection->RestCollection->Transform[0].GetTranslation().Z), SMALL_THRESHOLD); EXPECT_EQ(Collection->DynamicCollection->Transform.Num(), 1); - EXPECT_LT(FMath::Abs(Collection->DynamicCollection->Transform[0].GetTranslation().Z - Scale[2]), SMALL_THRESHOLD); + EXPECT_LT(FMath::Abs(Collection->DynamicCollection->Transform[0].GetTranslation().Z - Scale[0]), SMALL_THRESHOLD); } } diff --git a/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestSimulationField.cpp b/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestSimulationField.cpp index d461fe7a7015..ccac59c4475f 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestSimulationField.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestSimulationField.cpp @@ -60,8 +60,11 @@ namespace GeometryCollectionTest UnitTest.Initialize(); - EXPECT_EQ(CollectionOther->DynamicCollection->DynamicState[0], (int32)EObjectStateTypeEnum::Chaos_Object_Kinematic); - EXPECT_EQ(Collection->DynamicCollection->DynamicState[0], (int32)EObjectStateTypeEnum::Chaos_Object_Dynamic); + UnitTest.Solver->RegisterSimOneShotCallback([&]() + { + EXPECT_EQ(CollectionOther->DynamicCollection->DynamicState[0],(int32)EObjectStateTypeEnum::Chaos_Object_Kinematic); + EXPECT_EQ(Collection->DynamicCollection->DynamicState[0],(int32)EObjectStateTypeEnum::Chaos_Object_Dynamic); + }); } TYPED_TEST(AllTraits, GeometryCollection_RigidBodies_Field_KinematicActivationOnProxyDuringUpdate) diff --git a/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestSimulationSolver.cpp b/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestSimulationSolver.cpp index 01b5a8d54dea..ff22a5182ea2 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestSimulationSolver.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/GeometryCollection/GeometryCollectionTestSimulationSolver.cpp @@ -101,24 +101,30 @@ using namespace ChaosTest; UnitTest.AddSimulationObject(Collection); UnitTest.Initialize(); - TManagedArray& SimulationType = Collection->DynamicCollection->SimulationType; - EXPECT_EQ(SimulationType[0], FGeometryCollection::ESimulationTypes::FST_Clustered); - EXPECT_EQ(SimulationType[1], FGeometryCollection::ESimulationTypes::FST_Clustered); - EXPECT_EQ(SimulationType[2], FGeometryCollection::ESimulationTypes::FST_Rigid); + FVector StartingClusterPosition; + TManagedArray* Transform; + float StartingRigidDistance; - TManagedArray& Parent = Collection->DynamicCollection->Parent; - EXPECT_EQ(Parent[0], 2); - EXPECT_EQ(Parent[1], 2); - EXPECT_EQ(Parent[2], -1); + UnitTest.Solver->RegisterSimOneShotCallback([&]() + { + TManagedArray& SimulationType = Collection->DynamicCollection->SimulationType; + EXPECT_EQ(SimulationType[0],FGeometryCollection::ESimulationTypes::FST_Clustered); + EXPECT_EQ(SimulationType[1],FGeometryCollection::ESimulationTypes::FST_Clustered); + EXPECT_EQ(SimulationType[2],FGeometryCollection::ESimulationTypes::FST_Rigid); - // Set the one cluster to disabled - Collection->PhysObject->GetSolverClusterHandles()[0]->SetDisabled(true); + TManagedArray& Parent = Collection->DynamicCollection->Parent; + EXPECT_EQ(Parent[0],2); + EXPECT_EQ(Parent[1],2); + EXPECT_EQ(Parent[2],-1); - TManagedArray& Transform = Collection->DynamicCollection->Transform; - float StartingRigidDistance = (Transform[1].GetTranslation() - Transform[0].GetTranslation()).Size(); - EXPECT_LT(StartingRigidDistance - 20.0f, SMALL_THRESHOLD); + // Set the one cluster to disabled + Collection->PhysObject->GetSolverClusterHandles()[0]->SetDisabled(true); - FVector StartingClusterPosition = Transform[2].GetTranslation(); + Transform = &Collection->DynamicCollection->Transform; + StartingRigidDistance = ((*Transform)[1].GetTranslation() - (*Transform)[0].GetTranslation()).Size(); + EXPECT_LT(StartingRigidDistance - 20.0f,SMALL_THRESHOLD); + StartingClusterPosition = (*Transform)[2].GetTranslation(); + }); float CurrentRigidDistance = 0.f; @@ -127,11 +133,11 @@ using namespace ChaosTest; UnitTest.Advance(); // Distance between gc cubes remains the same - CurrentRigidDistance = (Transform[1].GetTranslation() - Transform[0].GetTranslation()).Size(); + CurrentRigidDistance = ((*Transform)[1].GetTranslation() - (*Transform)[0].GetTranslation()).Size(); EXPECT_LT(StartingRigidDistance-CurrentRigidDistance, SMALL_THRESHOLD); // Clustered particle doesn't move - EXPECT_LT((StartingClusterPosition - Transform[2].GetTranslation()).Size(), SMALL_THRESHOLD); + EXPECT_LT((StartingClusterPosition - (*Transform)[2].GetTranslation()).Size(), SMALL_THRESHOLD); } } @@ -363,32 +369,36 @@ using namespace ChaosTest; Collection->PhysObject->SetCollisionParticlesPerObjectFraction(1.0); UnitTest.Initialize(); - - // UnitTest.Advance(); - + TArray*> ParticleHandles; float TestMass = 7.0f; - // setup breaking filter - FSolverBreakingFilterSettings BreakingFilterSettings; - BreakingFilterSettings.FilterEnabled = true; - BreakingFilterSettings.MinMass = TestMass; - BreakingFilterSettings.MinSpeed = 0; - BreakingFilterSettings.MinVolume = 0; + UnitTest.Solver->RegisterSimOneShotCallback([&]() + { + + + // setup breaking filter + FSolverBreakingFilterSettings BreakingFilterSettings; + BreakingFilterSettings.FilterEnabled = true; + BreakingFilterSettings.MinMass = TestMass; + BreakingFilterSettings.MinSpeed = 0; + BreakingFilterSettings.MinVolume = 0; + + UnitTest.Solver->SetGenerateBreakingData(true); + UnitTest.Solver->SetBreakingFilterSettings(BreakingFilterSettings); + + ParticleHandles = Collection->PhysObject->GetSolverParticleHandles(); + + ParticleHandles[0]->SetM(TestMass + 1.0f); + ParticleHandles[1]->SetM(TestMass - 1.0f); + ParticleHandles[2]->SetM(TestMass - 2.0f); + ParticleHandles[3]->SetM(TestMass + 2.0f); + }); - UnitTest.Solver->SetGenerateBreakingData(true); - UnitTest.Solver->SetBreakingFilterSettings(BreakingFilterSettings); TEventHarvester Events(UnitTest.Solver); - TArray*>& ParticleHandles = Collection->PhysObject->GetSolverParticleHandles(); - - ParticleHandles[0]->SetM(TestMass + 1.0f); - ParticleHandles[1]->SetM(TestMass - 1.0f); - ParticleHandles[2]->SetM(TestMass - 2.0f); - ParticleHandles[3]->SetM(TestMass + 2.0f); - bool Impact = false; - for (int Loop=0; Loop<50; Loop++) + for(int Loop=0; Loop<50; Loop++) { // Events data on physics thread is appended until the game thread has had a chance to Tick & read it UnitTest.Advance(); @@ -396,19 +406,19 @@ using namespace ChaosTest; const auto& AllBreakingsArray = Events.BreakingEventData.BreakingData.AllBreakingsArray; Impact = (AllBreakingsArray.Num() > 0); - if (Impact) + if(Impact) { - EXPECT_EQ(ParticleHandles[0]->Disabled(), false); // piece1 active 6 mass - EXPECT_EQ(ParticleHandles[1]->Disabled(), false); // piece2 active 0.5 mass - EXPECT_EQ(ParticleHandles[2]->Disabled(), false); // piece3 active 0.5 mass - EXPECT_EQ(ParticleHandles[3]->Disabled(), false); // cluster active 7 mass - EXPECT_EQ(ParticleHandles[4]->Disabled(), true); // cluster + EXPECT_EQ(ParticleHandles[0]->Disabled(),false); // piece1 active 6 mass + EXPECT_EQ(ParticleHandles[1]->Disabled(),false); // piece2 active 0.5 mass + EXPECT_EQ(ParticleHandles[2]->Disabled(),false); // piece3 active 0.5 mass + EXPECT_EQ(ParticleHandles[3]->Disabled(),false); // cluster active 7 mass + EXPECT_EQ(ParticleHandles[4]->Disabled(),true); // cluster // breaking data - EXPECT_EQ(AllBreakingsArray.Num(), 2); // 2 pieces filtered out of 4 + EXPECT_EQ(AllBreakingsArray.Num(),2); // 2 pieces filtered out of 4 - EXPECT_LT(AllBreakingsArray[0].Mass - (TestMass + 2.0f), SMALL_THRESHOLD); - EXPECT_LT(AllBreakingsArray[1].Mass - (TestMass + 1.0f), SMALL_THRESHOLD); + EXPECT_LT(AllBreakingsArray[0].Mass - (TestMass + 2.0f),SMALL_THRESHOLD); + EXPECT_LT(AllBreakingsArray[1].Mass - (TestMass + 1.0f),SMALL_THRESHOLD); break; } diff --git a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestBroadphase.cpp b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestBroadphase.cpp index 98f97d515f15..bf581339f1aa 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestBroadphase.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestBroadphase.cpp @@ -682,103 +682,6 @@ namespace ChaosTest } template void BroadphaseCollectionTest(); - template - void TestPendingSpatialDataHandlePointerConflict() - { - // We are testing an edge case in the way we queue updates to our acceleration structures. - // Particle A is deleted and is pending removal from accel structure - // Particle B is allocated, coincidentally having the same handle pointer as Particle A - // Particle B is pending an insertion into structure - // Particle B is deleted - // Queue is flushed, we are testing that Particle A was removed successfully. - - // If this test fails, it means Particle B is probably stomping on data related to Particle A's removal. - - - TPBDRigidsSOAs Particles; - TArray*> ParticleHandles = Particles.CreateDynamicParticles(1); - - auto Particle = ParticleHandles[0]; - Particle->GTGeometryParticle() = TGeometryParticle::CreateParticle().Release(); - Particle->X() = TVector(0, 0, 0); - - THandleArray PhysicalMaterials; - TEvolution Evolution(Particles, PhysicalMaterials); - - // Flush spatial acceleration structures to put particle into structure. - Evolution.FlushSpatialAcceleration(); - - // Confirm particle is returned via query. - { - auto SpatialAccelerationCollection = Evolution.GetSpatialAcceleration(); - TArray SpatialIndices = SpatialAccelerationCollection->GetAllSpatialIndices(); - TAABB QueryBounds(FVec3(-1, -1, -1), FVec3(1, 1, 1)); - uint32 HitNum = 0; - for (FSpatialAccelerationIdx SpatialIdx : SpatialIndices) - { - auto SpatialAcceleration = SpatialAccelerationCollection->GetSubstructure(SpatialIdx); - TArray> Hits = SpatialAcceleration->FindAllIntersections(QueryBounds); - HitNum += Hits.Num(); - } - - EXPECT_EQ(HitNum, 1); - } - - - // Removal from accel structure is now pending (not completely applied until flush) - Evolution.RemoveParticleFromAccelerationStructure(*Particle); - - - // Assign new game thread particle to handle (this simulates Particle B allocation, with reused Handle pointer) - Particle->GTGeometryParticle() = TGeometryParticle::CreateParticle().Release(); - - // Remove new Particle from accel structure - Evolution.RemoveParticleFromAccelerationStructure(*Particle); - - // Flush changes, this should remove first particle successfully. - Evolution.FlushSpatialAcceleration(); - - // Confirm particle is not in internal structure by performing same query. - { - auto SpatialAccelerationCollection = Evolution.GetSpatialAcceleration(); - TArray SpatialIndices = SpatialAccelerationCollection->GetAllSpatialIndices(); - TAABB QueryBounds(FVec3(-1, -1, -1), FVec3(1, 1, 1)); - uint32 HitNum = 0; - for (FSpatialAccelerationIdx SpatialIdx : SpatialIndices) - { - auto SpatialAcceleration = SpatialAccelerationCollection->GetSubstructure(SpatialIdx); - TArray> Hits = SpatialAcceleration->FindAllIntersections(QueryBounds); - HitNum += Hits.Num(); - } - - EXPECT_EQ(HitNum, 0); - } - - // Confirm particle is not in external structure by swapping and testing. - { - using AABBTreeType = TAABBTree, TAABBTreeLeafArray, FReal>, FReal>; - TUniquePtr, FReal, 3>> ExternalStructure = TUniquePtr, FReal, 3>>(new TSpatialAccelerationCollection()); - Evolution.UpdateExternalAccelerationStructure(ExternalStructure); - - ISpatialAccelerationCollection, FReal, 3>& SpatialAccelerationCollection = *ExternalStructure.Get(); - TArray SpatialIndices = SpatialAccelerationCollection.GetAllSpatialIndices(); - TAABB QueryBounds(FVec3(-1, -1, -1), FVec3(1, 1, 1)); - uint32 HitNum = 0; - for (FSpatialAccelerationIdx SpatialIdx : SpatialIndices) - { - auto SpatialAcceleration = SpatialAccelerationCollection.GetSubstructure(SpatialIdx); - TArray> Hits = SpatialAcceleration->FindAllIntersections(QueryBounds); - HitNum += Hits.Num(); - } - - EXPECT_EQ(HitNum, 0); - } - } - - TYPED_TEST(AllEvolutions, TestPendingSpatialDataHandlePointerConflict) - { - TestPendingSpatialDataHandlePointerConflict(); - } template void SpatialAccelerationDirtyAndGlobalQueryStrestTest() diff --git a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestCollisions.cpp b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestCollisions.cpp index b585b4db9250..02efd0c56d1e 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestCollisions.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestCollisions.cpp @@ -395,10 +395,8 @@ namespace ChaosTest { RESET_PQ(Box); { INVARIANT_XR_START(Box); - INVARIANT_VW_START(Box); Collisions.ApplyPushOut(Dt, { Collisions.GetConstraintHandle(0) }, TSet*>(), 0, 1); INVARIANT_XR_END(Box); - INVARIANT_VW_END(Box); } } diff --git a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestDataMarshalling.cpp b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestDataMarshalling.cpp index 92c644d90d6a..5e776af3565a 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestDataMarshalling.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestDataMarshalling.cpp @@ -91,4 +91,77 @@ namespace ChaosTest EXPECT_EQ(BuffersSeen.Num(),Step == 0 ? 2 : 3); //we should only ever use three buffers } } + + TYPED_TEST(AllTraits, DataMarshalling_Callbacks) + { + auto* Solver = FChaosSolversModule::GetModule()->CreateSolver(nullptr, EThreadingMode::SingleThread); + Solver->SetEnabled(true); + + int Count = 0; + float Time = 0; + FSimCallbackHandle* Callback = &Solver->RegisterSimCallback([&Count, &Time](const TArray& Data) + { + EXPECT_EQ(Data.Num(),1); + EXPECT_EQ(Data[0]->Data.Int, Count); + ++Count; + EXPECT_EQ(Time,Data[0]->GetStartTime()); + }); + + const float Dt = 1/30.f; + + for(int Step = 0; Step < 10; ++Step) + { + Solver->FindOrCreateCallbackProducerData(*Callback).Data.Int = Step; + Solver->AdvanceAndDispatch_External(Dt); + Time += Dt; + + Solver->BufferPhysicsResults(); + Solver->FlipBuffers(); + } + + EXPECT_EQ(Count,10); + + Solver->UnregisterSimCallback(*Callback); + + for(int Step = 0; Step < 10; ++Step) + { + Solver->AdvanceAndDispatch_External(Dt); + Time += Dt; + + Solver->BufferPhysicsResults(); + Solver->FlipBuffers(); + } + + EXPECT_EQ(Count,10); + } + + TYPED_TEST(AllTraits,DataMarshalling_OneShotCallbacks) + { + auto* Solver = FChaosSolversModule::GetModule()->CreateSolver(nullptr,EThreadingMode::SingleThread); + Solver->SetEnabled(true); + + int Count = 0; + Solver->RegisterSimOneShotCallback([&Count]() + { + EXPECT_EQ(Count,0); + ++Count; + }); + + for(int Step = 0; Step < 10; ++Step) + { + Solver->RegisterSimOneShotCallback([Step, &Count]() + { + EXPECT_EQ(Count,Step+1); //at step plus first one we registered + ++Count; + }); + + Solver->AdvanceAndDispatch_External(1/30.f); + + Solver->BufferPhysicsResults(); + Solver->FlipBuffers(); + } + + EXPECT_EQ(Count,11); + + } } diff --git a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestEPA.cpp b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestEPA.cpp index 32a8295ebd23..c296112300c1 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestEPA.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestEPA.cpp @@ -807,6 +807,198 @@ namespace ChaosTest } } + // Defining boat geom data outside of single test scope as it's used in multiple. + const TArray> BoatSurfaceParticles( + { + {-118.965088, -100.379936, 105.818298}, + {-128.562881, 80.0933762, 107.703270}, + {-139.344116, -97.6986847, 77.2006836}, + {-150.005661, -100.080147, 51.9458313}, + {-139.707321, 97.7620926, 68.6699982}, + {-162.950150, 15.7422667, -12.4111595}, + {-133.124146, -77.7715225, 16.0983276}, + {-162.950150, -18.5639591, -8.11873245}, + {80.0627518, -94.3176956, 23.9974003}, + {144.317383, -64.4015045, 12.9772358}, + {-134.336945, 83.6453476, 19.9467926}, + {-28.9211884, 95.5794601, 24.0703869}, + {-29.2745361, 115.021286, 39.6718407}, + {270.352173, -9.99338818, 13.3286972}, + {168.206909, -9.18884468, 4.79613113}, + {152.132553, 26.3735561, 5.07503510}, + {-96.0630951, -5.13696861, -10.5715485}, + {-34.2370224, 118.708824, 51.9164314}, + {-115.816895, 100.536232, 105.218788}, + {190.384033, 113.884377, 39.8578377}, + {75.6613998, 116.777252, 48.2003098}, + {75.6914368, 116.705940, 56.3343391}, + {223.461243, 34.3406334, 119.657486}, + {154.527252, 7.14775467, 133.457825}, + {224.732254, -33.3490448, 120.477432}, + {181.900131, -70.9029160, 125.913544}, + {190.739014, 114.494293, 68.6749573}, + {-124.285568, 84.3786621, 111.995697}, + {-55.6235504, 105.829025, 107.703270}, + {144.056519, 88.6111145, 111.072426}, + {293.764893, 97.7141037, 68.6531982}, + {357.075989, 27.2315865, 88.9283295}, + {377.644470, 48.4299507, 68.7059937}, + {299.537781, 67.8833160, 92.9634094}, + {217.357620, 91.4785843, 96.6879578}, + {277.666443, 91.4017410, 36.2159767}, + {412.009827, 15.7422667, 69.0567322}, + {160.430008, 34.6351395, 128.190872}, + {251.469971, 54.2859497, 20.7005157}, + {179.907928, 69.9437637, 16.8336792}, + {132.472702, 94.3125381, 24.5205002}, + {373.345215, -14.2786741, 90.5335693}, + {104.055634, -116.337784, 64.5478134}, + {206.939423, -112.547020, 39.8371887}, + {215.453522, -112.922203, 68.6651306}, + {235.525650, -88.4538803, 28.6447029}, + {175.841675, -40.6552811, 9.08371735}, + {316.693298, -74.9164505, 39.9856682}, + {281.028259, -96.0662766, 39.8370399}, + {353.332031, -67.8981171, 68.7482452}, + {269.801300, -105.132248, 68.7129745}, + {257.381836, -76.0536499, 110.256386}, + {-47.0126572, -104.365433, 107.688568}, + {377.622498, 15.7422667, 39.0685501}, + {401.314575, -21.9763966, 64.5559235}, + {407.676208, -14.2786741, 73.3785629}, + {312.919617, -33.3405228, 28.3129959}, + {334.736877, 11.4330406, 26.2059746}, + {309.059326, 80.6674042, 39.9196320}, + {-142.015427, -9.19016743, -11.2502632}, + {-27.1481628, -111.977615, 35.8436165}, + {-23.9894104, -116.828667, 43.9551315} + }); + + const TArray> BoatConvexPlanes( + { + {{-118.965088, -100.379936, 105.818298},{-0.815755606, -0.0494019315, 0.576283097}}, + {{-128.562881, 80.0933762, 107.703270},{-0.920841396, -0.0110327024, 0.389781147}}, + {{-150.005661, -100.080147, 51.9458313},{-0.519791245, -0.801730216, 0.295035094}}, + {{-128.562881, 80.0933762, 107.703270},{-0.958117247, 0.0257631000, 0.285215139}}, + {{-139.707321, 97.7620926, 68.6699982},{-0.968365312, 0.0294600632, 0.247791693}}, + {{-133.124146, -77.7715225, 16.0983276},{0.00511667505, -0.376362234, -0.926458478}}, + {{-162.950150, -18.5639591, -8.11873245},{0.00966687687, -0.363861620, -0.931402802}}, + {{-162.950150, 15.7422667, -12.4111595},{-0.0140216080, 0.434951097, -0.900344849}}, + {{-134.336945, 83.6453476, 19.9467926},{-0.0402486622, 0.624916077, -0.779653668}}, + {{270.352173, -9.99338818, 13.3286972},{0.0835136175, 0.0455556102, -0.995464802}}, + {{168.206909, -9.18884468, 4.79613113},{0.0585431270, 0.0342863351, -0.997695863}}, + {{-96.0630951, -5.13696861, -10.5715485},{0.0525218807, 0.0805560499, -0.995365262}}, + {{-29.2745361, 115.021286, 39.6718407},{-0.204991043, 0.911147118, -0.357476622}}, + {{-134.336945, 83.6453476, 19.9467926},{-0.230919138, 0.927439809, -0.294162780}}, + {{-139.707321, 97.7620926, 68.6699982},{-0.187245682, 0.981143415, 0.0479236171}}, + {{190.384033, 113.884377, 39.8578377},{0.0107297711, 0.981200278, -0.192693755}}, + {{-34.2370224, 118.708824, 51.9164314},{0.0178666674, 0.999802530, 0.00869940408}}, + {{190.384033, 113.884377, 39.8578377},{0.00520140817, 0.958088040, -0.286426455}}, + {{223.461243, 34.3406334, 119.657486},{0.190408617, 0.0154655315, 0.981583059}}, + {{154.527252, 7.14775467, 133.457825},{0.159681797, -0.0393412262, 0.986384273}}, + {{75.6914368, 116.705940, 56.3343391},{0.0176137071, 0.999732912, 0.0149621498}}, + {{-115.816895, 100.536232, 105.218788},{-0.715656042, 0.553675890, 0.425769299}}, + {{-139.707321, 97.7620926, 68.6699982},{-0.817192018, 0.400413275, 0.414567769}}, + {{-128.562881, 80.0933762, 107.703270},{-0.685338736, -0.0440392531, 0.726891577}}, + {{-118.965088, -100.379936, 105.818298},{-0.0865496397, -0.0357803851, 0.995604813}}, + {{-55.6235504, 105.829025, 107.703270},{-0.0741617680, 0.418523729, 0.905172884}}, + {{144.056519, 88.6111145, 111.072426},{0.0611657463, 0.820554078, 0.568286657}}, + {{190.739014, 114.494293, 68.6749573},{0.00145421748, 0.974245131, 0.225486547}}, + {{-34.2370224, 118.708824, 51.9164314},{-0.0937685072, 0.977354348, 0.189699650}}, + {{293.764893, 97.7141037, 68.6531982},{0.160708517, 0.986737013, -0.0228640344}}, + {{190.384033, 113.884377, 39.8578377},{0.0236439183, 0.999490380, -0.0214455500}}, + {{75.6613998, 116.777252, 48.2003098},{0.0182868484, 0.999794960, 0.00869778637}}, + {{357.075989, 27.2315865, 88.9283295},{0.363979012, 0.433337778, 0.824462056}}, + {{377.644470, 48.4299507, 68.7059937},{0.369141936, 0.628997087, 0.684176087}}, + {{293.764893, 97.7141037, 68.6531982},{0.217538610, 0.641553879, 0.735585213}}, + {{217.357620, 91.4785843, 96.6879578},{0.159607396, 0.414469659, 0.895957828}}, + {{144.056519, 88.6111145, 111.072426},{0.156273633, 0.373305798, 0.914451420}}, + {{223.461243, 34.3406334, 119.657486},{0.229725912, 0.231315732, 0.945367098}}, + {{293.764893, 97.7141037, 68.6531982},{0.134402916, 0.824485123, 0.549690902}}, + {{190.739014, 114.494293, 68.6749573},{0.0830841213, 0.807267189, 0.584308803}}, + {{277.666443, 91.4017410, 36.2159767},{0.226969868, 0.928666651, -0.293364942}}, + {{357.075989, 27.2315865, 88.9283295},{0.384961128, 0.413572103, 0.825083613}}, + {{144.056519, 88.6111145, 111.072426},{0.0102420887, 0.305115640, 0.952260256}}, + {{-55.6235504, 105.829025, 107.703270},{-0.0136526823, 0.238040313, 0.971159339}}, + {{-124.285568, 84.3786621, 111.995697},{-0.0221296977, 0.192725569, 0.981003106}}, + {{154.527252, 7.14775467, 133.457825},{0.133184910, 0.158850595, 0.978278220}}, + {{223.461243, 34.3406334, 119.657486},{0.127949536, 0.334882438, 0.933532357}}, + {{270.352173, -9.99338818, 13.3286972},{0.113541767, 0.146057680, -0.982738674}}, + {{152.132553, 26.3735561, 5.07503510},{0.0967332050, 0.201390326, -0.974722803}}, + {{179.907928, 69.9437637, 16.8336792},{0.118871428, 0.310366035, -0.943155587}}, + {{152.132553, 26.3735561, 5.07503510},{0.0460564680, 0.232807800, -0.971431613}}, + {{-28.9211884, 95.5794601, 24.0703869},{0.00696368096, 0.603951454, -0.796990752}}, + {{190.384033, 113.884377, 39.8578377},{0.0805319995, 0.456198364, -0.886226594}}, + {{277.666443, 91.4017410, 36.2159767},{0.0808695257, 0.439591616, -0.894549847}}, + {{179.907928, 69.9437637, 16.8336792},{0.0254166014, 0.345391214, -0.938114524}}, + {{-162.950150, 15.7422667, -12.4111595},{0.00574636925, 0.407610655, -0.913137734}}, + {{-28.9211884, 95.5794601, 24.0703869},{0.00389994099, 0.625906646, -0.779888213}}, + {{412.009827, 15.7422667, 69.0567322},{0.367606252, 0.179364890, 0.912520528}}, + {{357.075989, 27.2315865, 88.9283295},{0.228729501, 0.126970738, 0.965174258}}, + {{223.461243, 34.3406334, 119.657486},{0.195577502, 0.0155502548, 0.980564892}}, + {{104.055634, -116.337784, 64.5478134},{0.0314623937, -0.999256194, -0.0222970415}}, + {{206.939423, -112.547020, 39.8371887},{0.0439339988, -0.463305235, -0.885109067}}, + {{80.0627518, -94.3176956, 23.9974003},{0.0430704951, -0.425489426, -0.903937817}}, + {{144.317383, -64.4015045, 12.9772358},{0.0910515115, -0.277681828, -0.956348479}}, + {{175.841675, -40.6552811, 9.08371735},{0.121729963, -0.241943270, -0.962624073}}, + {{270.352173, -9.99338818, 13.3286972},{0.176452607, -0.263455838, -0.948396325}}, + {{316.693298, -74.9164505, 39.9856682},{0.180675939, -0.298087925, -0.937283218}}, + {{281.028259, -96.0662766, 39.8370399},{0.117892988, -0.529992998, -0.839766979}}, + {{316.693298, -74.9164505, 39.9856682},{0.467810631, -0.786031187, -0.404114038}}, + {{353.332031, -67.8981171, 68.7482452},{0.403864205, -0.905904770, -0.127398163}}, + {{269.801300, -105.132248, 68.7129745},{0.211974993, -0.952931166, -0.216769129}}, + {{353.332031, -67.8981171, 68.7482452},{0.323771894, -0.726920843, 0.605605364}}, + {{257.381836, -76.0536499, 110.256386},{0.113805972, -0.797622085, 0.592323542}}, + {{215.453522, -112.922203, 68.6651306},{0.141719818, -0.988393307, -0.0547193065}}, + {{257.381836, -76.0536499, 110.256386},{0.0748343095, -0.782468021, 0.618177652}}, + {{181.900131, -70.9029160, 125.913544},{-0.0393289365, -0.256881803, 0.965642214}}, + {{104.055634, -116.337784, 64.5478134},{0.0170975495, -0.946409166, 0.322517306}}, + {{215.453522, -112.922203, 68.6651306},{0.0658586845, -0.785601914, 0.615217268}}, + {{353.332031, -67.8981171, 68.7482452},{0.384226114, -0.466992885, 0.796421945}}, + {{373.345215, -14.2786741, 90.5335693},{0.206286892, -0.0757761970, 0.975552976}}, + {{224.732254, -33.3490448, 120.477432},{0.196953446, -0.0832281485, 0.976873755}}, + {{316.693298, -74.9164505, 39.9856682},{0.434601337, -0.300671607, -0.848951280}}, + {{377.622498, 15.7422667, 39.0685501},{0.654218376, -0.0959888026, -0.750189602}}, + {{412.009827, 15.7422667, 69.0567322},{0.867719710, -0.191302225, -0.458765566}}, + {{407.676208, -14.2786741, 73.3785629},{0.691795230, -0.711691737, 0.122124225}}, + {{353.332031, -67.8981171, 68.7482452},{0.551859438, -0.626838267, -0.550022721}}, + {{412.009827, 15.7422667, 69.0567322},{0.446075022, 0.0641205981, 0.892695665}}, + {{373.345215, -14.2786741, 90.5335693},{0.394906074, -0.468489796, 0.790295124}}, + {{377.622498, 15.7422667, 39.0685501},{0.325547844, -0.228074327, -0.917605937}}, + {{316.693298, -74.9164505, 39.9856682},{0.197628111, -0.248304784, -0.948307872}}, + {{270.352173, -9.99338818, 13.3286972},{0.144101545, 0.154426888, -0.977438986}}, + {{251.469971, 54.2859497, 20.7005157},{0.195229396, 0.257776380, -0.946275234}}, + {{377.622498, 15.7422667, 39.0685501},{0.299516141, -0.189948440, -0.934991837}}, + {{312.919617, -33.3405228, 28.3129959},{0.245874554, -0.164760798, -0.955196083}}, + {{277.666443, 91.4017410, 36.2159767},{0.339942038, 0.877070487, -0.339391768}}, + {{293.764893, 97.7141037, 68.6531982},{0.492510736, 0.837980986, -0.234991312}}, + {{377.644470, 48.4299507, 68.7059937},{0.530996740, 0.568982124, -0.627934515}}, + {{377.622498, 15.7422667, 39.0685501},{0.250460476, 0.276656479, -0.927755713}}, + {{334.736877, 11.4330406, 26.2059746},{0.200788274, 0.261470705, -0.944095910}}, + {{377.644470, 48.4299507, 68.7059937},{0.542767346, 0.563946247, -0.622389138}}, + {{270.352173, -9.99338818, 13.3286972},{0.0817910284, -0.115049526, -0.989986837}}, + {{175.841675, -40.6552811, 9.08371735},{0.0557664521, -0.121505104, -0.991023004}}, + {{144.317383, -64.4015045, 12.9772358},{0.0422874913, -0.216078743, -0.975459754}}, + {{-162.950150, -18.5639591, -8.11873245},{-0.0924396142, -0.123621307, -0.988014519}}, + {{-162.950150, 15.7422667, -12.4111595},{0.0175636765, -0.0317834914, -0.999340415}}, + {{-96.0630951, -5.13696861, -10.5715485},{0.0355855115, -0.241038486, -0.969862998}}, + {{144.317383, -64.4015045, 12.9772358},{0.0132575780, -0.343341976, -0.939116895}}, + {{-133.124146, -77.7715225, 16.0983276},{-0.579293728, -0.541147888, -0.609571695}}, + {{-150.005661, -100.080147, 51.9458313},{-0.967441142, 0.0314226188, 0.251137793}}, + {{-133.124146, -77.7715225, 16.0983276},{-0.00871447474, -0.520026803, -0.854105532}}, + {{80.0627518, -94.3176956, 23.9974003},{0.0120858839, -0.606598854, -0.794916213}}, + {{104.055634, -116.337784, 64.5478134},{-0.0256504230, -0.982792795, 0.182921574}}, + {{-47.0126572, -104.365433, 107.688568},{-0.0589226782, -0.983500481, 0.171040595}}, + {{-118.965088, -100.379936, 105.818298},{-0.127220407, -0.989554763, 0.0677959770}}, + {{-150.005661, -100.080147, 51.9458313},{-0.145546019, -0.873032987, -0.465434998}}, + {{-27.1481628, -111.977615, 35.8436165},{0.00670416094, -0.857060790, -0.515171707}}, + {{206.939423, -112.547020, 39.8371887},{0.0170129854, -0.996484399, -0.0820325986}}, + {{-150.005661, -100.080147, 51.9458313},{-0.153074399, -0.805065334, -0.573095083}}, + {{154.527252, 7.14775467, 133.457825},{-0.0549643822, -0.115145117, 0.991826892}}, + {{-134.336945, 83.6453476, 19.9467926},{-0.852820277, 0.468897045, -0.229854882}}, + }); + + // Boat vs ground in athena empty, barely in contact // Normal ideally would point down, but returned normal pointed up before it was fixed { @@ -838,197 +1030,6 @@ namespace ChaosTest {512.000000,0.000000000,0.000000000} }); - TParticles BoatSurfaceParticles( - { - {-118.965088, -100.379936, 105.818298}, - {-128.562881, 80.0933762, 107.703270}, - {-139.344116, -97.6986847, 77.2006836}, - {-150.005661, -100.080147, 51.9458313}, - {-139.707321, 97.7620926, 68.6699982}, - {-162.950150, 15.7422667, -12.4111595}, - {-133.124146, -77.7715225, 16.0983276}, - {-162.950150, -18.5639591, -8.11873245}, - {80.0627518, -94.3176956, 23.9974003}, - {144.317383, -64.4015045, 12.9772358}, - {-134.336945, 83.6453476, 19.9467926}, - {-28.9211884, 95.5794601, 24.0703869}, - {-29.2745361, 115.021286, 39.6718407}, - {270.352173, -9.99338818, 13.3286972}, - {168.206909, -9.18884468, 4.79613113}, - {152.132553, 26.3735561, 5.07503510}, - {-96.0630951, -5.13696861, -10.5715485}, - {-34.2370224, 118.708824, 51.9164314}, - {-115.816895, 100.536232, 105.218788}, - {190.384033, 113.884377, 39.8578377}, - {75.6613998, 116.777252, 48.2003098}, - {75.6914368, 116.705940, 56.3343391}, - {223.461243, 34.3406334, 119.657486}, - {154.527252, 7.14775467, 133.457825}, - {224.732254, -33.3490448, 120.477432}, - {181.900131, -70.9029160, 125.913544}, - {190.739014, 114.494293, 68.6749573}, - {-124.285568, 84.3786621, 111.995697}, - {-55.6235504, 105.829025, 107.703270}, - {144.056519, 88.6111145, 111.072426}, - {293.764893, 97.7141037, 68.6531982}, - {357.075989, 27.2315865, 88.9283295}, - {377.644470, 48.4299507, 68.7059937}, - {299.537781, 67.8833160, 92.9634094}, - {217.357620, 91.4785843, 96.6879578}, - {277.666443, 91.4017410, 36.2159767}, - {412.009827, 15.7422667, 69.0567322}, - {160.430008, 34.6351395, 128.190872}, - {251.469971, 54.2859497, 20.7005157}, - {179.907928, 69.9437637, 16.8336792}, - {132.472702, 94.3125381, 24.5205002}, - {373.345215, -14.2786741, 90.5335693}, - {104.055634, -116.337784, 64.5478134}, - {206.939423, -112.547020, 39.8371887}, - {215.453522, -112.922203, 68.6651306}, - {235.525650, -88.4538803, 28.6447029}, - {175.841675, -40.6552811, 9.08371735}, - {316.693298, -74.9164505, 39.9856682}, - {281.028259, -96.0662766, 39.8370399}, - {353.332031, -67.8981171, 68.7482452}, - {269.801300, -105.132248, 68.7129745}, - {257.381836, -76.0536499, 110.256386}, - {-47.0126572, -104.365433, 107.688568}, - {377.622498, 15.7422667, 39.0685501}, - {401.314575, -21.9763966, 64.5559235}, - {407.676208, -14.2786741, 73.3785629}, - {312.919617, -33.3405228, 28.3129959}, - {334.736877, 11.4330406, 26.2059746}, - {309.059326, 80.6674042, 39.9196320}, - {-142.015427, -9.19016743, -11.2502632}, - {-27.1481628, -111.977615, 35.8436165}, - {-23.9894104, -116.828667, 43.9551315} - }); - - - - TArray> BoatConvexPlanes( - { - {{-118.965088, -100.379936, 105.818298},{-0.815755606, -0.0494019315, 0.576283097}}, - {{-128.562881, 80.0933762, 107.703270},{-0.920841396, -0.0110327024, 0.389781147}}, - {{-150.005661, -100.080147, 51.9458313},{-0.519791245, -0.801730216, 0.295035094}}, - {{-128.562881, 80.0933762, 107.703270},{-0.958117247, 0.0257631000, 0.285215139}}, - {{-139.707321, 97.7620926, 68.6699982},{-0.968365312, 0.0294600632, 0.247791693}}, - {{-133.124146, -77.7715225, 16.0983276},{0.00511667505, -0.376362234, -0.926458478}}, - {{-162.950150, -18.5639591, -8.11873245},{0.00966687687, -0.363861620, -0.931402802}}, - {{-162.950150, 15.7422667, -12.4111595},{-0.0140216080, 0.434951097, -0.900344849}}, - {{-134.336945, 83.6453476, 19.9467926},{-0.0402486622, 0.624916077, -0.779653668}}, - {{270.352173, -9.99338818, 13.3286972},{0.0835136175, 0.0455556102, -0.995464802}}, - {{168.206909, -9.18884468, 4.79613113},{0.0585431270, 0.0342863351, -0.997695863}}, - {{-96.0630951, -5.13696861, -10.5715485},{0.0525218807, 0.0805560499, -0.995365262}}, - {{-29.2745361, 115.021286, 39.6718407},{-0.204991043, 0.911147118, -0.357476622}}, - {{-134.336945, 83.6453476, 19.9467926},{-0.230919138, 0.927439809, -0.294162780}}, - {{-139.707321, 97.7620926, 68.6699982},{-0.187245682, 0.981143415, 0.0479236171}}, - {{190.384033, 113.884377, 39.8578377},{0.0107297711, 0.981200278, -0.192693755}}, - {{-34.2370224, 118.708824, 51.9164314},{0.0178666674, 0.999802530, 0.00869940408}}, - {{190.384033, 113.884377, 39.8578377},{0.00520140817, 0.958088040, -0.286426455}}, - {{223.461243, 34.3406334, 119.657486},{0.190408617, 0.0154655315, 0.981583059}}, - {{154.527252, 7.14775467, 133.457825},{0.159681797, -0.0393412262, 0.986384273}}, - {{75.6914368, 116.705940, 56.3343391},{0.0176137071, 0.999732912, 0.0149621498}}, - {{-115.816895, 100.536232, 105.218788},{-0.715656042, 0.553675890, 0.425769299}}, - {{-139.707321, 97.7620926, 68.6699982},{-0.817192018, 0.400413275, 0.414567769}}, - {{-128.562881, 80.0933762, 107.703270},{-0.685338736, -0.0440392531, 0.726891577}}, - {{-118.965088, -100.379936, 105.818298},{-0.0865496397, -0.0357803851, 0.995604813}}, - {{-55.6235504, 105.829025, 107.703270},{-0.0741617680, 0.418523729, 0.905172884}}, - {{144.056519, 88.6111145, 111.072426},{0.0611657463, 0.820554078, 0.568286657}}, - {{190.739014, 114.494293, 68.6749573},{0.00145421748, 0.974245131, 0.225486547}}, - {{-34.2370224, 118.708824, 51.9164314},{-0.0937685072, 0.977354348, 0.189699650}}, - {{293.764893, 97.7141037, 68.6531982},{0.160708517, 0.986737013, -0.0228640344}}, - {{190.384033, 113.884377, 39.8578377},{0.0236439183, 0.999490380, -0.0214455500}}, - {{75.6613998, 116.777252, 48.2003098},{0.0182868484, 0.999794960, 0.00869778637}}, - {{357.075989, 27.2315865, 88.9283295},{0.363979012, 0.433337778, 0.824462056}}, - {{377.644470, 48.4299507, 68.7059937},{0.369141936, 0.628997087, 0.684176087}}, - {{293.764893, 97.7141037, 68.6531982},{0.217538610, 0.641553879, 0.735585213}}, - {{217.357620, 91.4785843, 96.6879578},{0.159607396, 0.414469659, 0.895957828}}, - {{144.056519, 88.6111145, 111.072426},{0.156273633, 0.373305798, 0.914451420}}, - {{223.461243, 34.3406334, 119.657486},{0.229725912, 0.231315732, 0.945367098}}, - {{293.764893, 97.7141037, 68.6531982},{0.134402916, 0.824485123, 0.549690902}}, - {{190.739014, 114.494293, 68.6749573},{0.0830841213, 0.807267189, 0.584308803}}, - {{277.666443, 91.4017410, 36.2159767},{0.226969868, 0.928666651, -0.293364942}}, - {{357.075989, 27.2315865, 88.9283295},{0.384961128, 0.413572103, 0.825083613}}, - {{144.056519, 88.6111145, 111.072426},{0.0102420887, 0.305115640, 0.952260256}}, - {{-55.6235504, 105.829025, 107.703270},{-0.0136526823, 0.238040313, 0.971159339}}, - {{-124.285568, 84.3786621, 111.995697},{-0.0221296977, 0.192725569, 0.981003106}}, - {{154.527252, 7.14775467, 133.457825},{0.133184910, 0.158850595, 0.978278220}}, - {{223.461243, 34.3406334, 119.657486},{0.127949536, 0.334882438, 0.933532357}}, - {{270.352173, -9.99338818, 13.3286972},{0.113541767, 0.146057680, -0.982738674}}, - {{152.132553, 26.3735561, 5.07503510},{0.0967332050, 0.201390326, -0.974722803}}, - {{179.907928, 69.9437637, 16.8336792},{0.118871428, 0.310366035, -0.943155587}}, - {{152.132553, 26.3735561, 5.07503510},{0.0460564680, 0.232807800, -0.971431613}}, - {{-28.9211884, 95.5794601, 24.0703869},{0.00696368096, 0.603951454, -0.796990752}}, - {{190.384033, 113.884377, 39.8578377},{0.0805319995, 0.456198364, -0.886226594}}, - {{277.666443, 91.4017410, 36.2159767},{0.0808695257, 0.439591616, -0.894549847}}, - {{179.907928, 69.9437637, 16.8336792},{0.0254166014, 0.345391214, -0.938114524}}, - {{-162.950150, 15.7422667, -12.4111595},{0.00574636925, 0.407610655, -0.913137734}}, - {{-28.9211884, 95.5794601, 24.0703869},{0.00389994099, 0.625906646, -0.779888213}}, - {{412.009827, 15.7422667, 69.0567322},{0.367606252, 0.179364890, 0.912520528}}, - {{357.075989, 27.2315865, 88.9283295},{0.228729501, 0.126970738, 0.965174258}}, - {{223.461243, 34.3406334, 119.657486},{0.195577502, 0.0155502548, 0.980564892}}, - {{104.055634, -116.337784, 64.5478134},{0.0314623937, -0.999256194, -0.0222970415}}, - {{206.939423, -112.547020, 39.8371887},{0.0439339988, -0.463305235, -0.885109067}}, - {{80.0627518, -94.3176956, 23.9974003},{0.0430704951, -0.425489426, -0.903937817}}, - {{144.317383, -64.4015045, 12.9772358},{0.0910515115, -0.277681828, -0.956348479}}, - {{175.841675, -40.6552811, 9.08371735},{0.121729963, -0.241943270, -0.962624073}}, - {{270.352173, -9.99338818, 13.3286972},{0.176452607, -0.263455838, -0.948396325}}, - {{316.693298, -74.9164505, 39.9856682},{0.180675939, -0.298087925, -0.937283218}}, - {{281.028259, -96.0662766, 39.8370399},{0.117892988, -0.529992998, -0.839766979}}, - {{316.693298, -74.9164505, 39.9856682},{0.467810631, -0.786031187, -0.404114038}}, - {{353.332031, -67.8981171, 68.7482452},{0.403864205, -0.905904770, -0.127398163}}, - {{269.801300, -105.132248, 68.7129745},{0.211974993, -0.952931166, -0.216769129}}, - {{353.332031, -67.8981171, 68.7482452},{0.323771894, -0.726920843, 0.605605364}}, - {{257.381836, -76.0536499, 110.256386},{0.113805972, -0.797622085, 0.592323542}}, - {{215.453522, -112.922203, 68.6651306},{0.141719818, -0.988393307, -0.0547193065}}, - {{257.381836, -76.0536499, 110.256386},{0.0748343095, -0.782468021, 0.618177652}}, - {{181.900131, -70.9029160, 125.913544},{-0.0393289365, -0.256881803, 0.965642214}}, - {{104.055634, -116.337784, 64.5478134},{0.0170975495, -0.946409166, 0.322517306}}, - {{215.453522, -112.922203, 68.6651306},{0.0658586845, -0.785601914, 0.615217268}}, - {{353.332031, -67.8981171, 68.7482452},{0.384226114, -0.466992885, 0.796421945}}, - {{373.345215, -14.2786741, 90.5335693},{0.206286892, -0.0757761970, 0.975552976}}, - {{224.732254, -33.3490448, 120.477432},{0.196953446, -0.0832281485, 0.976873755}}, - {{316.693298, -74.9164505, 39.9856682},{0.434601337, -0.300671607, -0.848951280}}, - {{377.622498, 15.7422667, 39.0685501},{0.654218376, -0.0959888026, -0.750189602}}, - {{412.009827, 15.7422667, 69.0567322},{0.867719710, -0.191302225, -0.458765566}}, - {{407.676208, -14.2786741, 73.3785629},{0.691795230, -0.711691737, 0.122124225}}, - {{353.332031, -67.8981171, 68.7482452},{0.551859438, -0.626838267, -0.550022721}}, - {{412.009827, 15.7422667, 69.0567322},{0.446075022, 0.0641205981, 0.892695665}}, - {{373.345215, -14.2786741, 90.5335693},{0.394906074, -0.468489796, 0.790295124}}, - {{377.622498, 15.7422667, 39.0685501},{0.325547844, -0.228074327, -0.917605937}}, - {{316.693298, -74.9164505, 39.9856682},{0.197628111, -0.248304784, -0.948307872}}, - {{270.352173, -9.99338818, 13.3286972},{0.144101545, 0.154426888, -0.977438986}}, - {{251.469971, 54.2859497, 20.7005157},{0.195229396, 0.257776380, -0.946275234}}, - {{377.622498, 15.7422667, 39.0685501},{0.299516141, -0.189948440, -0.934991837}}, - {{312.919617, -33.3405228, 28.3129959},{0.245874554, -0.164760798, -0.955196083}}, - {{277.666443, 91.4017410, 36.2159767},{0.339942038, 0.877070487, -0.339391768}}, - {{293.764893, 97.7141037, 68.6531982},{0.492510736, 0.837980986, -0.234991312}}, - {{377.644470, 48.4299507, 68.7059937},{0.530996740, 0.568982124, -0.627934515}}, - {{377.622498, 15.7422667, 39.0685501},{0.250460476, 0.276656479, -0.927755713}}, - {{334.736877, 11.4330406, 26.2059746},{0.200788274, 0.261470705, -0.944095910}}, - {{377.644470, 48.4299507, 68.7059937},{0.542767346, 0.563946247, -0.622389138}}, - {{270.352173, -9.99338818, 13.3286972},{0.0817910284, -0.115049526, -0.989986837}}, - {{175.841675, -40.6552811, 9.08371735},{0.0557664521, -0.121505104, -0.991023004}}, - {{144.317383, -64.4015045, 12.9772358},{0.0422874913, -0.216078743, -0.975459754}}, - {{-162.950150, -18.5639591, -8.11873245},{-0.0924396142, -0.123621307, -0.988014519}}, - {{-162.950150, 15.7422667, -12.4111595},{0.0175636765, -0.0317834914, -0.999340415}}, - {{-96.0630951, -5.13696861, -10.5715485},{0.0355855115, -0.241038486, -0.969862998}}, - {{144.317383, -64.4015045, 12.9772358},{0.0132575780, -0.343341976, -0.939116895}}, - {{-133.124146, -77.7715225, 16.0983276},{-0.579293728, -0.541147888, -0.609571695}}, - {{-150.005661, -100.080147, 51.9458313},{-0.967441142, 0.0314226188, 0.251137793}}, - {{-133.124146, -77.7715225, 16.0983276},{-0.00871447474, -0.520026803, -0.854105532}}, - {{80.0627518, -94.3176956, 23.9974003},{0.0120858839, -0.606598854, -0.794916213}}, - {{104.055634, -116.337784, 64.5478134},{-0.0256504230, -0.982792795, 0.182921574}}, - {{-47.0126572, -104.365433, 107.688568},{-0.0589226782, -0.983500481, 0.171040595}}, - {{-118.965088, -100.379936, 105.818298},{-0.127220407, -0.989554763, 0.0677959770}}, - {{-150.005661, -100.080147, 51.9458313},{-0.145546019, -0.873032987, -0.465434998}}, - {{-27.1481628, -111.977615, 35.8436165},{0.00670416094, -0.857060790, -0.515171707}}, - {{206.939423, -112.547020, 39.8371887},{0.0170129854, -0.996484399, -0.0820325986}}, - {{-150.005661, -100.080147, 51.9458313},{-0.153074399, -0.805065334, -0.573095083}}, - {{154.527252, 7.14775467, 133.457825},{-0.0549643822, -0.115145117, 0.991826892}}, - {{-134.336945, 83.6453476, 19.9467926},{-0.852820277, 0.468897045, -0.229854882}}, - }); FVec3 GroundConvexScale = { 25,25,1 }; @@ -1037,7 +1038,9 @@ namespace ChaosTest - FConvex BoatConvex = FConvex(MoveTemp(BoatConvexPlanes), MoveTemp(BoatSurfaceParticles)); + auto BoatConvexPlanesCopy = BoatConvexPlanes; + auto BoatSurfaceParticlesCopy = TParticles(CopyTemp(BoatSurfaceParticles)); + FConvex BoatConvex = FConvex(MoveTemp(BoatConvexPlanesCopy), MoveTemp(BoatSurfaceParticlesCopy)); TRigidTransform BoatTransform(FVec3(-5421.507324, 2335.360840, 6.972876), FQuat(-0.016646, -0.008459, 0.915564, -0.401738), FVec3(1.0f)); @@ -1055,6 +1058,35 @@ namespace ChaosTest FVec3 WorldNormal = BoatTransform.TransformVectorNoScale(Normal); EXPECT_LT(Normal.Z, -0.9f); // Should point roughly down } // End of Boat test + + // Boat vs rock wall at carl POI behind waterfall w/ water level 0 in season 13 + // GJK was not progressing and reporting larger distance, when dist was actually 0, and was making nan in normal. + { + // Triangle + const FVec3 A = { -10934.1797, -11431.4863, -5661.06982 }; + const FVec3 B = { -10025.8525, -11155.4160, -6213.55322 }; + const FVec3 C = { -10938.6836, -11213.3320, -6213.55322 }; + const TTriangle Triangle(A, B, C); + FVec3 ExpectedNormal = FVec3::CrossProduct(Triangle[1] - Triangle[0], Triangle[2] - Triangle[0]); + ExpectedNormal.Normalize(); + + auto BoatConvexPlanesCopy = BoatConvexPlanes; + auto BoatSurfaceParticlesCopy = TParticles(CopyTemp(BoatSurfaceParticles)); + FConvex BoatConvex = FConvex(MoveTemp(BoatConvexPlanesCopy), MoveTemp(BoatSurfaceParticlesCopy)); + + TRigidTransform QueryTM(FVec3(-10831.1875, -11206.3750, -6140.38135), FQuat(-0.524916053, -0.0370868668, -0.126528814, 0.840879321), FVec3(1.0f)); + + FReal Penetration; + FVec3 ClosestA, ClosestB, Normal; + int32 NumIterations = 0; + + auto result = GJKPenetration(Triangle, BoatConvex, QueryTM, Penetration, ClosestA, ClosestB, Normal, 0.0f); + + // Confirm normal is valid and close to expected normal. + float dot = FVec3::DotProduct(ExpectedNormal, Normal); + EXPECT_NEAR(dot, 1, 0.0001f); + } // End of Boat test + } // Currently broken EPA edge cases. As they are fixed move them to EPARealFailures_Fixed above so that we can ensure they don't break again. diff --git a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestGraph.cpp b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestGraph.cpp index 5243fb643a73..d4a94f986638 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestGraph.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestGraph.cpp @@ -569,7 +569,7 @@ namespace ChaosTest { Constraints.AddConstraint(ConstrainedParticleIndices); } - SOAs.ClearPutToSleepThisFrame(); + SOAs.ClearTransientDirty(); Graph.InitializeGraph(SOAs.GetNonDisabledView()); Graph.ReserveConstraints(Constraints.NumConstraints()); diff --git a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestImplicits.cpp b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestImplicits.cpp index e4cf90631729..7c12cc1e1400 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestImplicits.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestImplicits.cpp @@ -444,16 +444,16 @@ namespace ChaosTest { EXPECT_VECTOR_NEAR_ERR(Subject.Normal(TVector3(-3 / 2., 0., 3 / 2.)), TVector3(-1, 0, 1).GetSafeNormal(), KINDA_SMALL_NUMBER, Error); // inside phi - EXPECT_EQ(Subject.SignedDistance(TVector3(1 / 2., 1 / 2., 1 / 2.)), -TVector3(1 / 2.).Size()) << *Error; - EXPECT_EQ(Subject.SignedDistance(TVector3(-1 / 2., -1 / 2., -1 / 2.)), -TVector3(1 / 2.).Size()) << *Error; + EXPECT_NEAR(Subject.SignedDistance(TVector3(1 / 2., 1 / 2., 1 / 2.)), -TVector3(1 / 2.).Size(), KINDA_SMALL_NUMBER) << *Error; + EXPECT_NEAR(Subject.SignedDistance(TVector3(-1 / 2., -1 / 2., -1 / 2.)), -TVector3(1 / 2.).Size(), KINDA_SMALL_NUMBER) << *Error; EXPECT_NEAR(Subject.SignedDistance(TVector3(0., sqrt(2) / 4., -sqrt(2) / 4.)), -1 / 2., KINDA_SMALL_NUMBER) << *Error; EXPECT_NEAR(Subject.SignedDistance(TVector3(0., -sqrt(2) / 4., sqrt(2) / 4.)), -1 / 2., KINDA_SMALL_NUMBER) << *Error; EXPECT_NEAR(Subject.SignedDistance(TVector3(sqrt(2) / 4., 0., -sqrt(2) / 4.)), -1 / 2., KINDA_SMALL_NUMBER) << *Error; EXPECT_NEAR(Subject.SignedDistance(TVector3(-sqrt(2) / 4., 0., sqrt(2) / 4.)), -1 / 2., KINDA_SMALL_NUMBER) << *Error; // outside phi - EXPECT_EQ(Subject.SignedDistance(TVector3(3 / 2., 3 / 2., 3 / 2.)), TVector3(1 / 2.).Size()) << *Error; - EXPECT_EQ(Subject.SignedDistance(TVector3(-3 / 2., -3 / 2., -3 / 2.)), TVector3(1 / 2.).Size()) << *Error; + EXPECT_NEAR(Subject.SignedDistance(TVector3(3 / 2., 3 / 2., 3 / 2.)), TVector3(1 / 2.).Size(), KINDA_SMALL_NUMBER) << *Error; + EXPECT_NEAR(Subject.SignedDistance(TVector3(-3 / 2., -3 / 2., -3 / 2.)), TVector3(1 / 2.).Size(), KINDA_SMALL_NUMBER) << *Error; EXPECT_NEAR(Subject.SignedDistance(TVector3(0., 3 * sqrt(2) / 4., -3 * sqrt(2) / 4.)), 1 / 2., KINDA_SMALL_NUMBER) << *Error; EXPECT_NEAR(Subject.SignedDistance(TVector3(0., -3 * sqrt(2) / 4., 3 * sqrt(2) / 4.)), 1 / 2., KINDA_SMALL_NUMBER) << *Error; EXPECT_NEAR(Subject.SignedDistance(TVector3(3 * sqrt(2) / 4., 0., -3 * sqrt(2) / 4.)), 1 / 2., KINDA_SMALL_NUMBER) << *Error; diff --git a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestPhysicsScene.cpp b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestPhysicsScene.cpp index fef3b6b59a61..41f914b73e23 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestPhysicsScene.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestPhysicsScene.cpp @@ -14,6 +14,7 @@ #include "Chaos/ChaosScene.h" #include "SQAccelerator.h" #include "CollisionQueryFilterCallbackCore.h" +#include "BodyInstanceCore.h" namespace ChaosTest { @@ -105,4 +106,508 @@ namespace ChaosTest { } } + + template + void AdvanceSolverNoPushHelper(TSolver* Solver, float Dt) + { + Solver->AdvanceSolverBy(Dt); + } + + GTEST_TEST(EngineInterface,AccelerationStructureHasSyncTimestamp) + { + //make sure acceleration structure has appropriate sync time + + FChaosScene Scene(nullptr); + Scene.GetSolver()->SetThreadingMode_External(EThreadingModeTemp::SingleThread); + Scene.GetSolver()->SetEnabled(true); + + EXPECT_EQ(Scene.GetSpacialAcceleration()->GetSyncTimestamp(),0); //timestamp of 0 because we flush when scene is created + + FReal TotalDt = 0; + for(int Step = 1; Step < 10; ++Step) + { + FVec3 Grav(0,0,-1); + Scene.SetUpForFrame(&Grav, 1,99999,99999,10,false); + Scene.StartFrame(); + Scene.GetSolver()->GetEvolution()->FlushSpatialAcceleration(); //make sure we get a new tree every step + Scene.EndFrame(); + + EXPECT_EQ(Scene.GetSpacialAcceleration()->GetSyncTimestamp(),Step); + } + } + + GTEST_TEST(EngineInterface,CreateActorPostFlush) + { + FChaosScene Scene(nullptr); + Scene.GetSolver()->SetThreadingMode_External(EThreadingModeTemp::SingleThread); + Scene.GetSolver()->SetEnabled(true); + + FActorCreationParams Params; + Params.Scene = &Scene; + + TGeometryParticle* Particle = nullptr; + + FChaosEngineInterface::CreateActor(Params,Particle); + EXPECT_NE(Particle,nullptr); + + { + auto Sphere = MakeUnique>(FVec3(0),3); + Particle->SetGeometry(MoveTemp(Sphere)); + } + + //tick solver but don't call EndFrame (want to flush and swap manually) + { + FVec3 Grav(0,0,-1); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + } + + //make sure acceleration structure is built + Scene.GetSolver()->GetEvolution()->FlushSpatialAcceleration(); + + //create actor after structure is finished, but before swap happens + TArray*> Particles ={Particle}; + Scene.AddActorsToScene_AssumesLocked(Particles); + + Scene.CopySolverAccelerationStructure(); //trigger swap manually and see pending changes apply + { + const auto HitBuffer = InSphereHelper(Scene,FTransform::Identity,3); + EXPECT_EQ(HitBuffer.GetNumHits(),1); + } + } + + GTEST_TEST(EngineInterface,MoveActorPostFlush) + { + FChaosScene Scene(nullptr); + Scene.GetSolver()->SetThreadingMode_External(EThreadingModeTemp::SingleThread); + Scene.GetSolver()->SetEnabled(true); + + FActorCreationParams Params; + Params.Scene = &Scene; + + TGeometryParticle* Particle = nullptr; + + FChaosEngineInterface::CreateActor(Params,Particle); + EXPECT_NE(Particle,nullptr); + + { + auto Sphere = MakeUnique>(FVec3(0),3); + Particle->SetGeometry(MoveTemp(Sphere)); + } + + //create actor before structure is ticked + TArray*> Particles ={Particle}; + Scene.AddActorsToScene_AssumesLocked(Particles); + + //tick solver so that particle is created, but don't call EndFrame (want to flush and swap manually) + { + FVec3 Grav(0,0,-1); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + } + + //make sure acceleration structure is built + Scene.GetSolver()->GetEvolution()->FlushSpatialAcceleration(); + + //move object to get hit (shows pending move is applied) + FChaosEngineInterface::SetGlobalPose_AssumesLocked(Particle,FTransform(FRotation3::FromIdentity(), FVec3(100,0,0))); + + Scene.CopySolverAccelerationStructure(); //trigger swap manually and see pending changes apply + { + TRigidTransform OverlapTM(FVec3(100,0,0),FRotation3::FromIdentity()); + const auto HitBuffer = InSphereHelper(Scene,OverlapTM,3); + EXPECT_EQ(HitBuffer.GetNumHits(),1); + } + } + + GTEST_TEST(EngineInterface,RemoveActorPostFlush) + { + FChaosScene Scene(nullptr); + Scene.GetSolver()->SetThreadingMode_External(EThreadingModeTemp::SingleThread); + Scene.GetSolver()->SetEnabled(true); + + FActorCreationParams Params; + Params.Scene = &Scene; + + TGeometryParticle* Particle = nullptr; + + FChaosEngineInterface::CreateActor(Params,Particle); + EXPECT_NE(Particle,nullptr); + + { + auto Sphere = MakeUnique>(FVec3(0),3); + Particle->SetGeometry(MoveTemp(Sphere)); + } + + //create actor before structure is ticked + TArray*> Particles ={Particle}; + Scene.AddActorsToScene_AssumesLocked(Particles); + + //tick solver so that particle is created, but don't call EndFrame (want to flush and swap manually) + { + FVec3 Grav(0,0,-1); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + } + + //make sure acceleration structure is built + Scene.GetSolver()->GetEvolution()->FlushSpatialAcceleration(); + + //delete object to get no hit + FChaosEngineInterface::ReleaseActor(Particle, &Scene); + + Scene.CopySolverAccelerationStructure(); //trigger swap manually and see pending changes apply + { + const auto HitBuffer = InSphereHelper(Scene,FTransform::Identity,3); + EXPECT_EQ(HitBuffer.GetNumHits(),0); + } + } + + GTEST_TEST(EngineInterface,RemoveActorPostFlush0Dt) + { + FChaosScene Scene(nullptr); + Scene.GetSolver()->SetThreadingMode_External(EThreadingModeTemp::SingleThread); + Scene.GetSolver()->SetEnabled(true); + + FActorCreationParams Params; + Params.Scene = &Scene; + + TGeometryParticle* Particle = nullptr; + + FChaosEngineInterface::CreateActor(Params,Particle); + EXPECT_NE(Particle,nullptr); + + { + auto Sphere = MakeUnique>(FVec3(0),3); + Particle->SetGeometry(MoveTemp(Sphere)); + } + + //create actor before structure is ticked + TArray*> Particles ={Particle}; + Scene.AddActorsToScene_AssumesLocked(Particles); + + //tick solver so that particle is created, but don't call EndFrame (want to flush and swap manually) + { + //use 0 dt to make sure pending operations are not sensitive to 0 dt + FVec3 Grav(0,0,-1); + Scene.SetUpForFrame(&Grav,0,99999,99999,10,false); + Scene.StartFrame(); + } + + //make sure acceleration structure is built + Scene.GetSolver()->GetEvolution()->FlushSpatialAcceleration(); + + //delete object to get no hit + FChaosEngineInterface::ReleaseActor(Particle,&Scene); + + Scene.CopySolverAccelerationStructure(); //trigger swap manually and see pending changes apply + { + const auto HitBuffer = InSphereHelper(Scene,FTransform::Identity,3); + EXPECT_EQ(HitBuffer.GetNumHits(),0); + } + } + + GTEST_TEST(EngineInterface,CreateAndRemoveActorPostFlush) + { + FChaosScene Scene(nullptr); + Scene.GetSolver()->SetThreadingMode_External(EThreadingModeTemp::SingleThread); + Scene.GetSolver()->SetEnabled(true); + + FActorCreationParams Params; + Params.Scene = &Scene; + + TGeometryParticle* Particle = nullptr; + + //tick solver, but don't call EndFrame (want to flush and swap manually) + { + FVec3 Grav(0,0,-1); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + } + + //make sure acceleration structure is built + Scene.GetSolver()->GetEvolution()->FlushSpatialAcceleration(); + + FChaosEngineInterface::CreateActor(Params,Particle); + EXPECT_NE(Particle,nullptr); + + { + auto Sphere = MakeUnique>(FVec3(0),3); + Particle->SetGeometry(MoveTemp(Sphere)); + } + + //create actor after flush + TArray*> Particles ={Particle}; + Scene.AddActorsToScene_AssumesLocked(Particles); + + //delete object right away to get no hit + FChaosEngineInterface::ReleaseActor(Particle,&Scene); + + Scene.CopySolverAccelerationStructure(); //trigger swap manually and see pending changes apply + { + const auto HitBuffer = InSphereHelper(Scene,FTransform::Identity,3); + EXPECT_EQ(HitBuffer.GetNumHits(),0); + } + } + + GTEST_TEST(EngineInterface,CreateDelayed) + { + for(int Delay = 0; Delay < 4; ++Delay) + { + FChaosScene Scene(nullptr); + Scene.GetSolver()->SetThreadingMode_External(EThreadingModeTemp::SingleThread); + Scene.GetSolver()->SetEnabled(true); + Scene.GetSolver()->GetMarshallingManager().SetTickDelay_External(Delay); + + FActorCreationParams Params; + Params.Scene = &Scene; + + TGeometryParticle* Particle = nullptr; + + FChaosEngineInterface::CreateActor(Params,Particle); + EXPECT_NE(Particle,nullptr); + + { + auto Sphere = MakeUnique>(FVec3(0),3); + Particle->SetGeometry(MoveTemp(Sphere)); + } + + //create actor after flush + TArray*> Particles ={Particle}; + Scene.AddActorsToScene_AssumesLocked(Particles); + + for(int Repeat = 0; Repeat < Delay; ++Repeat) + { + //tick solver + { + FVec3 Grav(0,0,-1); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + Scene.EndFrame(); + } + + //make sure sim hasn't seen it yet + { + FPBDRigidsEvolution* Evolution = Scene.GetSolver()->GetEvolution(); + const auto& SOA = Evolution->GetParticles(); + EXPECT_EQ(SOA.GetAllParticlesView().Num(),0); + } + + //make sure external thread knows about it + { + const auto HitBuffer = InSphereHelper(Scene,FTransform::Identity,3); + EXPECT_EQ(HitBuffer.GetNumHits(),1); + } + } + + //tick solver one last time + { + FVec3 Grav(0,0,-1); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + Scene.EndFrame(); + } + + //now sim knows about it + { + FPBDRigidsEvolution* Evolution = Scene.GetSolver()->GetEvolution(); + const auto& SOA = Evolution->GetParticles(); + EXPECT_EQ(SOA.GetAllParticlesView().Num(),1); + } + + Particle->SetX(FVec3(5,0,0)); + + for(int Repeat = 0; Repeat < Delay; ++Repeat) + { + //tick solver + { + FVec3 Grav(0,0,-1); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + Scene.EndFrame(); + } + + //make sure sim hasn't seen new X yet + { + FPBDRigidsEvolution* Evolution = Scene.GetSolver()->GetEvolution(); + const auto& SOA = Evolution->GetParticles(); + const auto& InternalParticle = *SOA.GetAllParticlesView().Begin(); + EXPECT_EQ(InternalParticle.X()[0],0); + } + } + + //tick solver one last time + { + FVec3 Grav(0,0,-1); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + Scene.EndFrame(); + } + + //now sim knows about new X + { + FPBDRigidsEvolution* Evolution = Scene.GetSolver()->GetEvolution(); + const auto& SOA = Evolution->GetParticles(); + const auto& InternalParticle = *SOA.GetAllParticlesView().Begin(); + EXPECT_EQ(InternalParticle.X()[0],5); + } + + //make sure commands are also deferred + + int Count = 0; + int ExternalCount = 0; + const auto Lambda = [&]() + { + ++Count; + EXPECT_EQ(Count,1); //only hit once on internal thread + EXPECT_EQ(ExternalCount,Delay); //internal hits with expected delay + }; + + Scene.GetSolver()->EnqueueCommandImmediate(Lambda); + + for(int Repeat = 0; Repeat < Delay+1; ++Repeat) + { + //tick solver + FVec3 Grav(0,0,-1); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + Scene.EndFrame(); + + ++ExternalCount; + } + + } + + } + + GTEST_TEST(EngineInterface,RemoveDelayed) + { + for(int Delay = 0; Delay < 4; ++Delay) + { + FChaosScene Scene(nullptr); + Scene.GetSolver()->SetThreadingMode_External(EThreadingModeTemp::SingleThread); + Scene.GetSolver()->SetEnabled(true); + Scene.GetSolver()->GetMarshallingManager().SetTickDelay_External(Delay); + + FActorCreationParams Params; + Params.Scene = &Scene; + + Params.bSimulatePhysics = true; //simulate so that sync body is triggered + Params.bStartAwake = true; + + TGeometryParticle* Particle = nullptr; + FChaosEngineInterface::CreateActor(Params,Particle); + EXPECT_NE(Particle,nullptr); + + { + auto Sphere = MakeUnique>(FVec3(0),3); + Particle->SetGeometry(MoveTemp(Sphere)); + auto Simulated = static_cast*>(Particle); + Simulated->SetV(FVec3(0,0,-1)); + } + + + //make second simulating particle that we don't delete. Needed to trigger a sync + //this is because some data is cleaned up on GT immediately + TGeometryParticle* Particle2 = nullptr; + FChaosEngineInterface::CreateActor(Params,Particle2); + EXPECT_NE(Particle2,nullptr); + { + auto Sphere = MakeUnique>(FVec3(0),3); + Particle2->SetGeometry(MoveTemp(Sphere)); + auto Simulated = static_cast*>(Particle2); + Simulated->SetV(FVec3(0,-1,0)); + } + + //create actor + TArray*> Particles ={Particle, Particle2}; + Scene.AddActorsToScene_AssumesLocked(Particles); + + //tick until it's being synced from sim + for(int Repeat = 0; Repeat < Delay; ++Repeat) + { + { + FVec3 Grav(0,0,0); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + Scene.EndFrame(); + } + } + + //x starts at 0 + EXPECT_NEAR(Particle->X()[2],0, 1e-4); + EXPECT_NEAR(Particle2->X()[1],0, 1e-4); + + //tick solver and see new position synced from sim + { + FVec3 Grav(0,0,0); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + Scene.EndFrame(); + EXPECT_NEAR(Particle->X()[2],-1, 1e-4); + EXPECT_NEAR(Particle2->X()[1],-1, 1e-4); + } + + //tick solver and delete in between solver finishing and sync + { + FVec3 Grav(0,0,0); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + + //delete particle + FChaosEngineInterface::ReleaseActor(Particle,&Scene); + + Scene.EndFrame(); + EXPECT_NEAR(Particle2->X()[1],-2, 1e-4); //other particle keeps moving + } + + + //tick again and don't crash + for(int Repeat = 0; Repeat < Delay + 1; ++Repeat) + { + { + FVec3 Grav(0,0,0); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + Scene.EndFrame(); + EXPECT_NEAR(Particle2->X()[1],-3 - Repeat, 1e-4); //other particle keeps moving + } + } + } + } + + GTEST_TEST(EngineInterface, SimRoundTrip) + { + FChaosScene Scene(nullptr); + Scene.GetSolver()->SetThreadingMode_External(EThreadingModeTemp::SingleThread); + Scene.GetSolver()->SetEnabled(true); + + FActorCreationParams Params; + Params.Scene = &Scene; + + TGeometryParticle* Particle = nullptr; + + FChaosEngineInterface::CreateActor(Params,Particle); + + { + auto Sphere = MakeUnique>(FVec3(0),3); + Particle->SetGeometry(MoveTemp(Sphere)); + } + TPBDRigidParticle* Simulated = static_cast*>(Particle); + + TArray*> Particles ={Particle}; + Scene.AddActorsToScene_AssumesLocked(Particles); + Simulated->SetObjectState(EObjectStateType::Dynamic); + Simulated->SetF(FVec3(0,0,10) * Simulated->M()); + + FVec3 Grav(0,0,0); + Scene.SetUpForFrame(&Grav,1,99999,99999,10,false); + Scene.StartFrame(); + Scene.EndFrame(); + + //integration happened and we get results back + EXPECT_EQ(Simulated->X(),FVec3(0,0,10)); + EXPECT_EQ(Simulated->V(),FVec3(0,0,10)); + + } } diff --git a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestRewind.cpp b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestRewind.cpp index 551965c9be22..d990d930acbd 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestRewind.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestRewind.cpp @@ -640,12 +640,21 @@ namespace ChaosTest { // Throw out the proxy Solver->UnregisterObject(Particle.Get()); - - // State should be the same as being at head because we removed it from solver + + //Unregister enqueues commands which won't run until next tick. + //Use this callback to inspect state after commands, but before sim of next step + Solver->RegisterSimOneShotCallback([&]() { - const FGeometryParticleState State = RewindData->GetPastStateAtFrame(*Particle,5); - EXPECT_EQ(Particle->X(), State.X()); - } + // State should be the same as being at head because we removed it from solver + { + const FGeometryParticleState State = RewindData->GetPastStateAtFrame(*Particle,5); + EXPECT_EQ(Particle->X(),State.X()); + } + }); + + TickSolverHelper(Module,Solver); + + Module->DestroySolver(Solver); } diff --git a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestSim.cpp b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestSim.cpp index af931f2d6f94..d427fab4da79 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestSim.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestSim.cpp @@ -8,11 +8,56 @@ #include "Chaos/Sphere.h" #include "Chaos/Utilities.h" #include "Modules/ModuleManager.h" +#include "ChaosSolversModule.h" +#include "PBDRigidsSolver.h" +#include "GeometryCollection/GeometryCollectionTestFramework.h" namespace ChaosTest { using namespace Chaos; + TYPED_TEST(AllTraits, SimTests_SphereSphereSimTest_StaticBoundsChange) + { + // This test spawns a dynamic and a static, then moves the static around a few times after initialization. + // The goal is to make sure that the bounds are updated correctly and the dynamic rests on top of the static + // in its final position. + + auto Sphere = TSharedPtr(new TSphere(TVector(0), 10)); + + // Create solver #TODO make TFramework a little more general instead of mostly geometry collection focused + GeometryCollectionTest::TFramework Framework; + Framework.Solver->SetEnabled(true); + + // Make a particle + TUniquePtr> Particle = Chaos::TPBDRigidParticle::CreateParticle(); + Particle->SetGeometry(Sphere); + Particle->SetX(TVector(1000, 1000, 200)); + Particle->SetGravityEnabled(true); + Framework.Solver->RegisterObject(Particle.Get()); + + TUniquePtr> Static = Chaos::TGeometryParticle::CreateParticle(); + Static->SetGeometry(Sphere); + Static->SetX(TVector(0, 0, 0)); + Framework.Solver->RegisterObject(Static.Get()); + + Static->SetX(TVector(2000, 1000, 0)); + Static->SetX(TVector(3000, 1000, 0)); + + ::ChaosTest::SetParticleSimDataToCollide({ Particle.Get(), Static.Get() }); + + for(int32 Iter = 0; Iter < 200; ++Iter) + { + Framework.Advance(); + + if(Iter == 0) + { + Static->SetX(TVector(1000, 1000, 0)); + } + } + + EXPECT_NEAR(Particle->X().Z, 20, 1); + } + TYPED_TEST(AllEvolutions, SimTests_SphereSphereSimTest) { using TEvolution = TypeParam; @@ -25,9 +70,9 @@ namespace ChaosTest { TUniquePtr PhysicsMaterial = MakeUnique(); PhysicsMaterial->SleepCounterThreshold = 2; - TUniquePtr Box(new TSphere(FVec3(0, 0, 0), 50)); - Static->SetGeometry(MakeSerializable(Box)); - Dynamic->SetGeometry(MakeSerializable(Box)); + TUniquePtr Sphere(new TSphere(FVec3(0, 0, 0), 50)); + Static->SetGeometry(MakeSerializable(Sphere)); + Dynamic->SetGeometry(MakeSerializable(Sphere)); Evolution.SetPhysicsMaterial(Dynamic, MakeSerializable(PhysicsMaterial)); @@ -36,6 +81,9 @@ namespace ChaosTest { Dynamic->I() = FMatrix33(100000.0f, 100000.0f, 100000.0f); Dynamic->InvI() = FMatrix33(1.0f / 100000.0f, 1.0f / 100000.0f, 1.0f / 100000.0f); + // The position of the static has changed and statics don't automatically update bounds, so update explicitly + Static->SetWorldSpaceInflatedBounds(Sphere->BoundingBox().TransformedAABB(TRigidTransform(Static->X(), Static->R()))); + ::ChaosTest::SetParticleSimDataToCollide({ Static,Dynamic }); @@ -136,6 +184,11 @@ namespace ChaosTest { TUniquePtr PhysicsMaterial = MakeUnique(); PhysicsMaterial->SleepingLinearThreshold = 20; PhysicsMaterial->SleepingAngularThreshold = 20; + PhysicsMaterial->SleepCounterThreshold = 1; + + Static->X() = FVec3(10, 10, 10); + Dynamic1->X() = FVec3(10, 10, 120); + Dynamic2->X() = FVec3(10, 10, 400); TUniquePtr StaticBox(new TBox(FVec3(-500, -500, -50), FVec3(500, 500, 50))); TUniquePtr DynamicBox(new TBox(FVec3(-50, -50, -50), FVec3(50, 50, 50))); @@ -146,9 +199,6 @@ namespace ChaosTest { Evolution.SetPhysicsMaterial(Dynamic1, MakeSerializable(PhysicsMaterial)); Evolution.SetPhysicsMaterial(Dynamic2, MakeSerializable(PhysicsMaterial)); - Static->X() = FVec3(10, 10, 10); - Dynamic1->X() = FVec3(10, 10, 120); - Dynamic2->X() = FVec3(10, 10, 400); Dynamic1->I() = FMatrix33(100000.0f, 100000.0f, 100000.0f); Dynamic1->InvI() = FMatrix33(1.0f / 100000.0f, 1.0f / 100000.0f, 1.0f / 100000.0f); Dynamic2->I() = FMatrix33(100000.0f, 100000.0f, 100000.0f); diff --git a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestUtility.cpp b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestUtility.cpp index 2e8f08740889..25841ad8c243 100644 --- a/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestUtility.cpp +++ b/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestUtility.cpp @@ -194,6 +194,11 @@ namespace ChaosTest { Particle->CollisionParticles()->X(CollisionIndex++) = TVector(-Scale[0] / 2.f, +Scale[1] / 2.f, +Scale[2] / 2.f); Particle->CollisionParticles()->X(CollisionIndex++) = TVector(+Scale[0] / 2.f, +Scale[1] / 2.f, +Scale[2] / 2.f); + // TODO: Change this error prone API to set bounds more automatically. This is easy to forget + Particle->SetLocalBounds(TAABB(TVector(-Scale[0] / 2.f), TVector(Scale[0] / 2.f))); + Particle->SetWorldSpaceInflatedBounds(TAABB(TVector(-Scale[0] / 2.f), TVector(Scale[0] / 2.f))); + Particle->SetHasBounds(true); + if (OutElements != nullptr) { /* @@ -592,6 +597,11 @@ namespace ChaosTest { InParticles.P() = InParticles.X(); InParticles.Q() = InParticles.R(); + // TODO: Change this error prone API to set bounds more automatically. This is easy to forget + InParticles.SetLocalBounds(TAABB(Cube.X(0), Cube.X(7))); + InParticles.SetWorldSpaceInflatedBounds(TAABB(Cube.X(0), Cube.X(7))); + InParticles.SetHasBounds(true); + InParticles.M() = 1.f; InParticles.InvM() = 1.f; InParticles.I() = PMatrix(1.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 1.f); diff --git a/Engine/Source/Programs/IncludeTool/IncludeTool/CompileEnvironment.cs b/Engine/Source/Programs/IncludeTool/IncludeTool/CompileEnvironment.cs index a95711aaea3c..04e3aaa14d89 100644 --- a/Engine/Source/Programs/IncludeTool/IncludeTool/CompileEnvironment.cs +++ b/Engine/Source/Programs/IncludeTool/IncludeTool/CompileEnvironment.cs @@ -345,7 +345,14 @@ namespace IncludeTool { if(!SourceFile.HasExtension(".a")) { - NewFileToEnvironment.Add(SourceFile, Environment); + if(NewFileToEnvironment.ContainsKey(SourceFile)) + { + Console.WriteLine("Source file {0} is compiled with multiple environments", SourceFile); + } + else + { + NewFileToEnvironment.Add(SourceFile, Environment); + } } } } diff --git a/Engine/Source/Programs/IncludeTool/IncludeTool/Rules.cs b/Engine/Source/Programs/IncludeTool/IncludeTool/Rules.cs index 3ab12ead8b7a..977d1da0dd66 100644 --- a/Engine/Source/Programs/IncludeTool/IncludeTool/Rules.cs +++ b/Engine/Source/Programs/IncludeTool/IncludeTool/Rules.cs @@ -423,8 +423,6 @@ namespace IncludeTool "/Engine/Source/Runtime/Experimental/Chaos/Public/Chaos/GeometryParticlesfwd.h", // invalid forward declaration - 'namespace Chaos' "/Engine/Source/Runtime/Experimental/Chaos/Public/Chaos/PBDRigidsEvolutionFwd.h", // invalid forward declaration - 'namespace Chaos' "/Engine/Source/Runtime/Experimental/ChaosSolvers/Public/PhysicsProxy/SingleParticlePhysicsProxyFwd.h", // invalid forward declaration - 'namespace Chaos' - "/Engine/Plugins/Experimental/ForwardingChannels/Source/ForwardingChannels/Public/ForwardingChannelsFwd.h", // invalid forward declaration - 'namespace ForwardingChannels' - "/Engine/Plugins/Experimental/LiveStreamAnimation/Source/LiveStreamAnimation/Public/LiveStreamAnimationFwd.h", // invalid forward declaration - 'namespace LiveStreamAnimation' }; /// diff --git a/Engine/Source/Programs/LiveCodingConsole/Private/SLogWidget.cpp b/Engine/Source/Programs/LiveCodingConsole/Private/SLogWidget.cpp index c96fda305197..3a60e49312e7 100644 --- a/Engine/Source/Programs/LiveCodingConsole/Private/SLogWidget.cpp +++ b/Engine/Source/Programs/LiveCodingConsole/Private/SLogWidget.cpp @@ -100,6 +100,7 @@ void SLogWidget::Construct(const FArguments& InArgs) .Marshaller(MessagesTextMarshaller) .IsReadOnly(true) .AlwaysShowScrollbars(true) + .SelectWordOnMouseDoubleClick(false) .OnHScrollBarUserScrolled(this, &SLogWidget::OnScrollX) .OnVScrollBarUserScrolled(this, &SLogWidget::OnScrollY) ] @@ -193,6 +194,8 @@ FReply SLogWidget::OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const ISourceCodeAccessModule& SourceCodeAccessModule = FModuleManager::LoadModuleChecked("SourceCodeAccess"); SourceCodeAccessModule.GetAccessor().OpenFileAtLine(PotentialCodeFilePath, LineNumber); } + + return FReply::Handled(); } return FReply::Unhandled(); diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildPlatform.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildPlatform.cs index 73edcab0cc74..a9dd5f6cb261 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildPlatform.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildPlatform.cs @@ -162,6 +162,15 @@ namespace UnrealBuildTool return UEBuildPlatform.GetSDK(Platform).HasRequiredSDKsInstalled(); } + /// + /// Returns SDK string as required by the platform + /// + /// Valid SDK string + public string GetRequiredSDKString() + { + return UEBuildPlatform.GetSDK(Platform).GetRequiredSDKString(); + } + /// /// Whether this platform requires specific Visual Studio version. /// diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs index 31bf25530e37..50f7ffaaf79b 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs @@ -1693,38 +1693,6 @@ namespace UnrealBuildTool } } - // Add global definitions for project-specific binaries. HACK: Also defining for monolithic builds in binary releases. Might be better to set this via command line instead? - if(!bUseSharedBuildEnvironment || bCompileMonolithic) - { - UEBuildBinary ExecutableBinary = Binaries[0]; - - bool IsCurrentPlatform; - if (Utils.IsRunningOnMono) - { - IsCurrentPlatform = Platform == UnrealTargetPlatform.Mac || (UEBuildPlatform.IsPlatformInGroup(Platform, UnrealPlatformGroup.Unix) && Platform == BuildHostPlatform.Current.Platform); - } - else - { - IsCurrentPlatform = Platform.IsInGroup(UnrealPlatformGroup.Windows) || Platform == UnrealTargetPlatform.HoloLens; - } - - if (IsCurrentPlatform) - { - // The hardcoded engine directory needs to be a relative path to match the normal EngineDir format. Not doing so breaks the network file system (TTP#315861). - string OutputFilePath = ExecutableBinary.OutputFilePath.FullName; - if (Platform == UnrealTargetPlatform.Mac && OutputFilePath.Contains(".app/Contents/MacOS")) - { - OutputFilePath = OutputFilePath.Substring(0, OutputFilePath.LastIndexOf(".app/Contents/MacOS") + 4); - } - string EnginePath = Utils.CleanDirectorySeparators(UnrealBuildTool.EngineDirectory.MakeRelativeTo(ExecutableBinary.OutputFilePath.Directory), '/'); - if (EnginePath.EndsWith("/") == false) - { - EnginePath += "/"; - } - GlobalCompileEnvironment.Definitions.Add(String.Format("UE_ENGINE_DIRECTORY=\"{0}\"", EnginePath)); - } - } - // On Mac and Linux we have actions that should be executed after all the binaries are created TargetToolChain.SetupBundleDependencies(Binaries, TargetName); @@ -3763,6 +3731,38 @@ namespace UnrealBuildTool // Set the global app name GlobalCompileEnvironment.Definitions.Add(String.Format("UE_APP_NAME=\"{0}\"", AppName)); + // Add global definitions for project-specific binaries. HACK: Also defining for monolithic builds in binary releases. Might be better to set this via command line instead? + if (!bUseSharedBuildEnvironment || bCompileMonolithic) + { + UEBuildBinary ExecutableBinary = Binaries[0]; + + bool IsCurrentPlatform; + if (Utils.IsRunningOnMono) + { + IsCurrentPlatform = Platform == UnrealTargetPlatform.Mac || (UEBuildPlatform.IsPlatformInGroup(Platform, UnrealPlatformGroup.Unix) && Platform == BuildHostPlatform.Current.Platform); + } + else + { + IsCurrentPlatform = Platform.IsInGroup(UnrealPlatformGroup.Windows) || Platform == UnrealTargetPlatform.HoloLens; + } + + if (IsCurrentPlatform) + { + // The hardcoded engine directory needs to be a relative path to match the normal EngineDir format. Not doing so breaks the network file system (TTP#315861). + string OutputFilePath = ExecutableBinary.OutputFilePath.FullName; + if (Platform == UnrealTargetPlatform.Mac && OutputFilePath.Contains(".app/Contents/MacOS")) + { + OutputFilePath = OutputFilePath.Substring(0, OutputFilePath.LastIndexOf(".app/Contents/MacOS") + 4); + } + string EnginePath = Utils.CleanDirectorySeparators(UnrealBuildTool.EngineDirectory.MakeRelativeTo(ExecutableBinary.OutputFilePath.Directory), '/'); + if (EnginePath.EndsWith("/") == false) + { + EnginePath += "/"; + } + GlobalCompileEnvironment.Definitions.Add(String.Format("UE_ENGINE_DIRECTORY=\"{0}\"", EnginePath)); + } + } + // Initialize the compile and link environments for the platform, configuration, and project. BuildPlatform.SetUpEnvironment(Rules, GlobalCompileEnvironment, GlobalLinkEnvironment); BuildPlatform.SetUpConfigurationEnvironment(Rules, GlobalCompileEnvironment, GlobalLinkEnvironment); diff --git a/Engine/Source/Programs/UnrealBuildTool/Modes/BuildMode.cs b/Engine/Source/Programs/UnrealBuildTool/Modes/BuildMode.cs index aca84a5e938d..7b0ce502f27a 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Modes/BuildMode.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Modes/BuildMode.cs @@ -338,6 +338,20 @@ namespace UnrealBuildTool // Get all the actions that are prerequisites for these targets. This forms the list of actions that we want executed. List PrerequisiteActions = ActionGraph.GatherPrerequisiteActions(MergedActions, MergedOutputItems); + // Create the action history + ActionHistory History = new ActionHistory(); + for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) + { + using (Timeline.ScopeEvent("Reading action history")) + { + TargetDescriptor TargetDescriptor = TargetDescriptors[TargetIdx]; + if(TargetDescriptor.ProjectFile != null) + { + History.Mount(TargetDescriptor.ProjectFile.Directory); + } + } + } + // Figure out which actions need to be built Dictionary ActionToOutdatedFlag = new Dictionary(); for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) @@ -351,13 +365,6 @@ namespace UnrealBuildTool CppDependencies = CppDependencyCache.CreateHierarchy(TargetDescriptor.ProjectFile, TargetDescriptor.Name, TargetDescriptor.Platform, TargetDescriptor.Configuration, Makefiles[TargetIdx].TargetType, TargetDescriptor.Architecture); } - // Create the action history - ActionHistory History; - using (Timeline.ScopeEvent("Reading action history")) - { - History = ActionHistory.CreateHierarchy(TargetDescriptor.ProjectFile, TargetDescriptor.Name, TargetDescriptor.Platform, Makefiles[TargetIdx].TargetType, TargetDescriptor.Architecture); - } - // Plan the actions to execute for the build. For single file compiles, always rebuild the source file regardless of whether it's out of date. if (TargetDescriptor.SpecificFilesToCompile.Count == 0) { @@ -428,7 +435,7 @@ namespace UnrealBuildTool // Save all the action histories now that files have been removed. We have to do this after deleting produced items to ensure that any // items created during the build don't have the wrong command line. - ActionHistory.SaveAll(); + History.Save(); // Create directories for the outdated produced items. ActionGraph.CreateDirectoriesForProducedItems(MergedActionsToExecute); diff --git a/Engine/Source/Programs/UnrealBuildTool/Modes/CleanMode.cs b/Engine/Source/Programs/UnrealBuildTool/Modes/CleanMode.cs index bcb710bfb173..e063bd535376 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Modes/CleanMode.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Modes/CleanMode.cs @@ -153,7 +153,6 @@ namespace UnrealBuildTool // Add all the makefiles and caches to be deleted FilesToDelete.Add(TargetMakefile.GetLocation(Target.ProjectFile, Target.Name, Target.Platform, Target.Configuration)); FilesToDelete.UnionWith(SourceFileMetadataCache.GetFilesToClean(Target.ProjectFile)); - FilesToDelete.UnionWith(ActionHistory.GetFilesToClean(Target.ProjectFile, Target.Name, Target.Platform, Target.Type, Target.Architecture)); // Add all the intermediate folders to be deleted foreach (DirectoryReference BaseDir in BaseDirs) diff --git a/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateClangDatabase.cs b/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateClangDatabase.cs index 568805c936e7..66044e95bf08 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateClangDatabase.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateClangDatabase.cs @@ -94,6 +94,16 @@ namespace UnrealBuildTool StringBuilder CommandBuilder = new StringBuilder(); CommandBuilder.AppendFormat("\"{0}\"", ClangPath.FullName); + + if (ModuleCompileEnvironment.CppStandard >= CppStandardVersion.Cpp17) + { + CommandBuilder.AppendFormat(" -std=c++17"); + } + else if (ModuleCompileEnvironment.CppStandard >= CppStandardVersion.Cpp14) + { + CommandBuilder.AppendFormat(" -std=c++14"); + } + foreach (FileItem ForceIncludeFile in ModuleCompileEnvironment.ForceIncludeFiles) { CommandBuilder.AppendFormat(" -include \"{0}\"", ForceIncludeFile.FullName); diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Android/AndroidPlatformSDK.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Android/AndroidPlatformSDK.cs index e0bd711026ba..fa88272c924e 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Android/AndroidPlatformSDK.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Android/AndroidPlatformSDK.cs @@ -116,7 +116,7 @@ namespace UnrealBuildTool return true; } - protected override string GetRequiredSDKString() + public override string GetRequiredSDKString() { return GetDesiredVersion(); } diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/UEBuildIOS.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/UEBuildIOS.cs index 59d89f6b9edf..2bb4d70f10ab 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/UEBuildIOS.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/UEBuildIOS.cs @@ -1127,7 +1127,7 @@ namespace UnrealBuildTool // if the project has an Oodle compression Dll, enable the decompressor on IOS if (Target.ProjectFile != null) { - DirectoryReference ProjectDir = DirectoryReference.GetParentDirectory(Target.ProjectFile); + DirectoryReference ProjectDir = Target.ProjectFile.Directory; string OodleDllPath = DirectoryReference.Combine(ProjectDir, "Binaries/ThirdParty/Oodle/Mac/libUnrealPakPlugin.dylib").FullName; if (File.Exists(OodleDllPath)) { diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxPlatformSDK.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxPlatformSDK.cs index 8037f2e425bc..860fcc2a8ff6 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxPlatformSDK.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxPlatformSDK.cs @@ -134,7 +134,7 @@ namespace UnrealBuildTool /// Returns SDK string as required by the platform /// /// Valid SDK string - protected override string GetRequiredSDKString() + public override string GetRequiredSDKString() { return GetDesiredVersion(); } diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Lumin/LuminPlatformSDK.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Lumin/LuminPlatformSDK.cs index ed22683b2bbb..0810e3777cf6 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Lumin/LuminPlatformSDK.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Lumin/LuminPlatformSDK.cs @@ -86,7 +86,7 @@ namespace UnrealBuildTool return true; } - protected override string GetRequiredSDKString() + public override string GetRequiredSDKString() { return GetDesiredVersion(); } diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/MacToolChain.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/MacToolChain.cs index 8fe7ee0330e7..e7f3b2e7917c 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/MacToolChain.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/MacToolChain.cs @@ -225,7 +225,7 @@ namespace UnrealBuildTool Result += " -c"; // Pass through the list of architectures - Result += string.Format(" -arch {0}", string.Join(" ", Architectures)); + Result += string.Format(" -arch {0}", string.Join(" -arch ", Architectures)); Result += " -isysroot " + Settings.BaseSDKDir + "/MacOSX" + Settings.MacOSSDKVersion + ".sdk"; Result += " -mmacosx-version-min=" + (CompileEnvironment.bEnableOSX109Support ? "10.9" : Settings.MacOSVersion); diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/UEBuildMac.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/UEBuildMac.cs index 4b521cbc6aab..1fa2e14efddd 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/UEBuildMac.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/UEBuildMac.cs @@ -21,11 +21,14 @@ namespace UnrealBuildTool public List Architectures = new List(); /// - /// Whether to generate dSYM files. - /// Lists Architectures that you want to build. + /// Whether to generate dSYM files. Defaults to true for Shipping builds and false + /// for all other builds. -dsym will force generation of a dsym for other builds, + /// -NoDsym will force it off for shipping. /// + [CommandLine("-dSYM", Value = "true")] + [CommandLine("-NoDSYM", Value = "false")] [XmlConfigFile(Category = "BuildConfiguration", Name = "bGeneratedSYMFile")] - public bool bGenerateDsymFile = true; + public bool? bGenerateDsymFile; /// /// Enables address sanitizer (ASan). @@ -76,7 +79,7 @@ namespace UnrealBuildTool #pragma warning disable CS1591 #endif - public bool bGenerateDsymFile + public bool? bGenerateDsymFile { get { return Inner.bGenerateDsymFile; } } @@ -153,7 +156,14 @@ namespace UnrealBuildTool Target.GlobalDefinitions.Add("FORCE_ANSI_ALLOCATOR=1"); } - Target.bUsePDBFiles = !Target.bDisableDebugInfo && Target.Configuration != UnrealTargetConfiguration.Debug && Platform == UnrealTargetPlatform.Mac && Target.MacPlatform.bGenerateDsymFile; + Target.GlobalDefinitions.Add("GL_SILENCE_DEPRECATION=1"); + + bool IsBuildMachine = Environment.GetEnvironmentVariable("IsBuildMachine") == "1"; + + // If the user explicitly provided an option for dSYM's then do that. If they did not, then we want one for shipping builds or if we're a build machine + bool WantDsym = Target.MacPlatform.bGenerateDsymFile ?? (Target.Configuration == UnrealTargetConfiguration.Shipping || IsBuildMachine); + + Target.bUsePDBFiles = !Target.bDisableDebugInfo && WantDsym; // we always deploy - the build machines need to be able to copy the files back, which needs the full bundle Target.bDeployAfterCompile = true; diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/UEBuildWindows.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/UEBuildWindows.cs index cbd5578f204b..d6855cb9c1f4 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/UEBuildWindows.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/UEBuildWindows.cs @@ -1127,6 +1127,23 @@ namespace UnrealBuildTool } } } + + // Check for LLVM_PATH environment variable. + string LLVMPath = Environment.GetEnvironmentVariable("LLVM_PATH"); + if (!String.IsNullOrEmpty(LLVMPath)) + { + DirectoryReference LLVMPathDir = new DirectoryReference(LLVMPath); + if (IsValidToolChainDirClang(LLVMPathDir)) + { + FileReference CompilerFile = FileReference.Combine(LLVMPathDir, "bin", "clang-cl.exe"); + if (FileReference.Exists(CompilerFile)) + { + FileVersionInfo VersionInfo = FileVersionInfo.GetVersionInfo(CompilerFile.FullName); + VersionNumber Version = new VersionNumber(VersionInfo.FileMajorPart, VersionInfo.FileMinorPart, VersionInfo.FileBuildPart); + ToolChainInstallations[Version] = new ToolChainInstallation(LLVMPathDir, false); + } + } + } } else if(Compiler == WindowsCompiler.Intel) { diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCEnvironment.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCEnvironment.cs index 1bd77a28c457..39b281594a9b 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCEnvironment.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCEnvironment.cs @@ -191,7 +191,7 @@ namespace UnrealBuildTool /// Base directory for the VC toolchain /// Target Architecture /// Directory containing the 32-bit toolchain binaries - static DirectoryReference GetVCToolPath(WindowsCompiler Compiler, DirectoryReference VCToolChainDir, WindowsArchitecture Architecture) + public static DirectoryReference GetVCToolPath(WindowsCompiler Compiler, DirectoryReference VCToolChainDir, WindowsArchitecture Architecture) { if (Compiler >= WindowsCompiler.VisualStudio2017) { diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCToolChain.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCToolChain.cs index 09d614cca5c1..5d7651f118cc 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCToolChain.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCToolChain.cs @@ -500,11 +500,18 @@ namespace UnrealBuildTool Arguments.Add("/wd4463"); // 4463 - overflow; assigning 1 to bit-field that can only hold values from -1 to 0 } - if(CompileEnvironment.bEnableUndefinedIdentifierWarnings) + if (CompileEnvironment.bEnableUndefinedIdentifierWarnings) { if (CompileEnvironment.bUndefinedIdentifierWarningsAsErrors) { - Arguments.Add("/we4668"); + if (Target.WindowsPlatform.Compiler == WindowsCompiler.VisualStudio2015_DEPRECATED) + { + Arguments.Add("/we4668"); + } + else if (Target.WindowsPlatform.Compiler == WindowsCompiler.VisualStudio2017) + { + Arguments.Add("/wd4668"); + } } else { diff --git a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/ProjectFileGenerator.cs b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/ProjectFileGenerator.cs index a4afb77592e7..cffc4c6064f2 100644 --- a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/ProjectFileGenerator.cs +++ b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/ProjectFileGenerator.cs @@ -1853,6 +1853,7 @@ namespace UnrealBuildTool // Create the target UEBuildTarget Target = UEBuildTarget.Create(TargetDesc, false, bUsePrecompiled); + AddTargetForIntellisense(Target); // Generate a compile environment for each module in the binary CppCompileEnvironment GlobalCompileEnvironment = Target.CreateCompileEnvironmentForProjectFiles(); foreach(UEBuildBinary Binary in Target.Binaries) @@ -1890,6 +1891,11 @@ namespace UnrealBuildTool } } + protected virtual void AddTargetForIntellisense(UEBuildTarget Target) + { + + } + /// /// Selects which platforms and build configurations we want in the project file @@ -2223,6 +2229,25 @@ namespace UnrealBuildTool GameProjects = new List(); ProgramProjects = new Dictionary(); + // Separate the .target.cs files that are platform extension specializations, per target name. These will be added alongside their base target.cs + Dictionary> AllSubTargetFilesPerTarget = new Dictionary>(); + HashSet AllSubTargetFiles = new HashSet(); + foreach( FileReference TargetFilePath in AllTargetFiles ) + { + string[] TargetPathSplit = TargetFilePath.GetFileNameWithoutAnyExtensions().Split(new char[]{'_'}, StringSplitOptions.RemoveEmptyEntries ); + if (TargetPathSplit.Length > 1 && (UnrealTargetPlatform.IsValidName(TargetPathSplit.Last()) || UnrealPlatformGroup.IsValidName(TargetPathSplit.Last()) ) ) + { + string TargetName = TargetPathSplit.First(); + if (!AllSubTargetFilesPerTarget.ContainsKey(TargetName)) + { + AllSubTargetFilesPerTarget.Add(TargetName, new List() ); + } + + AllSubTargetFilesPerTarget[TargetName].Add(TargetFilePath); + AllSubTargetFiles.Add(TargetFilePath); + } + } + // Get some standard directories //DirectoryReference EngineSourceProgramsDirectory = DirectoryReference.Combine(UnrealBuildTool.EngineSourceDirectory, "Programs"); DirectoryReference EnterpriseSourceProgramsDirectory = DirectoryReference.Combine(UnrealBuildTool.EnterpriseSourceDirectory, "Programs"); @@ -2230,7 +2255,7 @@ namespace UnrealBuildTool // Keep a cache of the default editor target for each project so that we don't have to interrogate the configs for each target Dictionary DefaultProjectEditorTargetCache = new Dictionary(); - foreach( FileReference TargetFilePath in AllTargetFiles ) + foreach( FileReference TargetFilePath in AllTargetFiles.Except(AllSubTargetFiles) ) { string TargetName = TargetFilePath.GetFileNameWithoutAnyExtensions(); // Remove both ".cs" and ".Target" @@ -2267,13 +2292,6 @@ namespace UnrealBuildTool } } - // skip target rules that are platform extension or platform group specializations - string[] TargetPathSplit = TargetFilePath.GetFileNameWithoutAnyExtensions().Split(new char[]{'_'}, StringSplitOptions.RemoveEmptyEntries ); - if (TargetPathSplit.Length > 1 && (UnrealTargetPlatform.IsValidName(TargetPathSplit.Last()) || UnrealPlatformGroup.IsValidName(TargetPathSplit.Last()) ) ) - { - WantProjectFileForTarget = false; - } - if (WantProjectFileForTarget) { RulesAssembly RulesAssembly; @@ -2493,6 +2511,13 @@ namespace UnrealBuildTool // Make sure the *.Target.cs file is in the project. ProjectFile.AddFileToProject(TargetFilePath, BaseFolder); + // Add all matching *_.Target.cs to the same folder + if (AllSubTargetFilesPerTarget.ContainsKey(TargetName)) + { + ProjectFile.AddFilesToProject( AllSubTargetFilesPerTarget[TargetName], BaseFolder ); + } + + Log.TraceVerbose("Generating target {0} for {1}", TargetRulesObject.Type.ToString(), ProjectFilePath); } } diff --git a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioCode/VSCodeProjectFileGenerator.cs b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioCode/VSCodeProjectFileGenerator.cs index 49ff6143a8e1..33c88d003ed1 100644 --- a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioCode/VSCodeProjectFileGenerator.cs +++ b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioCode/VSCodeProjectFileGenerator.cs @@ -6,6 +6,7 @@ using System.Text; using System.IO; using Tools.DotNETCommon; using System.Diagnostics; +using System.Linq; namespace UnrealBuildTool { @@ -40,6 +41,8 @@ namespace UnrealBuildTool private string FrameworkExecutableExtension; private string FrameworkLibraryExtension = ".dll"; + private readonly List BuildTargets = new List(); + /// /// Includes all files in the generated workspace. /// @@ -280,19 +283,91 @@ namespace UnrealBuildTool WriteTasksFile(ProjectData); WriteLaunchFile(ProjectData); WriteWorkspaceIgnoreFile(Projects); - WriteCppPropertiesFile(VSCodeDir, false, ProjectData); - WriteWorkspaceFile(ProjectData); - //WriteProjectDataFile(ProjectData); + WriteCppPropertiesFile(VSCodeDir, ProjectData); + WriteWorkspaceFile(); if (bForeignProject && bIncludeEngineSource) { // for installed builds we need to write the cpp properties file under the installed engine as well for intellisense to work DirectoryReference Ue4CodeDirectory = DirectoryReference.Combine(UnrealBuildTool.RootDirectory, ".vscode"); - WriteCppPropertiesFile(Ue4CodeDirectory, true, ProjectData); + WriteCppPropertiesFile(Ue4CodeDirectory, ProjectData); } return true; } + private class BuildTarget + { + public readonly string Name; + public readonly TargetType Type; + public readonly UnrealTargetPlatform Platform; + public readonly UnrealTargetConfiguration Configuration; + public readonly Dictionary ModuleCommandLines; + + public BuildTarget(string InName, TargetType InType, UnrealTargetPlatform InPlatform, UnrealTargetConfiguration InConfiguration, Dictionary InModulesCommandLines) + { + Name = InName; + Type = InType; + Platform = InPlatform; + Configuration = InConfiguration; + ModuleCommandLines = InModulesCommandLines; + } + + public override string ToString() + { + return Name.ToString() + " " + Type.ToString(); + } + } + + protected override void AddTargetForIntellisense(UEBuildTarget Target) + { + base.AddTargetForIntellisense(Target); + + // we do not need to keep track of which binary the invocation belongs to, only which target, as such we join all binaries into a single set + Dictionary ModuleDirectoryToCompileCommand = new Dictionary(); + + // Generate a compile environment for each module in the binary + CppCompileEnvironment GlobalCompileEnvironment = Target.CreateCompileEnvironmentForProjectFiles(); + foreach (UEBuildBinary Binary in Target.Binaries) + { + CppCompileEnvironment BinaryCompileEnvironment = Binary.CreateBinaryCompileEnvironment(GlobalCompileEnvironment); + foreach (UEBuildModuleCPP Module in Binary.Modules.OfType()) + { + CppCompileEnvironment ModuleCompileEnvironment = Module.CreateCompileEnvironmentForIntellisense(Target.Rules, BinaryCompileEnvironment); + + List ForceIncludePaths = new List(ModuleCompileEnvironment.ForceIncludeFiles.Select(x => x.Location)); + if (ModuleCompileEnvironment.PrecompiledHeaderIncludeFilename != null) + { + ForceIncludePaths.Add(ModuleCompileEnvironment.PrecompiledHeaderIncludeFilename); + } + + StringBuilder CommandBuilder = new StringBuilder(); + // Technically the command should include the compiler to use, but we only generate these for intellisense and thus the compiler is not needed + // And resolving the compiler when it would not be used is a waste of effort. + //CommandBuilder.AppendFormat("\"{0}\"", ClangPath.FullName); + + foreach (FileReference ForceIncludeFile in ForceIncludePaths) + { + CommandBuilder.AppendFormat(" -include \"{0}\"", ForceIncludeFile.FullName); + } + foreach (string Definition in ModuleCompileEnvironment.Definitions) + { + CommandBuilder.AppendFormat(" -D\"{0}\"", Definition); + } + foreach (DirectoryReference IncludePath in ModuleCompileEnvironment.UserIncludePaths) + { + CommandBuilder.AppendFormat(" -I\"{0}\"", IncludePath); + } + foreach (DirectoryReference IncludePath in ModuleCompileEnvironment.SystemIncludePaths) + { + CommandBuilder.AppendFormat(" -I\"{0}\"", IncludePath); + } + + ModuleDirectoryToCompileCommand.Add(Module.ModuleDirectory, CommandBuilder.ToString()); + } + } + + BuildTargets.Add(new BuildTarget(Target.TargetName, Target.TargetType, Target.Platform, Target.Configuration, ModuleDirectoryToCompileCommand)); + } private class ProjectData { @@ -326,7 +401,6 @@ namespace UnrealBuildTool public string Name; public TargetType Type; public List BuildProducts = new List(); - public List Defines = new List(); public Target(Project InParentProject, string InName, TargetType InType) { @@ -346,7 +420,6 @@ namespace UnrealBuildTool public string Name; public ProjectFile SourceProject; public List Targets = new List(); - public List IncludePaths; public override string ToString() { @@ -357,10 +430,9 @@ namespace UnrealBuildTool public List NativeProjects = new List(); public List CSharpProjects = new List(); public List AllProjects = new List(); - public List CombinedIncludePaths = new List(); - public List CombinedPreprocessorDefinitions = new List(); } + private ProjectData GatherProjectData(List InProjects, PlatformProjectGeneratorCollection PlatformProjectGenerators) { ProjectData ProjectData = new ProjectData(); @@ -412,53 +484,6 @@ namespace UnrealBuildTool } } } - - NewTarget.Defines.AddRange(Target.TargetRules.GlobalDefinitions); - } - - NewProject.IncludePaths = new List(); - - List RawIncludes = new List(Project.IntelliSenseIncludeSearchPaths); - RawIncludes.AddRange(Project.IntelliSenseSystemIncludeSearchPaths); - - if (HostPlatform == UnrealTargetPlatform.Win64) - { - RawIncludes.AddRange(VCToolChain.GetVCIncludePaths(UnrealTargetPlatform.Win64, WindowsPlatform.GetDefaultCompiler(null), null).Trim(';').Split(';')); - } - else - { - RawIncludes.Add("/usr/include"); - RawIncludes.Add("/usr/local/include"); - } - - foreach (string IncludePath in RawIncludes) - { - DirectoryReference AbsPath = DirectoryReference.Combine(Project.ProjectFilePath.Directory, IncludePath); - if (DirectoryReference.Exists(AbsPath)) - { - string Processed = MakeUnquotedPathString(AbsPath, EPathType.Absolute); - if (!NewProject.IncludePaths.Contains(Processed)) - { - NewProject.IncludePaths.Add(Processed); - } - - if (!ProjectData.CombinedIncludePaths.Contains(Processed)) - { - ProjectData.CombinedIncludePaths.Add(Processed); - } - - } - } - - foreach (string Definition in Project.IntelliSensePreprocessorDefinitions) - { - string Processed = Definition.Replace("\"", "\\\""); - // removing trailing spaces on preprocessor definitions as these can be added as empty defines confusing vscode - Processed = Processed.TrimEnd(' '); - if (!ProjectData.CombinedPreprocessorDefinitions.Contains(Processed)) - { - ProjectData.CombinedPreprocessorDefinitions.Add(Processed); - } } ProjectData.NativeProjects.Add(NewProject); @@ -532,73 +557,7 @@ namespace UnrealBuildTool return ProjectData; } - private void WriteProjectDataFile(ProjectData ProjectData) - { - JsonFile OutFile = new JsonFile(); - - OutFile.BeginRootObject(); - OutFile.BeginArray("Projects"); - - foreach (ProjectData.Project Project in ProjectData.AllProjects) - { - foreach (ProjectData.Target Target in Project.Targets) - { - OutFile.BeginObject(); - { - OutFile.AddField("Name", Project.Name); - OutFile.AddField("Type", Target.Type.ToString()); - - if (Project.SourceProject is VCSharpProjectFile) - { - OutFile.AddField("ProjectFile", MakePathString(Project.SourceProject.ProjectFilePath)); - } - else - { - OutFile.BeginArray("IncludePaths"); - { - foreach (string IncludePath in Project.IncludePaths) - { - OutFile.AddUnnamedField(IncludePath); - } - } - OutFile.EndArray(); - } - - OutFile.BeginArray("Defines"); - { - foreach (string Define in Target.Defines) - { - OutFile.AddUnnamedField(Define); - } - } - OutFile.EndArray(); - - OutFile.BeginArray("BuildProducts"); - { - foreach (ProjectData.BuildProduct BuildProduct in Target.BuildProducts) - { - OutFile.BeginObject(); - { - OutFile.AddField("Platform", BuildProduct.Platform.ToString()); - OutFile.AddField("Config", BuildProduct.Config.ToString()); - OutFile.AddField("OutputFile", MakePathString(BuildProduct.OutputFile)); - OutFile.AddField("OutputType", BuildProduct.OutputType.ToString()); - } - OutFile.EndObject(); - } - } - OutFile.EndArray(); - } - OutFile.EndObject(); - } - } - - OutFile.EndArray(); - OutFile.EndRootObject(); - OutFile.Write(FileReference.Combine(VSCodeDir, "unreal.json")); - } - - private void WriteCppPropertiesFile(DirectoryReference OutputDirectory, bool RewriteIncludePaths, ProjectData Projects) + private void WriteCppPropertiesFile(DirectoryReference OutputDirectory, ProjectData Projects) { DirectoryReference.CreateDirectory(OutputDirectory); @@ -608,55 +567,47 @@ namespace UnrealBuildTool { OutFile.BeginArray("configurations"); { - OutFile.BeginObject(); + foreach (ProjectData.Project Project in Projects.AllProjects) { - OutFile.AddField("name", "UnrealEngine"); - - OutFile.BeginArray("includePath"); + foreach (ProjectData.Target ProjectTarget in Project.Targets) { - if (!RewriteIncludePaths) + BuildTarget BuildTarget = BuildTargets.FirstOrDefault(Target => Target.Name == ProjectTarget.Name); + + // we do not generate intellisense for every target, as that just causes a lot of redundancy, as such we will not find a mapping for a lot of the targets + if (BuildTarget == null) + continue; + + OutFile.BeginObject(); + + string Name = string.Format("{0} {1} {2} {3} ({4})", ProjectTarget.Name, ProjectTarget.Type, BuildTarget.Platform, BuildTarget.Configuration, Project.Name); + OutFile.AddField("name", Name); + + if (HostPlatform == UnrealTargetPlatform.Win64) { - foreach (var Path in Projects.CombinedIncludePaths) - { - OutFile.AddUnnamedField(Path); - } + OutFile.AddField("intelliSenseMode", "msvc-x64"); } else { - OutFile.AddUnnamedField("${workspaceFolder}/**"); + OutFile.AddField("intelliSenseMode", "clang-x64"); } - } - OutFile.EndArray(); - if (HostPlatform == UnrealTargetPlatform.Win64) - { - OutFile.AddField("intelliSenseMode", "msvc-x64"); - } - else - { - OutFile.AddField("intelliSenseMode", "clang-x64"); - } - - if (HostPlatform == UnrealTargetPlatform.Mac) - { - OutFile.BeginArray("macFrameworkPath"); + if (HostPlatform == UnrealTargetPlatform.Mac) { - OutFile.AddUnnamedField("/System/Library/Frameworks"); - OutFile.AddUnnamedField("/Library/Frameworks"); + OutFile.BeginArray("macFrameworkPath"); + { + OutFile.AddUnnamedField("/System/Library/Frameworks"); + OutFile.AddUnnamedField("/Library/Frameworks"); + } + OutFile.EndArray(); } - OutFile.EndArray(); - } - OutFile.BeginArray("defines"); - { - foreach (string Definition in Projects.CombinedPreprocessorDefinitions) - { - OutFile.AddUnnamedField(Definition); - } + FileReference CompileCommands = FileReference.Combine(OutputDirectory, string.Format("compileCommands_{0}.json", Project.Name)); + WriteCompileCommands(CompileCommands, Project.SourceProject.SourceFiles,BuildTarget.ModuleCommandLines); + OutFile.AddField("compileCommands", MakePathString(CompileCommands, bInAbsolute: true, bForceSkipQuotes: true)); + + OutFile.EndObject(); } - OutFile.EndArray(); } - OutFile.EndObject(); } OutFile.EndArray(); } @@ -665,6 +616,50 @@ namespace UnrealBuildTool OutFile.Write(FileReference.Combine(OutputDirectory, "c_cpp_properties.json")); } + private void WriteCompileCommands(FileReference CompileCommandsFile, List SourceFiles, Dictionary ModuleCommandLines) + { + // this creates a compileCommands.json + // see VsCode Docs - https://code.visualstudio.com/docs/cpp/c-cpp-properties-schema-reference (compileCommands attribute) + // and the clang format description https://clang.llvm.org/docs/JSONCompilationDatabase.html + + using (JsonWriter Writer = new JsonWriter(CompileCommandsFile)) + { + Writer.WriteArrayStart(); + + Dictionary DirectoryToIntellisenseCompilerCommand = ModuleCommandLines; + + foreach (ProjectFile.SourceFile File in SourceFiles.OrderBy(x => x.Reference.FullName)) + { + DirectoryReference Directory = File.Reference.Directory; + string CompilerCommand; + if (!DirectoryToIntellisenseCompilerCommand.TryGetValue(Directory, out CompilerCommand)) + { + for (DirectoryReference ParentDir = Directory; ParentDir != null && ParentDir != UnrealBuildTool.RootDirectory; ParentDir = ParentDir.ParentDirectory) + { + if (DirectoryToIntellisenseCompilerCommand.TryGetValue(ParentDir, out CompilerCommand)) + { + break; + } + } + DirectoryToIntellisenseCompilerCommand[Directory] = CompilerCommand; + } + + if (CompilerCommand == null) + { + // no compiler command associated with the file, will happen for any file that is not a C++ file and is not an error + continue; + } + + Writer.WriteObjectStart(); + Writer.WriteValue("file", MakePathString(File.Reference, bInAbsolute: true, bForceSkipQuotes: true)); + Writer.WriteValue("command", CompilerCommand); + Writer.WriteValue("directory", UnrealBuildTool.EngineSourceDirectory.ToString()); + Writer.WriteObjectEnd(); + } + Writer.WriteArrayEnd(); + } + } + private void WriteNativeTask(ProjectData.Project InProject, JsonFile OutFile) { string[] Commands = { "Build", "Rebuild", "Clean" }; @@ -678,7 +673,6 @@ namespace UnrealBuildTool string Command = BaseCommand == "Rebuild" ? "Build" : BaseCommand; string TaskName = String.Format("{0} {1} {2} {3}", Target.Name, BuildProduct.Platform.ToString(), BuildProduct.Config, BaseCommand); string CleanTaskName = String.Format("{0} {1} {2} {3}", Target.Name, BuildProduct.Platform.ToString(), BuildProduct.Config, "Clean"); - //List ExtraParams = new List(); OutFile.BeginObject(); { @@ -885,25 +879,12 @@ namespace UnrealBuildTool OutFile.Write(FileReference.Combine(VSCodeDir, "tasks.json")); } - public string EscapePath(string InputPath) - { - string Result = InputPath; - if (Result.Contains(" ")) - { - Result = "\"" + Result + "\""; - } - return Result; - } - private FileReference GetExecutableFilename(ProjectFile Project, ProjectTarget Target, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration) { TargetRules TargetRulesObject = Target.TargetRules; FileReference TargetFilePath = Target.TargetFilePath; string TargetName = TargetFilePath == null ? Project.ProjectFilePath.GetFileNameWithoutExtension() : TargetFilePath.GetFileNameWithoutAnyExtensions(); string UBTPlatformName = Platform.ToString(); - //string UBTConfigurationName = Configuration.ToString(); - - //string ProjectName = Project.ProjectFilePath.GetFileNameWithoutExtension(); // Setup output path UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatform(Platform); @@ -1186,11 +1167,6 @@ namespace UnrealBuildTool OutFile.Write(FileReference.Combine(VSCodeDir, "launch.json")); } - protected override void ConfigureProjectFileGeneration(string[] Arguments, ref bool IncludeAllPlatforms) - { - base.ConfigureProjectFileGeneration(Arguments, ref IncludeAllPlatforms); - } - private void WriteWorkspaceIgnoreFile(List Projects) { List PathsToExclude = new List(); @@ -1250,7 +1226,7 @@ namespace UnrealBuildTool } } - private void WriteWorkspaceFile(ProjectData ProjectData) + private void WriteWorkspaceFile() { JsonFile WorkspaceFile = new JsonFile(); @@ -1284,6 +1260,7 @@ namespace UnrealBuildTool WorkspaceFile.BeginObject("settings"); { + // disable autodetect for typescript files to workaround slowdown in vscode as a result of parsing all files WorkspaceFile.AddField("typescript.tsc.autoDetect", "off"); } WorkspaceFile.EndObject(); diff --git a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Xcode/XcodeProject.cs b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Xcode/XcodeProject.cs index 9380654a516d..5903906f884b 100644 --- a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Xcode/XcodeProject.cs +++ b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Xcode/XcodeProject.cs @@ -635,7 +635,9 @@ namespace UnrealBuildTool Content.Append("\t\t" + TargetGuid + " /* " + TargetName + " */ = {" + ProjectFileGenerator.NewLine); Content.Append("\t\t\tisa = PBXLegacyTarget;" + ProjectFileGenerator.NewLine); - Content.Append("\t\t\tbuildArgumentsString = \"$(ACTION) $(UE_BUILD_TARGET_NAME) $(PLATFORM_NAME) $(UE_BUILD_TARGET_CONFIG)" + (UProjectPath == null ? "" : " \\\"" + UProjectPath.FullName + "\\\"") + (bHasEditorConfiguration ? " -buildscw" : "") + "\";" + ProjectFileGenerator.NewLine); + Content.Append("\t\t\tbuildArgumentsString = \"$(ACTION) $(UE_BUILD_TARGET_NAME) $(PLATFORM_NAME) $(UE_BUILD_TARGET_CONFIG)" + + (UProjectPath == null ? "" : " \\\"" + UProjectPath.FullName + "\\\"") + + "\";" + ProjectFileGenerator.NewLine); Content.Append("\t\t\tbuildConfigurationList = " + TargetBuildConfigGuid + " /* Build configuration list for PBXLegacyTarget \"" + TargetName + "\" */;" + ProjectFileGenerator.NewLine); Content.Append("\t\t\tbuildPhases = (" + ProjectFileGenerator.NewLine); Content.Append("\t\t\t);" + ProjectFileGenerator.NewLine); @@ -1544,19 +1546,32 @@ namespace UnrealBuildTool return Result.ToString(); } + private FileReference GetUserSchemeManagementFilePath() + { + return new FileReference(ProjectFilePath.FullName + "/xcuserdata/" + Environment.UserName + ".xcuserdatad/xcschemes/xcschememanagement.plist"); + } + + private DirectoryReference GetProjectSchemeDirectory() + { + return new DirectoryReference(ProjectFilePath.FullName + "/xcshareddata/xcschemes"); + } + + private FileReference GetProjectSchemeFilePathForTarget(string TargetName) + { + return FileReference.Combine(GetProjectSchemeDirectory(), TargetName + ".xcscheme"); + } + private void WriteSchemeFile(string TargetName, string TargetGuid, string BuildTargetGuid, string IndexTargetGuid, bool bHasEditorConfiguration, string GameProjectPath) { - DirectoryReference SchemesDir = new DirectoryReference(ProjectFilePath.FullName + "/xcshareddata/xcschemes"); - if (!DirectoryReference.Exists(SchemesDir)) - { - DirectoryReference.CreateDirectory(SchemesDir); - } + + FileReference SchemeFilePath = GetProjectSchemeFilePathForTarget(TargetName); + + DirectoryReference.CreateDirectory(SchemeFilePath.Directory); - string SchemeFilePath = SchemesDir + "/" + TargetName + ".xcscheme"; string OldCommandLineArguments = null; - if (File.Exists(SchemeFilePath)) + if (FileReference.Exists(SchemeFilePath)) { - string OldContents = File.ReadAllText(SchemeFilePath); + string OldContents = File.ReadAllText(SchemeFilePath.FullName); int OldCommandLineArgumentsStart = OldContents.IndexOf("") + "".Length; int OldCommandLineArgumentsEnd = OldContents.IndexOf(""); if (OldCommandLineArgumentsStart != -1 && OldCommandLineArgumentsEnd != -1) @@ -1692,7 +1707,7 @@ namespace UnrealBuildTool Content.Append(" " + ProjectFileGenerator.NewLine); Content.Append("" + ProjectFileGenerator.NewLine); - File.WriteAllText(SchemeFilePath, Content.ToString(), new UTF8Encoding()); + File.WriteAllText(SchemeFilePath.FullName, Content.ToString(), new UTF8Encoding()); Content.Clear(); @@ -1729,14 +1744,70 @@ namespace UnrealBuildTool Content.Append("" + ProjectFileGenerator.NewLine); Content.Append("" + ProjectFileGenerator.NewLine); - DirectoryReference ManagementFileDir = new DirectoryReference(ProjectFilePath.FullName + "/xcuserdata/" + Environment.UserName + ".xcuserdatad/xcschemes"); - if (!DirectoryReference.Exists(ManagementFileDir)) + FileReference ManagementFile = GetUserSchemeManagementFilePath(); + if (!DirectoryReference.Exists(ManagementFile.Directory)) { - DirectoryReference.CreateDirectory(ManagementFileDir); + DirectoryReference.CreateDirectory(ManagementFile.Directory); } - string ManagementFilePath = ManagementFileDir + "/xcschememanagement.plist"; - File.WriteAllText(ManagementFilePath, Content.ToString(), new UTF8Encoding()); + File.WriteAllText(ManagementFile.FullName, Content.ToString(), new UTF8Encoding()); + } + + public static IEnumerable GetSupportedPlatforms() + { + List SupportedPlatforms = new List(); + + if (XcodeProjectFileGenerator.ProjectFilePlatform.HasFlag(XcodeProjectFileGenerator.XcodeProjectFilePlatform.Mac)) + { + SupportedPlatforms.Add(UnrealTargetPlatform.Mac); + } + + if (XcodeProjectFileGenerator.ProjectFilePlatform.HasFlag(XcodeProjectFileGenerator.XcodeProjectFilePlatform.iOS)) + { + SupportedPlatforms.Add(UnrealTargetPlatform.IOS); + } + + if (XcodeProjectFileGenerator.ProjectFilePlatform.HasFlag(XcodeProjectFileGenerator.XcodeProjectFilePlatform.tvOS)) + { + SupportedPlatforms.Add(UnrealTargetPlatform.TVOS); + } + + return SupportedPlatforms; + } + + public static IEnumerable GetSupportedConfigurations() + { + return new UnrealTargetConfiguration[] { + UnrealTargetConfiguration.Debug, + UnrealTargetConfiguration.DebugGame, + UnrealTargetConfiguration.Development, + UnrealTargetConfiguration.Test, + UnrealTargetConfiguration.Shipping + }; + } + + public bool ShouldIncludeProjectInWorkspace() + { + return CanBuildProjectLocally(); + } + + public bool CanBuildProjectLocally() + { + foreach (ProjectTarget ProjectTarget in ProjectTargets) + { + foreach (UnrealTargetPlatform Platform in GetSupportedPlatforms()) + { + foreach (UnrealTargetConfiguration Config in GetSupportedConfigurations()) + { + if (MSBuildProjectFile.IsValidProjectPlatformAndConfiguration(ProjectTarget, Platform, Config, null)) + { + return true; + } + } + } + } + + return false; } /// Implements Project interface @@ -1861,10 +1932,25 @@ namespace UnrealBuildTool bSuccess = ProjectFileGenerator.WriteFileIfChanged(PBXProjFilePath.FullName, ProjectFileContent.ToString(), new UTF8Encoding()); } - if (bSuccess) + bool bNeedScheme = CanBuildProjectLocally(); + + if (bNeedScheme) { - WriteSchemeFile(TargetName, TargetGuid, BuildTargetGuid, IndexTargetGuid, bHasEditorConfiguration, GameProjectPath != null ? GameProjectPath.FullName : ""); + if (bSuccess) + { + WriteSchemeFile(TargetName, TargetGuid, BuildTargetGuid, IndexTargetGuid, bHasEditorConfiguration, GameProjectPath != null ? GameProjectPath.FullName : ""); + } } + else + { + // clean this up because we don't want it persisting if we narrow our project list + DirectoryReference SchemeDir = GetProjectSchemeDirectory(); + + if (DirectoryReference.Exists(SchemeDir)) + { + DirectoryReference.Delete(SchemeDir, true); + } + } return bSuccess; } diff --git a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Xcode/XcodeProjectFileGenerator.cs b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Xcode/XcodeProjectFileGenerator.cs index 4a9341969591..9bbc668c798d 100644 --- a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Xcode/XcodeProjectFileGenerator.cs +++ b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Xcode/XcodeProjectFileGenerator.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Text; using System.IO; using Tools.DotNETCommon; +using System.Linq; namespace UnrealBuildTool { @@ -155,6 +156,8 @@ namespace UnrealBuildTool return WriteFileIfChanged(Path, WorkspaceSettingsContent.ToString(), new UTF8Encoding()); } + + private bool WriteXcodeWorkspace() { bool bSuccess = true; @@ -165,48 +168,74 @@ namespace UnrealBuildTool WorkspaceDataContent.Append("" + ProjectFileGenerator.NewLine); + List BuildableProjects = new List(); + System.Action< List /* Folders */, string /* Ident */ > AddProjectsFunction = null; AddProjectsFunction = (FolderList, Ident) => { - int SchemeIndex = 0; foreach (XcodeProjectFolder CurFolder in FolderList) { WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine); AddProjectsFunction(CurFolder.SubFolders, Ident + " "); + + // Filter out anything that isn't an XC project, and that shouldn't be in the workspace + IEnumerable SupportedProjects = + CurFolder.ChildProjects + .Where(P => P is XcodeProjectFile) + .Select(P => P as XcodeProjectFile) + .Where(P => P.ShouldIncludeProjectInWorkspace()) + .OrderBy(P => P.ProjectFilePath.GetFileName()); - List ChildProjects = new List(CurFolder.ChildProjects); - ChildProjects.Sort((ProjectFile A, ProjectFile B) => { return A.ProjectFilePath.GetFileName().CompareTo(B.ProjectFilePath.GetFileName()); }); - foreach (ProjectFile CurProject in ChildProjects) + foreach (XcodeProjectFile XcodeProject in SupportedProjects) { - XcodeProjectFile XcodeProject = CurProject as XcodeProjectFile; - if (XcodeProject != null) - { - WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine); - WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine); - - // Also, update project's schemes index so that the schemes list order match projects order in the navigator - FileReference SchemeManagementFile = XcodeProject.ProjectFilePath + "/xcuserdata/" + Environment.UserName + ".xcuserdatad/xcschemes/xcschememanagement.plist"; - if (FileReference.Exists(SchemeManagementFile)) - { - string SchemeManagementContent = FileReference.ReadAllText(SchemeManagementFile); - SchemeManagementContent = SchemeManagementContent.Replace("orderHint\n\t\t\t1", "orderHint\n\t\t\t" + SchemeIndex.ToString() + ""); - FileReference.WriteAllText(SchemeManagementFile, SchemeManagementContent); - SchemeIndex++; - } - } + WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine); + WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine); } + BuildableProjects.AddRange(SupportedProjects); + WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine); } }; AddProjectsFunction(RootFolder.SubFolders, ""); - + WorkspaceDataContent.Append("" + ProjectFileGenerator.NewLine); + // Also, update project's schemes index so that the schemes are in a sensible order + // (Game, Editor, Client, Server, Programs) + int SchemeIndex = 0; + BuildableProjects.Sort((ProjA, ProjB) => { + + var TargetA = ProjA.ProjectTargets.OrderBy(T => T.TargetRules.Type).FirstOrDefault(); + var TargetB = ProjB.ProjectTargets.OrderBy(T => T.TargetRules.Type).FirstOrDefault(); + + var TypeA = TargetA != null ? TargetA.TargetRules.Type : TargetType.Program; + var TypeB = TargetB != null ? TargetB.TargetRules.Type : TargetType.Program; + + if (TypeA != TypeB) + { + return TypeA.CompareTo(TypeB); + } + + return TargetA.Name.CompareTo(TargetB.Name); + }); + + foreach (XcodeProjectFile XcodeProject in BuildableProjects) + { + FileReference SchemeManagementFile = XcodeProject.ProjectFilePath + "/xcuserdata/" + Environment.UserName + ".xcuserdatad/xcschemes/xcschememanagement.plist"; + if (FileReference.Exists(SchemeManagementFile)) + { + string SchemeManagementContent = FileReference.ReadAllText(SchemeManagementFile); + SchemeManagementContent = SchemeManagementContent.Replace("orderHint\n\t\t\t1", "orderHint\n\t\t\t" + SchemeIndex.ToString() + ""); + FileReference.WriteAllText(SchemeManagementFile, SchemeManagementContent); + SchemeIndex++; + } + } + string ProjectName = MasterProjectName; if (ProjectFilePlatform != XcodeProjectFilePlatform.All) { diff --git a/Engine/Source/Programs/UnrealBuildTool/System/ActionHistory.cs b/Engine/Source/Programs/UnrealBuildTool/System/ActionHistory.cs index bb55a84e35c6..c20452a1c9ab 100644 --- a/Engine/Source/Programs/UnrealBuildTool/System/ActionHistory.cs +++ b/Engine/Source/Programs/UnrealBuildTool/System/ActionHistory.cs @@ -1,9 +1,11 @@ // Copyright Epic Games, Inc. All Rights Reserved. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.Serialization.Formatters.Binary; using System.Security.Cryptography; using System.Text; @@ -14,7 +16,8 @@ namespace UnrealBuildTool /// /// Caches include dependency information to speed up preprocessing on subsequent runs. /// - class ActionHistory + [DebuggerDisplay("{Location}")] + class ActionHistoryLayer { /// /// Version number to check @@ -29,49 +32,28 @@ namespace UnrealBuildTool /// /// Path to store the cache data to. /// - FileReference Location; - - /// - /// Base directory for output files. Any files under this directory will have their command lines stored in this object, otherwise the parent will be used. - /// - DirectoryReference BaseDirectory; - - /// - /// The parent action history. Any files not under this base directory will use this. - /// - ActionHistory Parent; + public FileReference Location + { + get; + } /// /// The command lines used to produce files, keyed by the absolute file paths. /// - Dictionary OutputItemToCommandLineHash = new Dictionary(); + ConcurrentDictionary OutputItemToCommandLineHash = new ConcurrentDictionary(); /// /// Whether the dependency cache is dirty and needs to be saved. /// bool bModified; - /// - /// Object to use for guarding access to the OutputItemToCommandLine dictionary - /// - object LockObject = new object(); - - /// - /// Static cache of all loaded action history files - /// - static Dictionary LoadedFiles = new Dictionary(); - /// /// Constructor /// /// File to store this history in - /// Base directory for files to track - /// The parent action history - public ActionHistory(FileReference Location, DirectoryReference BaseDirectory, ActionHistory Parent) + public ActionHistoryLayer(FileReference Location) { this.Location = Location; - this.BaseDirectory = BaseDirectory; - this.Parent = Parent; if(FileReference.Exists(Location)) { @@ -95,7 +77,7 @@ namespace UnrealBuildTool return; } - OutputItemToCommandLineHash = Reader.ReadDictionary(() => Reader.ReadFileItem(), () => Reader.ReadFixedSizeByteArray(HashLength)); + OutputItemToCommandLineHash = new ConcurrentDictionary(Reader.ReadDictionary(() => Reader.ReadFileItem(), () => Reader.ReadFixedSizeByteArray(HashLength))); } } catch(Exception Ex) @@ -108,15 +90,18 @@ namespace UnrealBuildTool /// /// Saves this action history to disk /// - void Save() + public void Save() { - DirectoryReference.CreateDirectory(Location.Directory); - using(BinaryArchiveWriter Writer = new BinaryArchiveWriter(Location)) + if (bModified) { - Writer.WriteInt(CurrentVersion); - Writer.WriteDictionary(OutputItemToCommandLineHash, Key => Writer.WriteFileItem(Key), Value => Writer.WriteFixedSizeByteArray(Value)); + DirectoryReference.CreateDirectory(Location.Directory); + using (BinaryArchiveWriter Writer = new BinaryArchiveWriter(Location)) + { + Writer.WriteInt(CurrentVersion); + Writer.WriteDictionary(OutputItemToCommandLineHash, Key => Writer.WriteFileItem(Key), Value => Writer.WriteFixedSizeByteArray(Value)); + } + bModified = false; } - bModified = false; } /// @@ -157,25 +142,13 @@ namespace UnrealBuildTool /// True if the output item exists public bool UpdateProducingCommandLine(FileItem File, string CommandLine) { - if(File.Location.IsUnderDirectory(BaseDirectory) || Parent == null) + byte[] NewHash = ComputeHash(CommandLine); + if(OutputItemToCommandLineHash.TryAdd(File, NewHash)) { - byte[] NewHash = ComputeHash(CommandLine); - lock (LockObject) - { - byte[] CurrentHash; - if(!OutputItemToCommandLineHash.TryGetValue(File, out CurrentHash) || !CompareHashes(CurrentHash, NewHash)) - { - OutputItemToCommandLineHash[File] = NewHash; - bModified = true; - return true; - } - return false; - } - } - else - { - return Parent.UpdateProducingCommandLine(File, CommandLine); + bModified = true; + return true; } + return false; } /// @@ -214,34 +187,6 @@ namespace UnrealBuildTool return FileReference.Combine(ProjectFile.Directory, UEBuildTarget.GetPlatformIntermediateFolder(Platform, Architecture), TargetName, "ActionHistory.dat"); } - /// - /// Creates a hierarchy of action history stores for a particular target - /// - /// Project file for the target being built - /// Name of the target - /// Platform being built - /// The target type - /// The target architecture - /// Dependency cache hierarchy for the given project - public static ActionHistory CreateHierarchy(FileReference ProjectFile, string TargetName, UnrealTargetPlatform Platform, TargetType TargetType, string Architecture) - { - ActionHistory History = null; - - if(ProjectFile == null || !UnrealBuildTool.IsEngineInstalled()) - { - FileReference EngineCacheLocation = GetEngineLocation(TargetName, Platform, TargetType, Architecture); - History = FindOrAddHistory(EngineCacheLocation, UnrealBuildTool.EngineDirectory, History); - } - - if(ProjectFile != null) - { - FileReference ProjectCacheLocation = GetProjectLocation(ProjectFile, TargetName, Platform, Architecture); - History = FindOrAddHistory(ProjectCacheLocation, ProjectFile.Directory, History); - } - - return History; - } - /// /// Enumerates all the locations of action history files for the given target /// @@ -262,46 +207,224 @@ namespace UnrealBuildTool yield return GetProjectLocation(ProjectFile, TargetName, Platform, Architecture); } } + } + + /// + /// Information about actions producing artifacts under a particular directory + /// + [DebuggerDisplay("{BaseDir}")] + class ActionHistoryPartition + { + /// + /// The base directory for this partition + /// + public DirectoryReference BaseDir { get; } + + /// + /// Used to ensure exclusive access to the layers list + /// + object LockObject = new object(); + + /// + /// Map of filename to layer + /// + IReadOnlyList Layers = new List(); + + /// + /// Construct a new partition + /// + /// The base directory for this partition + public ActionHistoryPartition(DirectoryReference BaseDir) + { + this.BaseDir = BaseDir; + } + + /// + /// Attempt to update the producing commandline for the given file + /// + /// The file to update + /// The new command line + /// True if the command line was updated, false otherwise + public bool UpdateProducingCommandLine(FileItem File, string CommandLine) + { + FileReference LayerLocation = GetLayerLocationForFile(File.Location); + + ActionHistoryLayer Layer = Layers.FirstOrDefault(x => x.Location == LayerLocation); + if (Layer == null) + { + lock (LockObject) + { + Layer = Layers.FirstOrDefault(x => x.Location == LayerLocation); + if(Layer == null) + { + Layer = new ActionHistoryLayer(LayerLocation); + + List NewLayers = new List(Layers); + NewLayers.Add(Layer); + Layers = NewLayers; + } + } + } + return Layer.UpdateProducingCommandLine(File, CommandLine); + } + + /// + /// Get the path to the action history layer to use for the given file + /// + /// Path to the file to use + /// Path to the file + public FileReference GetLayerLocationForFile(FileReference Location) + { + int Offset = BaseDir.FullName.Length; + for (; ; ) + { + int NameOffset = Offset + 1; + + // Get the next directory separator + Offset = Location.FullName.IndexOf(Path.DirectorySeparatorChar, NameOffset + 1); + if (Offset == -1) + { + break; + } + + // Get the length of the name + int NameLength = Offset - NameOffset; + + // Try to find Binaries// in the path + if (MatchPathFragment(Location, NameOffset, NameLength, "Binaries")) + { + int PlatformOffset = Offset + 1; + int PlatformEndOffset = Location.FullName.IndexOf(Path.DirectorySeparatorChar, PlatformOffset); + if (PlatformEndOffset != -1) + { + string PlatformName = Location.FullName.Substring(PlatformOffset, PlatformEndOffset - PlatformOffset); + return FileReference.Combine(BaseDir, "Intermediate", "Build", PlatformName, "ActionHistory.bin"); + } + } + + // Try to find /Intermediate/Build/// in the path + if (MatchPathFragment(Location, NameOffset, NameLength, "Intermediate")) + { + int BuildOffset = Offset + 1; + int BuildEndOffset = Location.FullName.IndexOf(Path.DirectorySeparatorChar, BuildOffset); + if (BuildEndOffset != -1 && MatchPathFragment(Location, BuildOffset, BuildEndOffset - BuildOffset, "Build")) + { + // Skip the platform, target/app name, and configuration + int EndOffset = BuildEndOffset; + for (int Idx = 0; ; Idx++) + { + EndOffset = Location.FullName.IndexOf(Path.DirectorySeparatorChar, EndOffset + 1); + if (EndOffset == -1) + { + break; + } + if (Idx == 2) + { + return FileReference.Combine(BaseDir, Location.FullName.Substring(NameOffset, EndOffset - NameOffset), "ActionHistory.bin"); + } + } + } + } + } + return FileReference.Combine(BaseDir, "Intermediate", "Build", "ActionHistory.bin"); + } + + /// + /// Attempts to match a substring of a path with the given fragment + /// + /// Path to match against + /// Offset of the substring to match + /// Length of the substring to match + /// The path fragment + /// True if the substring matches + static bool MatchPathFragment(FileReference Location, int Offset, int Length, string Fragment) + { + return Length == Fragment.Length && String.Compare(Location.FullName, Offset, Fragment, 0, Fragment.Length, FileReference.Comparison) == 0; + } + + /// + /// Saves the modified layers + /// + public void Save() + { + foreach(ActionHistoryLayer Layer in Layers) + { + Layer.Save(); + } + } + } + + /// + /// A collection of ActionHistory layers + /// + class ActionHistory + { + /// + /// The lock object for this history + /// + object LockObject = new object(); + + /// + /// List of partitions + /// + List Partitions = new List(); + + /// + /// Constructor + /// + public ActionHistory() + { + Partitions.Add(new ActionHistoryPartition(UnrealBuildTool.EngineDirectory)); + } /// /// Reads a cache from the given location, or creates it with the given settings /// - /// File to store the cache - /// Base directory for files that this cache should store data for - /// The parent cache to use + /// Base directory for files that this cache should store data for /// Reference to a dependency cache with the given settings - static ActionHistory FindOrAddHistory(FileReference Location, DirectoryReference BaseDirectory, ActionHistory Parent) + public void Mount(DirectoryReference BaseDir) { - lock(LoadedFiles) + lock (LockObject) { - ActionHistory History; - if(LoadedFiles.TryGetValue(Location, out History)) + ActionHistoryPartition Partition = Partitions.FirstOrDefault(x => x.BaseDir == BaseDir); + if(Partition == null) { - Debug.Assert(History.BaseDirectory == BaseDirectory); - Debug.Assert(History.Parent == Parent); + Partition = new ActionHistoryPartition(BaseDir); + Partitions.Add(Partition); } - else - { - History = new ActionHistory(Location, BaseDirectory, Parent); - LoadedFiles.Add(Location, History); - } - return History; } } /// - /// Save all the loaded action histories + /// Gets the producing command line for the given file /// - public static void SaveAll() + /// The output file to look for + /// Receives the command line used to produce this file + /// True if the output item exists + public bool UpdateProducingCommandLine(FileItem File, string CommandLine) { - lock(LoadedFiles) + foreach (ActionHistoryPartition Partition in Partitions) { - foreach(ActionHistory History in LoadedFiles.Values) + if (File.Location.IsUnderDirectory(Partition.BaseDir)) { - if(History.bModified) - { - History.Save(); - } + return Partition.UpdateProducingCommandLine(File, CommandLine); + } + } + + Log.TraceWarning("File {0} is not under any action history root directory", File.Location); + return false; + } + + /// + /// Saves all layers of this action history + /// + public void Save() + { + lock (LockObject) + { + foreach (ActionHistoryPartition Partition in Partitions) + { + Partition.Save(); } } } diff --git a/Engine/Source/Programs/UnrealBuildTool/System/DynamicCompilation.cs b/Engine/Source/Programs/UnrealBuildTool/System/DynamicCompilation.cs index d54b5ecee39c..329fb2be6ffc 100644 --- a/Engine/Source/Programs/UnrealBuildTool/System/DynamicCompilation.cs +++ b/Engine/Source/Programs/UnrealBuildTool/System/DynamicCompilation.cs @@ -35,12 +35,13 @@ namespace UnrealBuildTool /// True if the assembly needs to be built private static bool RequiresCompilation(HashSet SourceFiles, FileReference AssemblyManifestFilePath, FileReference OutputAssemblyPath) { - // Do not compile the file if it's installed - if (UnrealBuildTool.IsFileInstalled(OutputAssemblyPath)) - { - Log.TraceLog("Skipping {0}: File is installed", OutputAssemblyPath); - return false; - } + // HACK: We cannot do this without breaking marketplace plugins. Need to distribute separate rules assemblies for those. +// // Do not compile the file if it's installed +// if (UnrealBuildTool.IsFileInstalled(OutputAssemblyPath)) +// { +// Log.TraceLog("Skipping {0}: File is installed", OutputAssemblyPath); +// return false; +// } // Check to see if we already have a compiled assembly file on disk FileItem OutputAssemblyInfo = FileItem.GetItemByFileReference(OutputAssemblyPath); diff --git a/Engine/Source/Programs/UnrealBuildTool/System/HotReload.cs b/Engine/Source/Programs/UnrealBuildTool/System/HotReload.cs index b7bdd3cf66f7..0d39da100dbd 100644 --- a/Engine/Source/Programs/UnrealBuildTool/System/HotReload.cs +++ b/Engine/Source/Programs/UnrealBuildTool/System/HotReload.cs @@ -468,7 +468,12 @@ namespace UnrealBuildTool { // Get the dependency history CppDependencyCache CppDependencies = CppDependencyCache.CreateHierarchy(TargetDescriptor.ProjectFile, TargetDescriptor.Name, TargetDescriptor.Platform, TargetDescriptor.Configuration, Makefile.TargetType, TargetDescriptor.Architecture); - ActionHistory History = ActionHistory.CreateHierarchy(TargetDescriptor.ProjectFile, TargetDescriptor.Name, TargetDescriptor.Platform, Makefile.TargetType, TargetDescriptor.Architecture); + + ActionHistory History = new ActionHistory(); + if(TargetDescriptor.ProjectFile != null) + { + History.Mount(TargetDescriptor.ProjectFile.Directory); + } if (TargetDescriptor.HotReloadMode == HotReloadMode.LiveCoding) { diff --git a/Engine/Source/Programs/UnrealBuildTool/System/PlatformExports.cs b/Engine/Source/Programs/UnrealBuildTool/System/PlatformExports.cs index 7cdc7af09817..37021b4a975c 100644 --- a/Engine/Source/Programs/UnrealBuildTool/System/PlatformExports.cs +++ b/Engine/Source/Programs/UnrealBuildTool/System/PlatformExports.cs @@ -99,6 +99,16 @@ namespace UnrealBuildTool return BuildPlatform.GetExcludedFolderNames().ToArray(); } + /// + /// Returns the respective platform sdk version string + /// + /// The target platform to query + public static string GetRequiredSDKString(UnrealTargetPlatform Platform) + { + UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatform(Platform, false); + return BuildPlatform.GetRequiredSDKString(); + } + /// /// Check whether the given platform supports XGE /// diff --git a/Engine/Source/Programs/UnrealBuildTool/UnrealBuildTool.cs b/Engine/Source/Programs/UnrealBuildTool/UnrealBuildTool.cs index f3766356066f..267c7807facd 100644 --- a/Engine/Source/Programs/UnrealBuildTool/UnrealBuildTool.cs +++ b/Engine/Source/Programs/UnrealBuildTool/UnrealBuildTool.cs @@ -39,7 +39,7 @@ namespace UnrealBuildTool /// Whether we're running with an installed project /// static private bool? bIsProjectInstalled; - + /// /// If we are running with an installed project, specifies the path to it /// @@ -174,7 +174,7 @@ namespace UnrealBuildTool IEnumerable RestrictedDirs = DirectoryReference.EnumerateDirectories(RestrictedBaseDir); CachedDirs.Item2.AddRange(RestrictedDirs); - // also look for nested platforms in the restricted + // also look for nested platforms in the restricted foreach (DirectoryReference RestrictedDir in RestrictedDirs) { DirectoryReference RestrictedPlatformExtensionBaseDir = DirectoryReference.Combine(RestrictedDir, "Platforms"); @@ -403,6 +403,7 @@ namespace UnrealBuildTool [CommandLine("-VSCode", Value="GenerateProjectFiles")] [CommandLine("-VSMac", Value="GenerateProjectFiles")] [CommandLine("-CLion", Value="GenerateProjectFiles")] + [CommandLine("-Rider", Value="GenerateProjectFiles")] public string Mode = null; /// @@ -581,7 +582,7 @@ namespace UnrealBuildTool } catch (Exception Ex) { - // Unhandled exception. + // Unhandled exception. Log.TraceError("Unhandled exception: {0}", ExceptionUtils.FormatException(Ex)); Log.TraceLog(ExceptionUtils.FormatExceptionDetails(Ex)); return (int)CompilationResult.OtherCompilationError; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Automation/P4Automation.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Automation/P4Automation.cs new file mode 100644 index 000000000000..2f782f0ee612 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Automation/P4Automation.cs @@ -0,0 +1,80 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using System.IO; + +namespace UnrealGameSync +{ + + static class P4Automation + { + + public static PerforceConnection GetConnection(out string ErrorMessage) + { + ErrorMessage = null; + + // Read the settings + string ServerAndPort = null; + string UserName = null; + string DepotPathSettings = null; + + Utility.ReadGlobalPerforceSettings(ref ServerAndPort, ref UserName, ref DepotPathSettings); + + if (string.IsNullOrEmpty(UserName)) + { + ErrorMessage = "Unable to get UGS perforce username from registry"; + return null; + } + + if (string.IsNullOrEmpty(ServerAndPort)) + { + ServerAndPort = "perforce:1666"; + } + + return new PerforceConnection(UserName, null, ServerAndPort); + + } + + public static bool PrintToTempFile(PerforceConnection Connection, string DepotPath, out string TempFileName, out string ErrorMessage) + { + TempFileName = null; + ErrorMessage = null; + + if (Connection == null) + { + Connection = GetConnection(out ErrorMessage); + if (Connection == null) + { + return false; + } + } + + BufferedTextWriter Log = new BufferedTextWriter(); + string DepotFileName = Path.GetFileName(DepotPath); + + // Reorder CL and extension + int Index = DepotFileName.IndexOf('@'); + if (Index == -1) + { + DepotFileName += "@Latest"; + Index = DepotFileName.IndexOf('@'); + } + + string CL = DepotFileName.Substring(Index + 1); + string FileName = DepotFileName.Substring(0, Index); + TempFileName = string.Format("{0}@{1}{2}", Path.GetFileNameWithoutExtension(FileName), CL, Path.GetExtension(FileName)); + + TempFileName = Path.Combine(Path.GetTempPath(), TempFileName); + + if (!Connection.PrintToFile(DepotPath, TempFileName, Log) || !File.Exists(TempFileName)) + { + ErrorMessage = string.Format("Unable to p4 print file {0}\\n{1}\\n", DepotPath, string.Join("\n", Log.Lines)); + return false; + } + + return true; + + } + + } + +} \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Automation/VisualStudioAutomation.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Automation/VisualStudioAutomation.cs new file mode 100644 index 000000000000..cc1749983d86 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Automation/VisualStudioAutomation.cs @@ -0,0 +1,305 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Windows.Forms; +using Microsoft.VisualStudio.Setup.Configuration; +using EnvDTE; +using System.Diagnostics; + +namespace UnrealGameSync +{ + + static class VisualStudioAutomation + { + public static bool OpenFile(string FileName, out string ErrorMessage, int Line = -1) + { + ErrorMessage = null; + + // first try to open via DTE + DTE DTE = VisualStudioAccessor.GetDTE(); + if (DTE != null) + { + DTE.ItemOperations.OpenFile(FileName); + DTE.MainWindow.Activate(); + + if (Line != -1) + { + (DTE.ActiveDocument.Selection as TextSelection).GotoLine(Line); + } + + return true; + } + + // unable to get DTE connection, so launch nesw VS instance + string Arguments = string.Format("\"{0}\"", FileName); + if (Line != -1) + { + Arguments += string.Format(" /command \"edit.goto {0}\"", Line); + } + + // Launch new visual studio instance + if (!VisualStudioAccessor.LaunchVisualStudio(Arguments, out ErrorMessage)) + { + return false; + } + + return true; + } + } + + /// + /// Visual Studio automation accessor + /// + static class VisualStudioAccessor + { + public static bool LaunchVisualStudio(string Arguments, out string ErrorMessage) + { + ErrorMessage = ""; + VisualStudioInstallation Install = VisualStudioInstallations.GetPreferredInstallation(); + + if (Install == null) + { + ErrorMessage = string.Format("Unable to get Visual Studio installation"); + return false; + } + try + { + + System.Diagnostics.Process VSProcess = new System.Diagnostics.Process { StartInfo = new ProcessStartInfo(Install.DevEnvPath, Arguments) }; + VSProcess.Start(); + VSProcess.WaitForInputIdle(); + } + catch (Exception Ex) + { + ErrorMessage = Ex.Message; + return false; + } + + return true; + + } + + [STAThread] + public static DTE GetDTE() + { + IRunningObjectTable Table; + if (Succeeded(GetRunningObjectTable(0, out Table)) && Table != null) + { + IEnumMoniker MonikersTable; + Table.EnumRunning(out MonikersTable); + + if (MonikersTable == null) + { + return null; + } + + MonikersTable.Reset(); + + // Look for all visual studio instances in the ROT + IMoniker[] Monikers = new IMoniker[] { null }; + while (MonikersTable.Next(1, Monikers, IntPtr.Zero) == 0) + { + IBindCtx BindContext; + string OutDisplayName; + IMoniker CurrentMoniker = Monikers[0]; + + if (!Succeeded(CreateBindCtx(0, out BindContext))) + { + continue; + } + + try + { + CurrentMoniker.GetDisplayName(BindContext, null, out OutDisplayName); + if (string.IsNullOrEmpty(OutDisplayName) || !IsVisualStudioDTEMoniker(OutDisplayName)) + { + continue; + } + } + catch (UnauthorizedAccessException) + { + // Some ROT objects require elevated permissions + continue; + } + + object ComObject; + if (!Succeeded(Table.GetObject(CurrentMoniker, out ComObject))) + { + continue; + } + + return ComObject as DTE; + } + } + + return null; + + } + + static bool IsVisualStudioDTEMoniker(string InName) + { + VisualStudioInstallation[] Installs = VisualStudioInstallations.Installs; + + for (int Idx = 0; Idx < Installs.Length; Idx++) + { + if (InName.StartsWith(Installs[Idx].ROTMoniker)) + { + return true; + } + } + + return false; + } + + static bool Succeeded(int Result) + { + return Result >= 0; + } + + + [DllImport("ole32.dll")] + public static extern int CreateBindCtx(int Reserved, out IBindCtx BindCtx); + + [DllImport("ole32.dll")] + public static extern int GetRunningObjectTable(int Reserved, out IRunningObjectTable ROT); + + } + + class VisualStudioInstallation + { + /// + /// Base directory for the installation + /// + public string BaseDir; + + + /// + /// Path of the devenv executable + /// + public string DevEnvPath; + + /// + /// Visual Studio major version number + /// + public int MajorVersion; + + /// + /// Running Object Table moniker for this installation + /// + public string ROTMoniker; + + } + + /// + /// + /// + static class VisualStudioInstallations + { + + public static VisualStudioInstallation GetPreferredInstallation(int MajorVersion = 0) + { + + if (CachedInstalls.Count == 0) + { + return null; + } + + if (MajorVersion == 0) + { + return CachedInstalls.First(); + } + + VisualStudioInstallation Installation = CachedInstalls.FirstOrDefault(Install => { return Install.MajorVersion == MajorVersion; }); + + return Installation; + + } + + public static VisualStudioInstallation[] Installs + { + get { return CachedInstalls.ToArray(); } + } + + public static void Refresh() + { + GetVisualStudioInstallations(); + } + + static List GetVisualStudioInstallations() + { + CachedInstalls.Clear(); + + try + { + SetupConfiguration Setup = new SetupConfiguration(); + IEnumSetupInstances Enumerator = Setup.EnumAllInstances(); + + ISetupInstance[] Instances = new ISetupInstance[1]; + for (; ; ) + { + int NumFetched; + Enumerator.Next(1, Instances, out NumFetched); + + if (NumFetched == 0) + { + break; + } + + ISetupInstance2 Instance = (ISetupInstance2)Instances[0]; + if ((Instance.GetState() & InstanceState.Local) == InstanceState.Local) + { + string VersionString = Instance.GetInstallationVersion(); + string[] Components = VersionString.Split('.'); + + if (Components.Length == 0) + { + continue; + } + + int MajorVersion; + string InstallationPath = Instance.GetInstallationPath(); + string DevEnvPath = Path.Combine(InstallationPath, "Common7\\IDE\\devenv.exe"); + + if (!int.TryParse(Components[0], out MajorVersion) || (MajorVersion != 15 && MajorVersion != 16)) + { + continue; + } + + + if (!File.Exists(DevEnvPath)) + { + continue; + } + + VisualStudioInstallation Installation = new VisualStudioInstallation() { BaseDir = InstallationPath, DevEnvPath = DevEnvPath, MajorVersion = MajorVersion, ROTMoniker = string.Format("!VisualStudio.DTE.{0}.0", MajorVersion) }; + + CachedInstalls.Add(Installation); + } + } + } + catch (Exception Ex) + { + MessageBox.Show(string.Format("Exception while finding Visual Studio installations {0}", Ex.Message)); + } + + // prefer newer versions + CachedInstalls.Sort((A, B) => { return -A.MajorVersion.CompareTo(B.MajorVersion); }); + + return CachedInstalls; + } + + static VisualStudioInstallations() + { + GetVisualStudioInstallations(); + } + + static readonly List CachedInstalls = new List(); + + } + +} \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/AutomationServer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/AutomationServer.cs index bb88e0db2cd0..dcad3c00622e 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/AutomationServer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/AutomationServer.cs @@ -2,22 +2,23 @@ using Microsoft.Win32; using System; -using System.Collections.Generic; using System.IO; -using System.Linq; +using System.IO.Pipes; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; -using System.Threading.Tasks; +using System.Windows.Forms; namespace UnrealGameSync { enum AutomationRequestType { - SyncProject, - FindProject, - OpenProject, + SyncProject = 0, + FindProject = 1, + OpenProject = 2, + ExecCommand = 3, + OpenIssue = 4, } class AutomationRequestInput @@ -131,35 +132,60 @@ namespace UnrealGameSync class AutomationServer : IDisposable { + TcpListener Listener; public const int DefaultPortNumber = 30422; - TcpListener Listener; - TcpClient CurrentClient; - Thread BackgroundThread; + NamedPipeServerStream IPCStream; + static UnicodeEncoding StreamEncoding = new UnicodeEncoding(); + const string UGSChannel = @"\.\pipe\UGSChannel"; + + Thread UriThread; + Thread TcpThread; + + EventWaitHandle ShutdownEvent; Action PostRequest; + bool bDisposing; TextWriter Log; + string CommandLineUri; - public AutomationServer(Action PostRequest, TextWriter Log) + public AutomationServer(Action PostRequest, TextWriter Log, string Uri) { - this.PostRequest = PostRequest; - this.Log = Log; - - int PortNumber = GetPortNumber(); - if(PortNumber > 0) + try { - try - { - Listener = new TcpListener(IPAddress.Loopback, PortNumber); - Listener.Start(); + ShutdownEvent = new ManualResetEvent(false); + this.PostRequest = PostRequest; + this.Log = Log; + this.CommandLineUri = Uri; + + // IPC named pipe + IPCStream = new NamedPipeServerStream(UGSChannel, PipeDirection.In, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous); - BackgroundThread = new Thread(() => Run()); - BackgroundThread.Start(); - } - catch(Exception Ex) + // TCP listener setup + int PortNumber = GetPortNumber(); + if (PortNumber > 0) { - Log.WriteLine("Unable to start automation server: {0}", Ex.ToString()); + try + { + Listener = new TcpListener(IPAddress.Loopback, PortNumber); + Listener.Start(); + TcpThread = new Thread(() => RunTcp()); + TcpThread.Start(); + + } + catch (Exception Ex) + { + Listener = null; + Log.WriteLine("Unable to start automation server tcp listener: {0}", Ex.ToString()); + } } + + UriThread = new Thread(() => RunUri()); + UriThread.Start(); + } + catch (Exception Ex) + { + Log.WriteLine("Unable to start automation server: {0}", Ex.ToString()); } } @@ -188,15 +214,83 @@ namespace UnrealGameSync } } - public void Run() + void RunUri() { - try + // Handle main process command line URI request + if (!string.IsNullOrEmpty(CommandLineUri)) { - for(;;) + HandleUri(CommandLineUri); + } + + for (;;) + { + try { - TcpClient Client = CurrentClient = Listener.AcceptTcpClient(); + + IAsyncResult IPCResult = IPCStream.BeginWaitForConnection(null, null); + + int WaitResult = WaitHandle.WaitAny(new WaitHandle[] { ShutdownEvent, IPCResult.AsyncWaitHandle }); + + // Shutting down + if (WaitResult == 0) + { + break; + } + try { + IPCStream.EndWaitForConnection(IPCResult); + + Log.WriteLine("Accepted Uri connection"); + + // Read URI + string Uri = ReadString(IPCStream); + + Log.WriteLine("Received Uri: {0}", Uri); + + IPCStream.Disconnect(); + + HandleUri(Uri); + + } + catch (Exception Ex) + { + Log.WriteLine("Exception: {0}", Ex.ToString()); + } + } + catch (Exception Ex) + { + if (!bDisposing) + { + Log.WriteLine("Exception: {0}", Ex.ToString()); + } + } + } + } + + + void RunTcp() + { + + for (;;) + { + try + { + + IAsyncResult TCPResult = Listener.BeginAcceptTcpClient(null, null); + + int WaitResult = WaitHandle.WaitAny(new WaitHandle[] { ShutdownEvent, TCPResult.AsyncWaitHandle }); + + // Shutting down + if (WaitResult == 0) + { + break; + } + + try + { + TcpClient Client = Listener.EndAcceptTcpClient(TCPResult); + Log.WriteLine("Accepted connection from {0}", Client.Client.RemoteEndPoint); NetworkStream Stream = Client.GetStream(); @@ -205,7 +299,7 @@ namespace UnrealGameSync Log.WriteLine("Received input: {0} (+{1} bytes)", Input.Type, Input.Data.Length); AutomationRequestOutput Output; - using(AutomationRequest Request = new AutomationRequest(Input)) + using (AutomationRequest Request = new AutomationRequest(Input)) { PostRequest(Request); Request.Complete.Wait(); @@ -215,50 +309,137 @@ namespace UnrealGameSync Output.Write(Stream); Log.WriteLine("Sent output: {0} (+{1} bytes)", Output.Result, Output.Data.Length); } - catch(Exception Ex) + catch (Exception Ex) { Log.WriteLine("Exception: {0}", Ex.ToString()); } finally { - Client.Close(); + TCPResult = null; Log.WriteLine("Closed connection."); } - CurrentClient = null; } - } - catch(Exception Ex) - { - if(!bDisposing) + catch (Exception Ex) { - Log.WriteLine("Exception: {0}", Ex.ToString()); + if (!bDisposing) + { + Log.WriteLine("Exception: {0}", Ex.ToString()); + } } } - Log.WriteLine("Closing socket."); } - + + void HandleUri(string Uri) + { + try + { + UriResult Result = UriHandler.HandleUri(Uri); + if (!Result.Success) + { + if (!string.IsNullOrEmpty(Result.Error)) + { + MessageBox.Show(String.Format("Error handling uri: {0}", Result.Error)); + } + + return; + } + + if (Result.Request != null) + { + PostRequest(Result.Request); + Result.Request.Complete.Wait(); + Result.Request.Dispose(); + } + } + catch { } + } + + /// + /// Sends UGS scope URI from secondary process to main for handling + /// + public static void SendUri(string Uri) + { + using (NamedPipeClientStream ClientStream = new NamedPipeClientStream(".", UGSChannel, PipeDirection.Out, PipeOptions.None)) + { + try + { + ClientStream.Connect(5000); + WriteString(ClientStream, Uri); + } + catch (Exception) + { + + } + } + } + + static string ReadString(Stream Stream) + { + int Len = Stream.ReadByte() * 256; + Len += Stream.ReadByte(); + byte[] InBuffer = new byte[Len]; + Stream.Read(InBuffer, 0, Len); + + return StreamEncoding.GetString(InBuffer); + } + + static void WriteString(Stream Stream, string Output) + { + byte[] OutBuffer = StreamEncoding.GetBytes(Output); + + int Len = OutBuffer.Length; + + if (Len > ushort.MaxValue) + { + Len = ushort.MaxValue; + } + + Stream.WriteByte((byte)(Len / 256)); + Stream.WriteByte((byte)(Len & 255)); + Stream.Write(OutBuffer, 0, Len); + Stream.Flush(); + } + + public void Dispose() { + const int Timeout = 5000; bDisposing = true; + ShutdownEvent.Set(); - TcpClient Client = CurrentClient; - if(Client != null) + if (UriThread != null) { - try { Client.Close(); } catch { } - Client = null; + if (!UriThread.Join(Timeout)) + { + try { UriThread.Abort(); } catch { } + } + + UriThread = null; } - if(Listener != null) + if (TcpThread != null) + { + if (!TcpThread.Join(Timeout)) + { + try { TcpThread.Abort(); } catch { } + } + + TcpThread = null; + } + + + // clean up IPC stream + if (IPCStream != null) + { + IPCStream.Dispose(); + } + + if (Listener != null) { Listener.Stop(); Listener = null; } - if(BackgroundThread != null) - { - BackgroundThread.Join(); - BackgroundThread = null; - } } } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/BuildStep.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/BuildStep.cs index aa5bb15e2333..3b6d7bf4ed3e 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/BuildStep.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/BuildStep.cs @@ -1,4 +1,4 @@ -// Copyright Epic Games, Inc. All Rights Reserved. +// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; @@ -38,6 +38,20 @@ namespace UnrealGameSync public bool bScheduledSync; public bool bShowAsTool; + public BuildStep(Guid InUniqueId, int InOrderIndex, string InDescription, string InStatusText, int InEstimatedDuration, string InFileName, string InArguments, string InWorkingDir, bool bInUseLogWindow) + { + UniqueId = InUniqueId; + OrderIndex = InOrderIndex; + Description = InDescription; + StatusText = InStatusText; + EstimatedDuration = InEstimatedDuration; + Type = BuildStepType.Other; + FileName = InFileName; + Arguments = InArguments; + WorkingDir = InWorkingDir; + bUseLogWindow = bInUseLogWindow; + } + public BuildStep(Guid InUniqueId, int InOrderIndex, string InDescription, string InStatusText, int InEstimatedDuration, string InTarget, string InPlatform, string InConfiguration, string InArguments, bool bInSyncDefault) { UniqueId = InUniqueId; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ConfigFile.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ConfigFile.cs index 54f135fff3a9..5a5f7a06efa0 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ConfigFile.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ConfigFile.cs @@ -431,11 +431,38 @@ namespace UnrealGameSync { } + string GetTempFileName(string FileName) + { + return FileName + ".tmp"; + } + public void Load(string FileName) { Parse(File.ReadAllLines(FileName)); } + public bool TryLoad(string FileName, TextWriter Log) + { + FileInfo FileInfo = new FileInfo(FileName); + if (FileInfo.Exists) + { + Log.WriteLine("Loading config file from {0} ({1} bytes)", FileInfo.FullName, FileInfo.Length); + Load(FileInfo.FullName); + return true; + } + + FileInfo TempFileInfo = new FileInfo(GetTempFileName(FileName)); + if (TempFileInfo.Exists) + { + Log.WriteLine("Loading temporary config file from {0} ({1} bytes)", TempFileInfo.FullName, TempFileInfo.Length); + Load(TempFileInfo.FullName); + return true; + } + + Log.WriteLine("No existing config file at {0}", FileName); + return false; + } + public void Parse(string[] Lines) { ConfigSection CurrentSection = null; @@ -471,7 +498,10 @@ namespace UnrealGameSync public void Save(string FileName) { - using(StreamWriter Writer = new StreamWriter(FileName)) + string TempFileName = GetTempFileName(FileName); + File.Delete(TempFileName); + + using (StreamWriter Writer = new StreamWriter(TempFileName)) { for(int Idx = 0; Idx < Sections.Count; Idx++) { @@ -496,6 +526,9 @@ namespace UnrealGameSync } } } + + File.Delete(FileName); + File.Move(TempFileName, FileName); } public ConfigSection FindSection(string Name) diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.Designer.cs index 76cde76e9c64..71e6c419e25a 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.Designer.cs @@ -85,6 +85,7 @@ namespace UnrealGameSync this.BuildListContextMenu_CustomTool_End = new System.Windows.Forms.ToolStripSeparator(); this.BuildListContextMenu_ViewInSwarm = new System.Windows.Forms.ToolStripMenuItem(); this.BuildListContextMenu_MoreInfo = new System.Windows.Forms.ToolStripMenuItem(); + this.BuildListContextMenu_CopyChangelistNumber = new System.Windows.Forms.ToolStripMenuItem(); this.NotifyIcon = new System.Windows.Forms.NotifyIcon(this.components); this.toolStripSeparator7 = new System.Windows.Forms.ToolStripSeparator(); this.BuildListToolTip = new System.Windows.Forms.ToolTip(this.components); @@ -484,6 +485,7 @@ namespace UnrealGameSync this.BuildListContextMenu_CustomTool_Start, this.BuildListContextMenu_CustomTool_End, this.BuildListContextMenu_ViewInSwarm, + this.BuildListContextMenu_CopyChangelistNumber, this.BuildListContextMenu_MoreInfo}); this.BuildListContextMenu.Name = "BuildListContextMenu"; this.BuildListContextMenu.Size = new System.Drawing.Size(199, 612); @@ -699,6 +701,13 @@ namespace UnrealGameSync this.BuildListContextMenu_MoreInfo.Size = new System.Drawing.Size(198, 22); this.BuildListContextMenu_MoreInfo.Text = "More Info..."; this.BuildListContextMenu_MoreInfo.Click += new System.EventHandler(this.BuildListContextMenu_MoreInfo_Click); + // + // BuildListContextMenu_CopyChangelistNumber + // + this.BuildListContextMenu_CopyChangelistNumber.Name = "BuildListContextMenu_CopyChangelistNumber"; + this.BuildListContextMenu_CopyChangelistNumber.Size = new System.Drawing.Size(199, 22); + this.BuildListContextMenu_CopyChangelistNumber.Text = "Copy Changelist"; + this.BuildListContextMenu_CopyChangelistNumber.Click += new System.EventHandler(this.BuildListContextMenu_CopyChangelistNumber_Click); // // toolStripSeparator7 // @@ -1387,5 +1396,6 @@ namespace UnrealGameSync private System.Windows.Forms.ToolStripMenuItem pS4ToolStripMenuItem; private System.IO.FileSystemWatcher EditorConfigWatcher; private System.Windows.Forms.ToolStripMenuItem BuildListContextMenu_ViewInSwarm; + private System.Windows.Forms.ToolStripMenuItem BuildListContextMenu_CopyChangelistNumber; } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.cs index 35611cdd7f51..eb31d0376664 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.cs @@ -37,6 +37,9 @@ namespace UnrealGameSync void ModifyApplicationSettings(); void UpdateAlertWindows(); void UpdateTintColors(); + + IssueMonitor CreateIssueMonitor(string ApiUrl, string UserName); + void ReleaseIssueMonitor(IssueMonitor IssueMonitor); } delegate void WorkspaceStartupCallback(WorkspaceControl Workspace, bool bCancel); @@ -309,7 +312,7 @@ namespace UnrealGameSync System.Threading.Timer StartupTimer; List StartupCallbacks; - public WorkspaceControl(IWorkspaceControlOwner InOwner, string InApiUrl, string InOriginalExecutableFileName, bool bInUnstable, DetectProjectSettingsTask DetectSettings, IssueMonitor InIssueMonitor, LineBasedTextWriter InLog, UserSettings InSettings) + public WorkspaceControl(IWorkspaceControlOwner InOwner, string InApiUrl, string InOriginalExecutableFileName, bool bInUnstable, DetectProjectSettingsTask DetectSettings, LineBasedTextWriter InLog, UserSettings InSettings) { InitializeComponent(); @@ -320,7 +323,6 @@ namespace UnrealGameSync DataFolder = DetectSettings.DataFolder; OriginalExecutableFileName = InOriginalExecutableFileName; bUnstable = bInUnstable; - IssueMonitor = InIssueMonitor; Log = InLog; Settings = InSettings; WorkspaceSettings = InSettings.FindOrAddWorkspace(DetectSettings.BranchClientPath); @@ -374,6 +376,13 @@ namespace UnrealGameSync CurrentSyncFilterHash = WorkspaceSettings.CurrentSyncFilterHash; } + // Figure out which API server to use + string NewApiUrl; + if (TryGetProjectSetting(DetectSettings.LatestProjectConfigFile, "ApiUrl", out NewApiUrl)) + { + ApiUrl = NewApiUrl; + } + string TelemetryProjectIdentifier = PerforceUtils.GetClientOrDepotDirectoryName(DetectSettings.NewSelectedProjectIdentifier); Workspace = new Workspace(PerforceClient, BranchDirectoryName, SelectedFileName, DetectSettings.BranchClientPath, DetectSettings.NewSelectedClientFileName, CurrentChangeNumber, CurrentSyncFilterHash, WorkspaceSettings.LastBuiltChangeNumber, TelemetryProjectIdentifier, DetectSettings.bIsEnterpriseProject, new LogControlTextWriter(SyncLog)); @@ -410,6 +419,8 @@ namespace UnrealGameSync StartupTimer = new System.Threading.Timer(x => MainThreadSynchronizationContext.Post((o) => { if (!IsDisposed) { StartupTimerElapsed(false); } }, null), null, TimeSpan.FromSeconds(20.0), TimeSpan.FromMilliseconds(-1.0)); StartupCallbacks = new List(); + string IssuesApiUrl = GetIssuesApiUrl(); + IssueMonitor = InOwner.CreateIssueMonitor(IssuesApiUrl, DetectSettings.PerforceClient.UserName); IssueMonitor.OnIssuesChanged += IssueMonitor_OnIssuesChangedAsync; if (SelectedFileName.EndsWith(".uproject", StringComparison.OrdinalIgnoreCase)) @@ -700,6 +711,7 @@ namespace UnrealGameSync if (IssueMonitor != null) { IssueMonitor.OnIssuesChanged -= IssueMonitor_OnIssuesChangedAsync; + Owner.ReleaseIssueMonitor(IssueMonitor); IssueMonitor = null; } if (StartupTimer != null) @@ -874,23 +886,26 @@ namespace UnrealGameSync void StartSync(int ChangeNumber) { - StartSync(ChangeNumber, null); + StartSync(ChangeNumber, false, null); } - void StartSync(int ChangeNumber, WorkspaceUpdateCallback Callback) + void StartSync(int ChangeNumber, bool bSyncOnly, WorkspaceUpdateCallback Callback) { WorkspaceUpdateOptions Options = WorkspaceUpdateOptions.Sync | WorkspaceUpdateOptions.SyncArchives | WorkspaceUpdateOptions.GenerateProjectFiles; - if (Settings.bBuildAfterSync) + if (!bSyncOnly) { - Options |= WorkspaceUpdateOptions.Build; - } - if ((Settings.bBuildAfterSync || ShouldSyncPrecompiledEditor) && Settings.bRunAfterSync) - { - Options |= WorkspaceUpdateOptions.RunAfterSync; - } - if (Settings.bOpenSolutionAfterSync) - { - Options |= WorkspaceUpdateOptions.OpenSolutionAfterSync; + if (Settings.bBuildAfterSync) + { + Options |= WorkspaceUpdateOptions.Build; + } + if ((Settings.bBuildAfterSync || ShouldSyncPrecompiledEditor) && Settings.bRunAfterSync) + { + Options |= WorkspaceUpdateOptions.RunAfterSync; + } + if (Settings.bOpenSolutionAfterSync) + { + Options |= WorkspaceUpdateOptions.OpenSolutionAfterSync; + } } StartWorkspaceUpdate(ChangeNumber, Options, Callback); } @@ -1441,8 +1456,28 @@ namespace UnrealGameSync } } + string GetIssuesApiUrl() + { + string IssuesApiUrl; + if (!TryGetProjectSetting(PerforceMonitor.LatestProjectConfigFile, "IssuesApiUrl", out IssuesApiUrl)) + { + IssuesApiUrl = ApiUrl; + } + return IssuesApiUrl; + } + void UpdateBuildMetadata() { + // Refresh the issue monitor if it's changed + string IssuesApiUrl = GetIssuesApiUrl(); + if (IssuesApiUrl != IssueMonitor.ApiUrl) + { + Log.WriteLine("Changing issues API url from {0} to {1}", IssueMonitor.ApiUrl, IssuesApiUrl); + IssueMonitor NewIssueMonitor = Owner.CreateIssueMonitor(IssuesApiUrl, Workspace.Perforce.UserName); + Owner.ReleaseIssueMonitor(IssueMonitor); + IssueMonitor = NewIssueMonitor; + } + // Update the column settings first, since this may affect the badges we hide UpdateColumnSettings(); @@ -2792,7 +2827,7 @@ namespace UnrealGameSync } else { - StartSync(Change.Number, null); + StartSync(Change.Number, false, null); } } } @@ -3281,7 +3316,7 @@ namespace UnrealGameSync bool bHasOtherAssignedIssue = false; foreach (IssueData Issue in IssueMonitor.GetIssues()) { - if (Issue.Project == BuildHealthProject) + if (Issue.Projects.Contains(BuildHealthProject)) { Issues.Add(Issue); } @@ -3377,7 +3412,10 @@ namespace UnrealGameSync private void BuildHealthContextMenu_Settings_Click(object sender, EventArgs e) { - IssueSettingsWindow IssueSettings = new IssueSettingsWindow(Settings); + string BuildHealthProject; + TryGetProjectSetting(PerforceMonitor.LatestProjectConfigFile, "BuildHealthProject", out BuildHealthProject); + + IssueSettingsWindow IssueSettings = new IssueSettingsWindow(Settings, BuildHealthProject ?? ""); if (IssueSettings.ShowDialog(this) == DialogResult.OK) { Owner.UpdateAlertWindows(); @@ -3691,7 +3729,7 @@ namespace UnrealGameSync Rectangle SyncBadgeRectangle = GetSyncBadgeRectangle(HitTest.Item.SubItems[StatusColumn.Index].Bounds); if (SyncBadgeRectangle.Contains(Args.Location) && CanSyncChange(Change.Number)) { - StartSync(Change.Number, null); + StartSync(Change.Number, false, null); } } @@ -4158,6 +4196,32 @@ namespace UnrealGameSync BadgeFont = new Font(BuildFont.FontFamily, BuildFont.SizeInPoints - 2, FontStyle.Bold); } + public void ExecCommand(string Description, string StatusText, string FileName, string Arguments, string WorkingDir, bool bUseLogWindow) + { + if (Workspace != null && !Workspace.IsBusy()) + { + Guid Id = Guid.NewGuid(); + + BuildStep CustomBuildStep = new BuildStep(Guid.NewGuid(), 0, Description, StatusText, 1, FileName, Arguments, WorkingDir, bUseLogWindow); + + List UserBuildSteps = new List(); + UserBuildSteps.Add(CustomBuildStep.ToConfigObject()); + + WorkspaceUpdateContext Context = new WorkspaceUpdateContext(Workspace.CurrentChangeNumber, WorkspaceUpdateOptions.Build, null, GetDefaultBuildStepObjects(), UserBuildSteps, new HashSet { CustomBuildStep.UniqueId }, GetWorkspaceVariables(Workspace.CurrentChangeNumber)); + StartWorkspaceUpdate(Context, null); + } + } + + public void SyncChange(int ChangeNumber, bool bSyncOnly, WorkspaceUpdateCallback Callback) + { + if(Workspace != null) + { + Owner.ShowAndActivate(); + SelectChange(ChangeNumber); + StartSync(ChangeNumber, bSyncOnly, Callback); + } + } + public void SyncLatestChange() { SyncLatestChange(null); @@ -4178,9 +4242,7 @@ namespace UnrealGameSync } else if (ChangeNumber >= PerforceMonitor.LastChangeByCurrentUser || MessageBox.Show(String.Format("The changelist that would be synced is before the last change you submitted.\n\nIf you continue, your changes submitted after CL {0} will be locally removed from your workspace until you can sync past them again.\n\nAre you sure you want to continue?", ChangeNumber), "Local changes will be removed", MessageBoxButtons.YesNo) == DialogResult.Yes) { - Owner.ShowAndActivate(); - SelectChange(ChangeNumber); - StartSync(ChangeNumber, Callback); + SyncChange(ChangeNumber, false, Callback); } } } @@ -4256,6 +4318,11 @@ namespace UnrealGameSync } } + private void BuildListContextMenu_CopyChangelistNumber_Click(object sender, EventArgs e) + { + System.Windows.Forms.Clipboard.SetText(ContextMenuChange.Number.ToString()); + } + private void BuildListContextMenu_AddStar_Click(object sender, EventArgs e) { if (MessageBox.Show("Starred builds are meant to convey a stable, verified build to the rest of the team. Do not star a build unless it has been fully tested.\n\nAre you sure you want to star this build?", "Confirm star", MessageBoxButtons.YesNo) == DialogResult.Yes) @@ -4955,6 +5022,7 @@ namespace UnrealGameSync } DiagnosticsText.AppendFormat("Perforce monitor: {0}\n", (PerforceMonitor == null) ? "(inactive)" : PerforceMonitor.LastStatusMessage); DiagnosticsText.AppendFormat("Event monitor: {0}\n", (EventMonitor == null) ? "(inactive)" : EventMonitor.LastStatusMessage); + DiagnosticsText.AppendFormat("Issue monitor: {0}\n", (IssueMonitor == null) ? "(inactive)" : IssueMonitor.LastStatusMessage); DiagnosticsWindow Diagnostics = new DiagnosticsWindow(DataFolder, DiagnosticsText.ToString()); Diagnostics.ShowDialog(this); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DeploymentSettings.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DeploymentSettings.cs index 40a161c9d5f1..02c8cd163d5e 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DeploymentSettings.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DeploymentSettings.cs @@ -31,6 +31,11 @@ namespace UnrealGameSync /// public static readonly string ApiUrl = null; + /// + /// Servers to connect to for issue details by default + /// + public static readonly List DefaultIssueApiUrls = new List(); + /// /// Specifies the depot path to sync down the stable version of UGS from, without a trailing slash (eg. //depot/UnrealGameSync/bin). This is a site-specific setting. /// The UnrealGameSync executable should be located at Release/UnrealGameSync.exe under this path, with any dependent DLLs. @@ -43,5 +48,21 @@ namespace UnrealGameSync /// public static readonly CreateTelemetrySinkDelegate CreateTelemetrySink = (UserName, SessionId, Log) => new NullTelemetrySink(); #endif + +#if !UGS_LAUNCHER + /// + /// Delegate to allow validating a project being opened + /// + /// The detected settings for the project + /// The logger + /// Receives an error on failure + /// + public delegate bool DetectProjectSettingsEvent(DetectProjectSettingsTask Task, TextWriter Log, out string Error); + + /// + /// Called to validate the project settings + /// + public static DetectProjectSettingsEvent OnDetectProjectSettings = null; +#endif } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DetectProjectSettingsTask.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DetectProjectSettingsTask.cs index bb198aa2c53a..273a00584993 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DetectProjectSettingsTask.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DetectProjectSettingsTask.cs @@ -296,6 +296,17 @@ namespace UnrealGameSync LocalConfigFiles = new List>(); LatestProjectConfigFile = PerforceMonitor.ReadProjectConfigFile(PerforceClient, BranchClientPath, NewSelectedClientFileName, CacheFolder, LocalConfigFiles, Log); + // Run any event hooks + if (DeploymentSettings.OnDetectProjectSettings != null) + { + string Message; + if (!DeploymentSettings.OnDetectProjectSettings(this, Log, out Message)) + { + ErrorMessage = Message; + return false; + } + } + // Succeed! ErrorMessage = null; return true; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/EventMonitor.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/EventMonitor.cs index e6bc278e23b4..dfc2700f5e85 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/EventMonitor.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/EventMonitor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.Diagnostics; using System.Linq; using System.Text.Json; @@ -32,6 +33,7 @@ namespace UnrealGameSync class LatestData { + public int Version { get; set; } = 1; public long LastEventId { get; set; } public long LastCommentId { get; set; } public long LastBuildId { get; set; } @@ -134,6 +136,59 @@ namespace UnrealGameSync Mixed, } + public enum UgsUserVote + { + None, + CompileSuccess, + CompileFailure, + Good, + Bad + } + + class GetUserDataResponseV2 + { + public string User { get; set; } + public long? SyncTime { get; set; } + public UgsUserVote? Vote { get; set; } + public string Comment { get; set; } + public bool Investigating { get; set; } + public bool Starred { get; set; } + } + + class GetBadgeDataResponseV2 + { + public string Name { get; set; } + public string Url { get; set; } + public BadgeResult State { get; set; } + } + + class GetMetadataResponseV2 + { + public int Change { get; set; } + public string Project { get; set; } + public List Users { get; set; } + public List Badges { get; set; } + } + + class GetMetadataListResponseV2 + { + public long SequenceNumber { get; set; } + public List Items { get; set; } + } + + class UpdateMetadataRequestV2 + { + public string Stream { get; set; } + public int Change { get; set; } + public string Project { get; set; } + public string UserName { get; set; } + public bool? Synced { get; set; } + public string Vote { get; set; } + public bool? Investigating { get; set; } + public bool? Starred { get; set; } + public string Comment { get; set; } + } + class EventSummary { public int ChangeNumber; @@ -149,6 +204,7 @@ namespace UnrealGameSync class EventMonitor : IDisposable { string ApiUrl; + int ApiVersion; string Project; string CurrentUserName; Thread WorkerThread; @@ -158,7 +214,7 @@ namespace UnrealGameSync ConcurrentQueue OutgoingComments = new ConcurrentQueue(); ConcurrentQueue IncomingComments = new ConcurrentQueue(); ConcurrentQueue IncomingBadges = new ConcurrentQueue(); - Dictionary ChangeNumberToSummary = new Dictionary(); + SortedDictionary ChangeNumberToSummary = new SortedDictionary(); Dictionary UserNameToLastSyncEvent = new Dictionary(StringComparer.InvariantCultureIgnoreCase); Dictionary BadgeNameToLatestData = new Dictionary(); BoundedLogWriter LogWriter; @@ -168,6 +224,15 @@ namespace UnrealGameSync List InvestigationEvents = new List(); List ActiveInvestigations = new List(); + // MetadataV2 + string MetadataStream; + string MetadataProject; + long IncomingMetadataId; + ConcurrentQueue IncomingMetadata = new ConcurrentQueue(); + int MinChange; + int NewMinChange; + long MetadataSequenceNumber; + public event Action OnUpdatesReady; public EventMonitor(string InApiUrl, string InProject, string InCurrentUserName, string InLogFileName) @@ -177,6 +242,21 @@ namespace UnrealGameSync CurrentUserName = InCurrentUserName; LatestIds = new LatestData { LastBuildId = 0, LastCommentId = 0, LastEventId = 0 }; + MetadataStream = Project.ToLowerInvariant().TrimEnd('/'); + if (MetadataStream.StartsWith("//", StringComparison.Ordinal)) + { + int NextIdx = MetadataStream.IndexOf('/', 2); + if (NextIdx != -1) + { + NextIdx = MetadataStream.IndexOf('/', NextIdx + 1); + if (NextIdx != -1) + { + MetadataProject = MetadataStream.Substring(NextIdx + 1); + MetadataStream = MetadataStream.Substring(0, NextIdx); + } + } + } + LogWriter = new BoundedLogWriter(InLogFileName); if(ApiUrl == null) { @@ -219,6 +299,31 @@ namespace UnrealGameSync // Build a lookup for all the change numbers FilterChangeNumbers = new HashSet(ChangeNumbers); + // Figure out the minimum changelist number to fetch + int PrevNewMinChange = NewMinChange; + if (ChangeNumbers.Any()) + { + NewMinChange = ChangeNumbers.Min(x => x); + } + else + { + NewMinChange = 0; + } + + // Remove any changes which are no longer relevant + if (ApiVersion == 2) + { + while (ChangeNumberToSummary.Count > 0) + { + int FirstChange = ChangeNumberToSummary.Keys.First(); + if (FirstChange >= NewMinChange) + { + break; + } + ChangeNumberToSummary.Remove(FirstChange); + } + } + // Clear out the list of active users for each review we have UserNameToLastSyncEvent.Clear(); foreach(EventSummary Summary in ChangeNumberToSummary.Values) @@ -237,6 +342,12 @@ namespace UnrealGameSync // Clear the list of active investigations, since this depends on the changes we're showing ActiveInvestigations = null; + + // Trigger an update if there's something to do + if (NewMinChange < PrevNewMinChange || (NewMinChange != 0 && PrevNewMinChange == 0)) + { + RefreshEvent.Set(); + } } protected EventSummary FindOrAddSummary(int ChangeNumber) @@ -259,6 +370,12 @@ namespace UnrealGameSync public void ApplyUpdates() { + GetMetadataResponseV2 Metadata; + while (IncomingMetadata.TryDequeue(out Metadata)) + { + ConvertMetadataToEvents(Metadata); + } + EventData Event; while(IncomingEvents.TryDequeue(out Event)) { @@ -398,6 +515,89 @@ namespace UnrealGameSync return true; } + static EventType? GetEventTypeFromVote(UgsUserVote? State) + { + if (State != null) + { + switch (State.Value) + { + case UgsUserVote.CompileSuccess: + return EventType.Compiles; + case UgsUserVote.CompileFailure: + return EventType.DoesNotCompile; + case UgsUserVote.Good: + return EventType.Good; + case UgsUserVote.Bad: + return EventType.Bad; + } + } + return null; + } + + void ConvertMetadataToEvents(GetMetadataResponseV2 Metadata) + { + EventSummary Summary; + if (ChangeNumberToSummary.TryGetValue(Metadata.Change, out Summary)) + { + foreach (string CurrentUser in Summary.CurrentUsers) + { + UserNameToLastSyncEvent.Remove(CurrentUser); + } + ChangeNumberToSummary.Remove(Metadata.Change); + } + + if (Metadata.Badges != null) + { + foreach (GetBadgeDataResponseV2 BadgeData in Metadata.Badges) + { + BadgeData Badge = new BadgeData(); + Badge.ChangeNumber = Metadata.Change; + Badge.BuildType = BadgeData.Name; + Badge.Result = BadgeData.State; + Badge.Url = BadgeData.Url; + Badge.Project = Metadata.Project; + IncomingBadges.Enqueue(Badge); + } + } + + if (Metadata.Users != null) + { + foreach (GetUserDataResponseV2 UserData in Metadata.Users) + { + if (UserData.SyncTime != null) + { + EventData Event = new EventData { Id = UserData.SyncTime.Value, Change = Metadata.Change, Project = Metadata.Project, UserName = UserData.User, Type = EventType.Syncing }; + IncomingEvents.Enqueue(Event); + } + + EventType? Type = GetEventTypeFromVote(UserData.Vote); + if (Type != null) + { + EventData Event = new EventData { Id = ++IncomingMetadataId, Change = Metadata.Change, Project = Metadata.Project, UserName = UserData.User, Type = Type.Value }; + IncomingEvents.Enqueue(Event); + } + + if (UserData.Investigating) + { + EventData Event = new EventData { Id = ++IncomingMetadataId, Change = Metadata.Change, Project = Metadata.Project, UserName = UserData.User, Type = EventType.Starred }; + IncomingEvents.Enqueue(Event); + } + + if (UserData.Starred) + { + EventData Event = new EventData { Id = ++IncomingMetadataId, Change = Metadata.Change, Project = Metadata.Project, UserName = UserData.User, Type = EventType.Starred }; + IncomingEvents.Enqueue(Event); + } + + if (UserData.Comment != null) + { + CommentData Comment = new CommentData { Id = ++IncomingMetadataId, ChangeNumber = Metadata.Change, Project = Metadata.Project, UserName = UserData.User, Text = UserData.Comment }; + IncomingComments.Enqueue(Comment); + } + } + } + } + static ReviewVerdict GetVerdict(IEnumerable Events, IEnumerable Badges) { int NumPositiveReviews = Events.Count(x => x.Type == EventType.Good); @@ -442,7 +642,7 @@ namespace UnrealGameSync void ApplyFilteredUpdate(EventData Event) { - if(Event.Type == EventType.Syncing && FilterChangeNumbers.Contains(Event.Change)) + if(Event.Type == EventType.Syncing && FilterChangeNumbers.Contains(Event.Change) && !String.IsNullOrEmpty(Event.UserName)) { // Update the active users list for this change EventData LastSync; @@ -499,7 +699,7 @@ namespace UnrealGameSync ReadEventsFromBackend(bUpdateThrottledRequests); // Send a notification that we're ready to update - if((IncomingEvents.Count > 0 || IncomingBadges.Count > 0 || IncomingComments.Count > 0) && OnUpdatesReady != null) + if((IncomingMetadata.Count > 0 || IncomingEvents.Count > 0 || IncomingBadges.Count > 0 || IncomingComments.Count > 0) && OnUpdatesReady != null) { OnUpdatesReady(); } @@ -516,8 +716,14 @@ namespace UnrealGameSync { Stopwatch Timer = Stopwatch.StartNew(); LogWriter.WriteLine("Posting event... ({0}, {1}, {2})", Event.Change, Event.UserName, Event.Type); - - RESTApi.POST(ApiUrl, "event", JsonSerializer.Serialize(Event)); + if (ApiVersion == 2) + { + SendMetadataUpdateUpdateV2(Event.Change, Event.Project, Event.UserName, Event.Type, null); + } + else + { + RESTApi.POST(ApiUrl, "event", JsonSerializer.Serialize(Event)); + } return true; } catch(Exception Ex) @@ -533,7 +739,14 @@ namespace UnrealGameSync { Stopwatch Timer = Stopwatch.StartNew(); LogWriter.WriteLine("Posting comment... ({0}, {1}, {2}, {3})", Comment.ChangeNumber, Comment.UserName, Comment.Text, Comment.Project); - RESTApi.POST(ApiUrl, "comment", JsonSerializer.Serialize(Comment)); + if (ApiVersion == 2) + { + SendMetadataUpdateUpdateV2(Comment.ChangeNumber, Comment.Project, Comment.UserName, null, Comment.Text); + } + else + { + RESTApi.POST(ApiUrl, "comment", JsonSerializer.Serialize(Comment)); + } LogWriter.WriteLine("Done in {0}ms.", Timer.ElapsedMilliseconds); return true; } @@ -544,6 +757,55 @@ namespace UnrealGameSync } } + void SendMetadataUpdateUpdateV2(int Change, string Project, string UserName, EventType? Event, string Comment) + { + UpdateMetadataRequestV2 Update = new UpdateMetadataRequestV2(); + Update.Stream = MetadataStream; + Update.Project = MetadataProject; + Update.Change = Change; + Update.UserName = UserName; + + if (Event != null) + { + switch (Event) + { + case EventType.Syncing: + Update.Synced = true; + break; + case EventType.Compiles: + Update.Vote = nameof(UgsUserVote.CompileSuccess); + break; + case EventType.DoesNotCompile: + Update.Vote = nameof(UgsUserVote.CompileFailure); + break; + case EventType.Good: + Update.Vote = nameof(UgsUserVote.Good); + break; + case EventType.Bad: + Update.Vote = nameof(UgsUserVote.Bad); + break; + case EventType.Unknown: + Update.Vote = nameof(UgsUserVote.None); + break; + case EventType.Starred: + Update.Starred = true; + break; + case EventType.Unstarred: + Update.Starred = false; + break; + case EventType.Investigating: + Update.Investigating = true; + break; + case EventType.Resolved: + Update.Investigating = false; + break; + } + } + Update.Comment = Comment; + + RESTApi.POST(ApiUrl, "metadata", JsonSerializer.Serialize(Update)); + } + bool ReadEventsFromBackend(bool bFireThrottledRequests) { try @@ -554,14 +816,55 @@ namespace UnrealGameSync ////////////// /// Initial Ids ////////////// - if (LatestIds.LastBuildId == 0 && LatestIds.LastCommentId == 0 && LatestIds.LastEventId == 0) + if (ApiVersion == 0) { LatestData InitialIds = RESTApi.GET(ApiUrl, "latest", string.Format("project={0}", Project)); + ApiVersion = (InitialIds.Version == 0)? 1 : InitialIds.Version; LatestIds.LastBuildId = InitialIds.LastBuildId; LatestIds.LastCommentId = InitialIds.LastCommentId; LatestIds.LastEventId = InitialIds.LastEventId; } + if (ApiVersion == 2) + { + int NewMinChangeCopy = NewMinChange; + if (NewMinChangeCopy != 0) + { + // If the range of changes has decreased, update the MinChange value before we fetch anything + MinChange = Math.Max(MinChange, NewMinChangeCopy); + + // Get the first part of the query + string CommonArgs = String.Format("stream={0}", MetadataStream); + if (MetadataProject != null) + { + CommonArgs += String.Format("&project={0}", MetadataProject); + } + + // Fetch any updates in the current range of changes + if (MinChange != 0) + { + GetMetadataListResponseV2 NewEventList = RESTApi.GET(ApiUrl, "metadata", String.Format("{0}&minchange={1}&sequence={2}", CommonArgs, MinChange, MetadataSequenceNumber)); + foreach (GetMetadataResponseV2 NewEvent in NewEventList.Items) + { + IncomingMetadata.Enqueue(NewEvent); + } + MetadataSequenceNumber = Math.Max(NewEventList.SequenceNumber, MetadataSequenceNumber); + } + + // Fetch any new changes + if (NewMinChangeCopy < MinChange) + { + List NewEvents = RESTApi.GET>(ApiUrl, "metadata", String.Format("{0}&minchange={1}&maxchange={2}", CommonArgs, NewMinChangeCopy, NewMinChange)); + foreach (GetMetadataResponseV2 NewEvent in NewEvents) + { + IncomingMetadata.Enqueue(NewEvent); + } + MinChange = NewMinChangeCopy; + } + } + } + else + { ////////////// /// Bulids ////////////// @@ -597,6 +900,7 @@ namespace UnrealGameSync LatestIds.LastCommentId = Math.Max(LatestIds.LastCommentId, Comment.Id); } } + } LastStatusMessage = String.Format("Last update took {0}ms", Timer.ElapsedMilliseconds); LogWriter.WriteLine("Done in {0}ms.", Timer.ElapsedMilliseconds); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/FindFoldersToCleanTask.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/FindFoldersToCleanTask.cs index 8e2f73fa1f25..1b344f98222d 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/FindFoldersToCleanTask.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/FindFoldersToCleanTask.cs @@ -54,6 +54,7 @@ namespace UnrealGameSync int RemainingFoldersToScan; ManualResetEvent FinishedScan = new ManualResetEvent(false); bool bAbortScan; + string ScanError; public List FileNames = new List(); @@ -80,20 +81,29 @@ namespace UnrealGameSync { if(!bAbortScan) { - if((Folder.Directory.Attributes & FileAttributes.ReparsePoint) == 0) + try { - foreach(DirectoryInfo SubDirectory in Folder.Directory.EnumerateDirectories()) + if ((Folder.Directory.Attributes & FileAttributes.ReparsePoint) == 0) { - FolderToClean SubFolder = new FolderToClean(SubDirectory); - Folder.NameToSubFolder[SubFolder.Name] = SubFolder; - QueueFolderToPopulate(SubFolder); - } - foreach(FileInfo File in Folder.Directory.EnumerateFiles()) - { - FileAttributes Attributes = File.Attributes; // Force the value to be cached. - Folder.NameToFile[File.Name] = File; + foreach (DirectoryInfo SubDirectory in Folder.Directory.EnumerateDirectories()) + { + FolderToClean SubFolder = new FolderToClean(SubDirectory); + Folder.NameToSubFolder[SubFolder.Name] = SubFolder; + QueueFolderToPopulate(SubFolder); + } + foreach (FileInfo File in Folder.Directory.EnumerateFiles()) + { + FileAttributes Attributes = File.Attributes; // Force the value to be cached. + Folder.NameToFile[File.Name] = File; + } } } + catch (Exception Ex) + { + string NewError = String.Format("Unable to enumerate contents of {0} due to an error:\n\n{1}", Folder.Directory.FullName, Ex); + Interlocked.CompareExchange(ref ScanError, NewError, null); + bAbortScan = true; + } } if(Interlocked.Decrement(ref RemainingFoldersToScan) == 0) @@ -201,6 +211,9 @@ namespace UnrealGameSync Log.WriteLine("Finding files in workspace..."); Log.WriteLine(); + // Clear the current error + ScanError = null; + // Start enumerating all the files that exist locally foreach(string SyncPath in SyncPaths) { @@ -300,6 +313,13 @@ namespace UnrealGameSync // Wait to finish scanning the directory FinishedScan.WaitOne(); + // Check if there was an error + if (ScanError != null) + { + ErrorMessage = ScanError; + return false; + } + // Find the value of the P4CONFIG variable string PerforceConfigFile; PerforceClient.GetSetting("P4CONFIG", out PerforceConfigFile, Log); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.Designer.cs index 3fd0b76dd150..9b82ab8b942b 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.Designer.cs @@ -35,8 +35,6 @@ namespace UnrealGameSync this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); this.label2 = new System.Windows.Forms.Label(); this.ParallelSyncThreadsSpinner = new System.Windows.Forms.NumericUpDown(); - this.UserNameTextBox = new UnrealGameSync.TextBoxWithCueBanner(); - this.ServerTextBox = new UnrealGameSync.TextBoxWithCueBanner(); this.label3 = new System.Windows.Forms.Label(); this.OkBtn = new System.Windows.Forms.Button(); this.CancelBtn = new System.Windows.Forms.Button(); @@ -50,12 +48,16 @@ namespace UnrealGameSync this.label6 = new System.Windows.Forms.Label(); this.tableLayoutPanel5 = new System.Windows.Forms.TableLayoutPanel(); this.UseUnstableBuildCheckBox = new System.Windows.Forms.CheckBox(); - this.DepotPathTextBox = new UnrealGameSync.TextBoxWithCueBanner(); this.groupBox4 = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.EnableProtocolHandlerCheckBox = new System.Windows.Forms.CheckBox(); this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); this.EnableAutomationCheckBox = new System.Windows.Forms.CheckBox(); this.AutomationPortTextBox = new System.Windows.Forms.TextBox(); this.AdvancedBtn = new System.Windows.Forms.Button(); + this.DepotPathTextBox = new UnrealGameSync.TextBoxWithCueBanner(); + this.UserNameTextBox = new UnrealGameSync.TextBoxWithCueBanner(); + this.ServerTextBox = new UnrealGameSync.TextBoxWithCueBanner(); this.groupBox1.SuspendLayout(); this.tableLayoutPanel1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.ParallelSyncThreadsSpinner)).BeginInit(); @@ -65,6 +67,7 @@ namespace UnrealGameSync this.tableLayoutPanel4.SuspendLayout(); this.tableLayoutPanel5.SuspendLayout(); this.groupBox4.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); this.flowLayoutPanel1.SuspendLayout(); this.SuspendLayout(); // @@ -147,24 +150,6 @@ namespace UnrealGameSync 0, 0, 0}); - // - // UserNameTextBox - // - this.UserNameTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.UserNameTextBox.CueBanner = "Default"; - this.UserNameTextBox.Location = new System.Drawing.Point(133, 39); - this.UserNameTextBox.Name = "UserNameTextBox"; - this.UserNameTextBox.Size = new System.Drawing.Size(651, 23); - this.UserNameTextBox.TabIndex = 1; - // - // ServerTextBox - // - this.ServerTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.ServerTextBox.CueBanner = "Default"; - this.ServerTextBox.Location = new System.Drawing.Point(133, 5); - this.ServerTextBox.Name = "ServerTextBox"; - this.ServerTextBox.Size = new System.Drawing.Size(651, 23); - this.ServerTextBox.TabIndex = 0; // // label3 // @@ -180,7 +165,7 @@ namespace UnrealGameSync // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(661, 430); + this.OkBtn.Location = new System.Drawing.Point(661, 460); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(89, 27); this.OkBtn.TabIndex = 2; @@ -192,7 +177,7 @@ namespace UnrealGameSync // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(756, 430); + this.CancelBtn.Location = new System.Drawing.Point(756, 460); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(89, 27); this.CancelBtn.TabIndex = 3; @@ -319,35 +304,55 @@ namespace UnrealGameSync this.UseUnstableBuildCheckBox.Text = "Use Unstable Build"; this.UseUnstableBuildCheckBox.UseVisualStyleBackColor = true; // - // DepotPathTextBox - // - this.DepotPathTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.DepotPathTextBox.CueBanner = null; - this.DepotPathTextBox.Location = new System.Drawing.Point(3, 3); - this.DepotPathTextBox.Name = "DepotPathTextBox"; - this.DepotPathTextBox.Size = new System.Drawing.Size(558, 23); - this.DepotPathTextBox.TabIndex = 0; - // // groupBox4 // - this.groupBox4.Controls.Add(this.flowLayoutPanel1); + this.groupBox4.Controls.Add(this.tableLayoutPanel2); this.groupBox4.Location = new System.Drawing.Point(17, 344); this.groupBox4.Name = "groupBox4"; - this.groupBox4.Size = new System.Drawing.Size(822, 73); + this.groupBox4.Size = new System.Drawing.Size(822, 93); this.groupBox4.TabIndex = 5; this.groupBox4.TabStop = false; - this.groupBox4.Text = "Automation"; + this.groupBox4.Text = "Integration"; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel2.ColumnCount = 1; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel2.Controls.Add(this.EnableProtocolHandlerCheckBox, 0, 1); + this.tableLayoutPanel2.Controls.Add(this.flowLayoutPanel1, 0, 0); + this.tableLayoutPanel2.Location = new System.Drawing.Point(18, 23); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 2; + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel2.Size = new System.Drawing.Size(787, 56); + this.tableLayoutPanel2.TabIndex = 7; + // + // EnableProtocolHandlerCheckBox + // + this.EnableProtocolHandlerCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.EnableProtocolHandlerCheckBox.AutoSize = true; + this.EnableProtocolHandlerCheckBox.Location = new System.Drawing.Point(3, 32); + this.EnableProtocolHandlerCheckBox.Name = "EnableProtocolHandlerCheckBox"; + this.EnableProtocolHandlerCheckBox.Size = new System.Drawing.Size(197, 19); + this.EnableProtocolHandlerCheckBox.TabIndex = 0; + this.EnableProtocolHandlerCheckBox.Text = "Enable \"ugs://\" protocol handler"; + this.EnableProtocolHandlerCheckBox.UseVisualStyleBackColor = true; // // flowLayoutPanel1 // + this.flowLayoutPanel1.Anchor = System.Windows.Forms.AnchorStyles.Left; this.flowLayoutPanel1.AutoSize = true; this.flowLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; this.flowLayoutPanel1.Controls.Add(this.EnableAutomationCheckBox); this.flowLayoutPanel1.Controls.Add(this.AutomationPortTextBox); - this.flowLayoutPanel1.Location = new System.Drawing.Point(22, 26); + this.flowLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0); this.flowLayoutPanel1.Name = "flowLayoutPanel1"; - this.flowLayoutPanel1.Size = new System.Drawing.Size(281, 29); + this.flowLayoutPanel1.Size = new System.Drawing.Size(281, 28); this.flowLayoutPanel1.TabIndex = 2; // // EnableAutomationCheckBox @@ -372,7 +377,8 @@ namespace UnrealGameSync // // AdvancedBtn // - this.AdvancedBtn.Location = new System.Drawing.Point(17, 430); + this.AdvancedBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.AdvancedBtn.Location = new System.Drawing.Point(17, 460); this.AdvancedBtn.Name = "AdvancedBtn"; this.AdvancedBtn.Size = new System.Drawing.Size(105, 27); this.AdvancedBtn.TabIndex = 6; @@ -380,13 +386,41 @@ namespace UnrealGameSync this.AdvancedBtn.UseVisualStyleBackColor = true; this.AdvancedBtn.Click += new System.EventHandler(this.AdvancedBtn_Click); // + // DepotPathTextBox + // + this.DepotPathTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.DepotPathTextBox.CueBanner = null; + this.DepotPathTextBox.Location = new System.Drawing.Point(3, 3); + this.DepotPathTextBox.Name = "DepotPathTextBox"; + this.DepotPathTextBox.Size = new System.Drawing.Size(558, 23); + this.DepotPathTextBox.TabIndex = 0; + // + // UserNameTextBox + // + this.UserNameTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.UserNameTextBox.CueBanner = "Default"; + this.UserNameTextBox.Location = new System.Drawing.Point(133, 39); + this.UserNameTextBox.Name = "UserNameTextBox"; + this.UserNameTextBox.Size = new System.Drawing.Size(651, 23); + this.UserNameTextBox.TabIndex = 1; + // + // ServerTextBox + // + this.ServerTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.ServerTextBox.CueBanner = "Default"; + this.ServerTextBox.Location = new System.Drawing.Point(133, 5); + this.ServerTextBox.Name = "ServerTextBox"; + this.ServerTextBox.Size = new System.Drawing.Size(651, 23); + this.ServerTextBox.TabIndex = 0; + // // ApplicationSettingsWindow // this.AcceptButton = this.OkBtn; this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.CancelButton = this.CancelBtn; - this.ClientSize = new System.Drawing.Size(857, 469); + this.ClientSize = new System.Drawing.Size(857, 499); this.Controls.Add(this.AdvancedBtn); this.Controls.Add(this.groupBox4); this.Controls.Add(this.groupBox3); @@ -414,7 +448,8 @@ namespace UnrealGameSync this.tableLayoutPanel5.ResumeLayout(false); this.tableLayoutPanel5.PerformLayout(); this.groupBox4.ResumeLayout(false); - this.groupBox4.PerformLayout(); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); this.flowLayoutPanel1.ResumeLayout(false); this.flowLayoutPanel1.PerformLayout(); this.ResumeLayout(false); @@ -448,5 +483,7 @@ namespace UnrealGameSync private System.Windows.Forms.Label label2; private System.Windows.Forms.NumericUpDown ParallelSyncThreadsSpinner; private System.Windows.Forms.Button AdvancedBtn; + private System.Windows.Forms.CheckBox EnableProtocolHandlerCheckBox; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; } } \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.cs index 0d24e2126e78..657aeaf8edad 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.cs @@ -53,6 +53,7 @@ namespace UnrealGameSync string InitialDepotPath; bool bInitialUnstable; int InitialAutomationPortNumber; + ProtocolHandlerState InitialProtocolHandlerState; bool? bRestartUnstable; @@ -68,6 +69,7 @@ namespace UnrealGameSync bInitialUnstable = bUnstable; InitialAutomationPortNumber = AutomationServer.GetPortNumber(); + InitialProtocolHandlerState = ProtocolHandlerUtils.GetState(); this.AutomaticallyRunAtStartupCheckBox.Checked = IsAutomaticallyRunAtStartup(); this.KeepInTrayCheckBox.Checked = Settings.bKeepInTray; @@ -100,6 +102,19 @@ namespace UnrealGameSync this.AutomationPortTextBox.Enabled = false; this.AutomationPortTextBox.Text = AutomationServer.DefaultPortNumber.ToString(); } + + if(InitialProtocolHandlerState == ProtocolHandlerState.Installed) + { + this.EnableProtocolHandlerCheckBox.CheckState = CheckState.Checked; + } + else if (InitialProtocolHandlerState == ProtocolHandlerState.NotInstalled) + { + this.EnableProtocolHandlerCheckBox.CheckState = CheckState.Unchecked; + } + else + { + this.EnableProtocolHandlerCheckBox.CheckState = CheckState.Indeterminate; + } } public static bool? ShowModal(IWin32Window Owner, PerforceConnection DefaultConnection, bool bUnstable, string OriginalExecutableFileName, UserSettings Settings, TextWriter Log) @@ -195,6 +210,21 @@ namespace UnrealGameSync Settings.Save(); } + if (EnableProtocolHandlerCheckBox.CheckState == CheckState.Checked) + { + if (InitialProtocolHandlerState != ProtocolHandlerState.Installed) + { + ProtocolHandlerUtils.Install(); + } + } + else if (EnableProtocolHandlerCheckBox.CheckState == CheckState.Unchecked) + { + if (InitialProtocolHandlerState != ProtocolHandlerState.NotInstalled) + { + ProtocolHandlerUtils.Uninstall(); + } + } + DialogResult = DialogResult.OK; Close(); } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedBuildWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedBuildWindow.Designer.cs new file mode 100644 index 000000000000..1113e92ed116 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedBuildWindow.Designer.cs @@ -0,0 +1,357 @@ +namespace UnrealGameSync.Forms +{ + partial class AutomatedBuildWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.ChangeLink = new System.Windows.Forms.LinkLabel(); + this.ServerLabel = new System.Windows.Forms.Label(); + this.CancelBtn = new System.Windows.Forms.Button(); + this.OkBtn = new System.Windows.Forms.Button(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.WorkspaceNameLabel = new System.Windows.Forms.Label(); + this.WorkspaceNameTextBox = new System.Windows.Forms.TextBox(); + this.WorkspaceNameNewBtn = new System.Windows.Forms.Button(); + this.WorkspaceNameBrowseBtn = new System.Windows.Forms.Button(); + this.WorkspacePathLabel = new System.Windows.Forms.Label(); + this.WorkspacePathTextBox = new System.Windows.Forms.TextBox(); + this.WorkspacePathBrowseBtn = new System.Windows.Forms.Button(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.ExecCommandCheckBox = new System.Windows.Forms.CheckBox(); + this.ExecCommandTextBox = new System.Windows.Forms.TextBox(); + this.SyncToChangeCheckBox = new System.Windows.Forms.CheckBox(); + this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.groupBox2.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + this.groupBox3.SuspendLayout(); + this.tableLayoutPanel3.SuspendLayout(); + this.tableLayoutPanel1.SuspendLayout(); + this.flowLayoutPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // ChangeLink + // + this.ChangeLink.AutoSize = true; + this.ChangeLink.Location = new System.Drawing.Point(340, 0); + this.ChangeLink.Name = "ChangeLink"; + this.ChangeLink.Size = new System.Drawing.Size(57, 15); + this.ChangeLink.TabIndex = 14; + this.ChangeLink.TabStop = true; + this.ChangeLink.Text = "Change..."; + this.ChangeLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.ChangeLink_LinkClicked); + // + // ServerLabel + // + this.ServerLabel.AutoSize = true; + this.ServerLabel.Location = new System.Drawing.Point(3, 0); + this.ServerLabel.Name = "ServerLabel"; + this.ServerLabel.Size = new System.Drawing.Size(331, 15); + this.ServerLabel.TabIndex = 13; + this.ServerLabel.Text = "Using default Perforce connection (perforce:1666, Ben.Marsh)"; + // + // CancelBtn + // + this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.CancelBtn.Location = new System.Drawing.Point(667, 264); + this.CancelBtn.Name = "CancelBtn"; + this.CancelBtn.Size = new System.Drawing.Size(84, 26); + this.CancelBtn.TabIndex = 12; + this.CancelBtn.Text = "Cancel"; + this.CancelBtn.UseVisualStyleBackColor = true; + // + // OkBtn + // + this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.OkBtn.Location = new System.Drawing.Point(577, 264); + this.OkBtn.Name = "OkBtn"; + this.OkBtn.Size = new System.Drawing.Size(84, 26); + this.OkBtn.TabIndex = 11; + this.OkBtn.Text = "Ok"; + this.OkBtn.UseVisualStyleBackColor = true; + this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click); + // + // groupBox2 + // + this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox2.Controls.Add(this.tableLayoutPanel2); + this.groupBox2.Location = new System.Drawing.Point(20, 48); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(719, 103); + this.groupBox2.TabIndex = 10; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Workspace"; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel2.ColumnCount = 4; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.Controls.Add(this.WorkspaceNameLabel, 0, 0); + this.tableLayoutPanel2.Controls.Add(this.WorkspaceNameTextBox, 1, 0); + this.tableLayoutPanel2.Controls.Add(this.WorkspaceNameNewBtn, 2, 0); + this.tableLayoutPanel2.Controls.Add(this.WorkspaceNameBrowseBtn, 3, 0); + this.tableLayoutPanel2.Controls.Add(this.WorkspacePathLabel, 0, 1); + this.tableLayoutPanel2.Controls.Add(this.WorkspacePathTextBox, 1, 1); + this.tableLayoutPanel2.Controls.Add(this.WorkspacePathBrowseBtn, 3, 1); + this.tableLayoutPanel2.Location = new System.Drawing.Point(17, 23); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 2; + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel2.Size = new System.Drawing.Size(682, 65); + this.tableLayoutPanel2.TabIndex = 10; + // + // WorkspaceNameLabel + // + this.WorkspaceNameLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.WorkspaceNameLabel.AutoSize = true; + this.WorkspaceNameLabel.Location = new System.Drawing.Point(3, 8); + this.WorkspaceNameLabel.Name = "WorkspaceNameLabel"; + this.WorkspaceNameLabel.Size = new System.Drawing.Size(74, 15); + this.WorkspaceNameLabel.TabIndex = 1; + this.WorkspaceNameLabel.Text = "Name:"; + // + // WorkspaceNameTextBox + // + this.WorkspaceNameTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.WorkspaceNameTextBox.Location = new System.Drawing.Point(83, 4); + this.WorkspaceNameTextBox.Name = "WorkspaceNameTextBox"; + this.WorkspaceNameTextBox.Size = new System.Drawing.Size(401, 23); + this.WorkspaceNameTextBox.TabIndex = 2; + this.WorkspaceNameTextBox.TextChanged += new System.EventHandler(this.WorkspaceNameTextBox_TextChanged); + // + // WorkspaceNameNewBtn + // + this.WorkspaceNameNewBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.WorkspaceNameNewBtn.Location = new System.Drawing.Point(490, 3); + this.WorkspaceNameNewBtn.Name = "WorkspaceNameNewBtn"; + this.WorkspaceNameNewBtn.Size = new System.Drawing.Size(89, 26); + this.WorkspaceNameNewBtn.TabIndex = 3; + this.WorkspaceNameNewBtn.Text = "New..."; + this.WorkspaceNameNewBtn.UseVisualStyleBackColor = true; + this.WorkspaceNameNewBtn.Click += new System.EventHandler(this.WorkspaceNameNewBtn_Click); + // + // WorkspaceNameBrowseBtn + // + this.WorkspaceNameBrowseBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.WorkspaceNameBrowseBtn.Location = new System.Drawing.Point(585, 3); + this.WorkspaceNameBrowseBtn.Name = "WorkspaceNameBrowseBtn"; + this.WorkspaceNameBrowseBtn.Size = new System.Drawing.Size(94, 26); + this.WorkspaceNameBrowseBtn.TabIndex = 4; + this.WorkspaceNameBrowseBtn.Text = "Browse..."; + this.WorkspaceNameBrowseBtn.UseVisualStyleBackColor = true; + this.WorkspaceNameBrowseBtn.Click += new System.EventHandler(this.WorkspaceNameBrowseBtn_Click); + // + // WorkspacePathLabel + // + this.WorkspacePathLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.WorkspacePathLabel.AutoSize = true; + this.WorkspacePathLabel.Location = new System.Drawing.Point(3, 41); + this.WorkspacePathLabel.Name = "WorkspacePathLabel"; + this.WorkspacePathLabel.Size = new System.Drawing.Size(74, 15); + this.WorkspacePathLabel.TabIndex = 5; + this.WorkspacePathLabel.Text = "Path:"; + // + // WorkspacePathTextBox + // + this.WorkspacePathTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel2.SetColumnSpan(this.WorkspacePathTextBox, 2); + this.WorkspacePathTextBox.Location = new System.Drawing.Point(83, 37); + this.WorkspacePathTextBox.Name = "WorkspacePathTextBox"; + this.WorkspacePathTextBox.Size = new System.Drawing.Size(496, 23); + this.WorkspacePathTextBox.TabIndex = 6; + // + // WorkspacePathBrowseBtn + // + this.WorkspacePathBrowseBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.WorkspacePathBrowseBtn.Location = new System.Drawing.Point(585, 35); + this.WorkspacePathBrowseBtn.Name = "WorkspacePathBrowseBtn"; + this.WorkspacePathBrowseBtn.Size = new System.Drawing.Size(94, 27); + this.WorkspacePathBrowseBtn.TabIndex = 7; + this.WorkspacePathBrowseBtn.Text = "Browse..."; + this.WorkspacePathBrowseBtn.UseVisualStyleBackColor = true; + this.WorkspacePathBrowseBtn.Click += new System.EventHandler(this.WorkspacePathBrowseBtn_Click); + // + // groupBox3 + // + this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox3.Controls.Add(this.tableLayoutPanel3); + this.groupBox3.Location = new System.Drawing.Point(20, 157); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.Size = new System.Drawing.Size(719, 92); + this.groupBox3.TabIndex = 15; + this.groupBox3.TabStop = false; + this.groupBox3.Text = "Actions"; + // + // tableLayoutPanel3 + // + this.tableLayoutPanel3.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel3.ColumnCount = 1; + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.Controls.Add(this.tableLayoutPanel1, 0, 1); + this.tableLayoutPanel3.Controls.Add(this.SyncToChangeCheckBox, 0, 0); + this.tableLayoutPanel3.Location = new System.Drawing.Point(17, 21); + this.tableLayoutPanel3.Name = "tableLayoutPanel3"; + this.tableLayoutPanel3.RowCount = 2; + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel3.Size = new System.Drawing.Size(682, 59); + this.tableLayoutPanel3.TabIndex = 10; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.ExecCommandCheckBox, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.ExecCommandTextBox, 1, 0); + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 29); + this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 1; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(682, 30); + this.tableLayoutPanel1.TabIndex = 10; + // + // ExecCommandCheckBox + // + this.ExecCommandCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.ExecCommandCheckBox.AutoSize = true; + this.ExecCommandCheckBox.Checked = true; + this.ExecCommandCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; + this.ExecCommandCheckBox.Location = new System.Drawing.Point(3, 5); + this.ExecCommandCheckBox.Name = "ExecCommandCheckBox"; + this.ExecCommandCheckBox.Size = new System.Drawing.Size(108, 19); + this.ExecCommandCheckBox.TabIndex = 0; + this.ExecCommandCheckBox.Text = "Run command:"; + this.ExecCommandCheckBox.UseVisualStyleBackColor = true; + this.ExecCommandCheckBox.CheckedChanged += new System.EventHandler(this.ExecCommandCheckBox_CheckedChanged); + // + // ExecCommandTextBox + // + this.ExecCommandTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.ExecCommandTextBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.ExecCommandTextBox.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ExecCommandTextBox.Location = new System.Drawing.Point(117, 4); + this.ExecCommandTextBox.Name = "ExecCommandTextBox"; + this.ExecCommandTextBox.Size = new System.Drawing.Size(562, 22); + this.ExecCommandTextBox.TabIndex = 1; + this.ExecCommandTextBox.Text = "RunUAT.bat -Script=Foo -Target=\"Build Something\""; + // + // SyncToChangeCheckBox + // + this.SyncToChangeCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.SyncToChangeCheckBox.AutoSize = true; + this.SyncToChangeCheckBox.Checked = true; + this.SyncToChangeCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; + this.SyncToChangeCheckBox.Location = new System.Drawing.Point(3, 5); + this.SyncToChangeCheckBox.Name = "SyncToChangeCheckBox"; + this.SyncToChangeCheckBox.Size = new System.Drawing.Size(149, 19); + this.SyncToChangeCheckBox.TabIndex = 0; + this.SyncToChangeCheckBox.Text = "Sync to changelist 1234"; + this.SyncToChangeCheckBox.UseVisualStyleBackColor = true; + // + // flowLayoutPanel1 + // + this.flowLayoutPanel1.Controls.Add(this.ServerLabel); + this.flowLayoutPanel1.Controls.Add(this.ChangeLink); + this.flowLayoutPanel1.Location = new System.Drawing.Point(20, 15); + this.flowLayoutPanel1.Name = "flowLayoutPanel1"; + this.flowLayoutPanel1.Size = new System.Drawing.Size(719, 27); + this.flowLayoutPanel1.TabIndex = 16; + this.flowLayoutPanel1.WrapContents = false; + // + // AutomatedBuildWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(763, 302); + this.Controls.Add(this.flowLayoutPanel1); + this.Controls.Add(this.groupBox3); + this.Controls.Add(this.CancelBtn); + this.Controls.Add(this.OkBtn); + this.Controls.Add(this.groupBox2); + this.Font = new System.Drawing.Font("Segoe UI", 9F); + this.Icon = global::UnrealGameSync.Properties.Resources.Icon; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "AutomatedBuildWindow"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Build Locally"; + this.groupBox2.ResumeLayout(false); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + this.groupBox3.ResumeLayout(false); + this.tableLayoutPanel3.ResumeLayout(false); + this.tableLayoutPanel3.PerformLayout(); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.flowLayoutPanel1.ResumeLayout(false); + this.flowLayoutPanel1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.LinkLabel ChangeLink; + private System.Windows.Forms.Label ServerLabel; + private System.Windows.Forms.Button CancelBtn; + private System.Windows.Forms.Button OkBtn; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.CheckBox ExecCommandCheckBox; + private System.Windows.Forms.TextBox ExecCommandTextBox; + private System.Windows.Forms.CheckBox SyncToChangeCheckBox; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; + private System.Windows.Forms.Label WorkspaceNameLabel; + private System.Windows.Forms.TextBox WorkspaceNameTextBox; + private System.Windows.Forms.Button WorkspaceNameNewBtn; + private System.Windows.Forms.Button WorkspaceNameBrowseBtn; + private System.Windows.Forms.Label WorkspacePathLabel; + private System.Windows.Forms.TextBox WorkspacePathTextBox; + private System.Windows.Forms.Button WorkspacePathBrowseBtn; + } +} \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedBuildWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedBuildWindow.cs new file mode 100644 index 000000000000..75ffc2508eb5 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedBuildWindow.cs @@ -0,0 +1,206 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using UnrealGameSync.Properties; + +namespace UnrealGameSync.Forms +{ + partial class AutomatedBuildWindow : Form + { + public class BuildInfo + { + public AutomatedSyncWindow.WorkspaceInfo SelectedWorkspaceInfo; + public string ProjectPath; + public bool bSync; + public string ExecCommand; + } + + string StreamName; + TextWriter Log; + + string ServerAndPortOverride; + string UserNameOverride; + PerforceConnection DefaultConnection; + + BuildInfo Result; + + private AutomatedBuildWindow(string StreamName, int Changelist, string Command, PerforceConnection DefaultConnection, string DefaultWorkspaceName, string DefaultProjectPath, TextWriter Log) + { + this.StreamName = StreamName; + this.DefaultConnection = DefaultConnection; + this.Log = Log; + + InitializeComponent(); + + ActiveControl = WorkspaceNameTextBox; + + MinimumSize = Size; + MaximumSize = new Size(32768, Size.Height); + + SyncToChangeCheckBox.Text = String.Format("Sync to changelist {0}", Changelist); + ExecCommandTextBox.Text = Command; + + if (DefaultWorkspaceName != null) + { + WorkspaceNameTextBox.Text = DefaultWorkspaceName; + WorkspaceNameTextBox.Select(WorkspaceNameTextBox.Text.Length, 0); + } + + if (DefaultProjectPath != null) + { + WorkspacePathTextBox.Text = DefaultProjectPath; + WorkspacePathTextBox.Select(WorkspacePathTextBox.Text.Length, 0); + } + + UpdateServerLabel(); + UpdateOkButton(); + UpdateWorkspacePathBrowseButton(); + } + + private PerforceConnection Perforce + { + get { return Utility.OverridePerforceSettings(DefaultConnection, ServerAndPortOverride, UserNameOverride); } + } + + public static bool ShowModal(IWin32Window Owner, PerforceConnection DefaultConnection, string StreamName, string ProjectPath, int Changelist, string Command, UserSettings Settings, TextWriter Log, out BuildInfo BuildInfo) + { + string DefaultWorkspaceName = AutomatedSyncWindow.FindDefaultWorkspace(Owner, DefaultConnection, StreamName, Log); + + string DefaultProjectPath = null; + if(!String.IsNullOrEmpty(ProjectPath)) + { + DefaultProjectPath = ProjectPath; + } + else if(DefaultWorkspaceName != null) + { + string ClientPrefix = String.Format("//{0}/", DefaultWorkspaceName); + foreach (UserSelectedProjectSettings ProjectSettings in Settings.RecentProjects) + { + if (ProjectSettings.ClientPath.StartsWith(ClientPrefix, StringComparison.OrdinalIgnoreCase)) + { + DefaultProjectPath = ProjectSettings.ClientPath.Substring(ClientPrefix.Length - 1); + break; + } + } + } + + AutomatedBuildWindow Window = new AutomatedBuildWindow(StreamName, Changelist, Command, DefaultConnection, DefaultWorkspaceName, DefaultProjectPath, Log); + if (Window.ShowDialog() == DialogResult.OK) + { + BuildInfo = Window.Result; + return true; + } + else + { + BuildInfo = null; + return false; + } + } + + private void ChangeLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + if (ConnectWindow.ShowModal(this, DefaultConnection, ref ServerAndPortOverride, ref UserNameOverride, Log)) + { + UpdateServerLabel(); + } + } + + private void UpdateServerLabel() + { + ServerLabel.Text = OpenProjectWindow.GetServerLabelText(DefaultConnection, ServerAndPortOverride, UserNameOverride); + } + + private void WorkspaceNameNewBtn_Click(object sender, EventArgs e) + { + string WorkspaceName; + if (NewWorkspaceWindow.ShowModal(this, Perforce, StreamName, WorkspaceNameTextBox.Text, Log, out WorkspaceName)) + { + WorkspaceNameTextBox.Text = WorkspaceName; + UpdateOkButton(); + } + } + + private void WorkspaceNameBrowseBtn_Click(object sender, EventArgs e) + { + string WorkspaceName = WorkspaceNameTextBox.Text; + if (SelectWorkspaceWindow.ShowModal(this, Perforce, WorkspaceName, Log, out WorkspaceName)) + { + WorkspaceNameTextBox.Text = WorkspaceName; + UpdateOkButton(); + } + } + + private void UpdateOkButton() + { + OkBtn.Enabled = (WorkspaceNameTextBox.Text.Length > 0); + } + + private void OkBtn_Click(object sender, EventArgs e) + { + AutomatedSyncWindow.WorkspaceInfo SelectedWorkspaceInfo; + if (AutomatedSyncWindow.ValidateWorkspace(this, Perforce, WorkspaceNameTextBox.Text, StreamName, Log, out SelectedWorkspaceInfo)) + { + Result = new BuildInfo(); + Result.SelectedWorkspaceInfo = SelectedWorkspaceInfo; + Result.ProjectPath = WorkspacePathTextBox.Text; + Result.bSync = SyncToChangeCheckBox.Checked; + Result.ExecCommand = ExecCommandTextBox.Text; + + DialogResult = DialogResult.OK; + Close(); + } + } + + private void ExecCommandCheckBox_CheckedChanged(object sender, EventArgs e) + { + ExecCommandTextBox.Enabled = ExecCommandCheckBox.Checked; + } + + private void UpdateWorkspacePathBrowseButton() + { + string WorkspaceName; + WorkspacePathBrowseBtn.Enabled = TryGetWorkspaceName(out WorkspaceName); + } + + private void WorkspacePathBrowseBtn_Click(object sender, EventArgs e) + { + string WorkspaceName; + if (TryGetWorkspaceName(out WorkspaceName)) + { + string WorkspacePath = WorkspacePathTextBox.Text.Trim(); + if (SelectProjectFromWorkspaceWindow.ShowModal(this, Perforce, WorkspaceName, WorkspacePath, Log, out WorkspacePath)) + { + WorkspacePathTextBox.Text = WorkspacePath; + UpdateOkButton(); + } + } + } + + private bool TryGetWorkspaceName(out string WorkspaceName) + { + string Text = WorkspaceNameTextBox.Text.Trim(); + if (Text.Length == 0) + { + WorkspaceName = null; + return false; + } + + WorkspaceName = Text; + return true; + } + + private void WorkspaceNameTextBox_TextChanged(object sender, EventArgs e) + { + UpdateWorkspacePathBrowseButton(); + } + } +} diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedBuildWindow.resx b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedBuildWindow.resx new file mode 100644 index 000000000000..1af7de150c99 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedBuildWindow.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.cs index b3a52e5e2f41..afa20e65a04f 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.cs @@ -53,9 +53,9 @@ namespace UnrealGameSync } } - if(CandidateClients.Count == 1) + if(CandidateClients.Count >= 1) { - WorkspaceName = CandidateClients[0].Name; + WorkspaceName = CandidateClients.OrderByDescending(x => x.Access).First().Name; } ErrorMessage = null; @@ -148,14 +148,21 @@ namespace UnrealGameSync get { return Utility.OverridePerforceSettings(DefaultConnection, ServerAndPortOverride, UserNameOverride); } } - public static bool ShowModal(IWin32Window Owner, PerforceConnection DefaultConnection, string StreamName, string ProjectPath, out WorkspaceInfo WorkspaceInfo, TextWriter Log) + public static string FindDefaultWorkspace(IWin32Window Owner, PerforceConnection DefaultConnection, string StreamName, TextWriter Log) { FindDefaultWorkspaceTask FindWorkspace = new FindDefaultWorkspaceTask(StreamName); string ErrorMessage; PerforceModalTask.Execute(Owner, DefaultConnection, FindWorkspace, "Finding workspace", "Finding default workspace, please wait...", Log, out ErrorMessage); - AutomatedSyncWindow Window = new AutomatedSyncWindow(StreamName, ProjectPath, FindWorkspace.WorkspaceName, DefaultConnection, Log); + return FindWorkspace.WorkspaceName; + } + + public static bool ShowModal(IWin32Window Owner, PerforceConnection DefaultConnection, string StreamName, string ProjectPath, out WorkspaceInfo WorkspaceInfo, TextWriter Log) + { + string WorkspaceName = FindDefaultWorkspace(Owner, DefaultConnection, StreamName, Log); + + AutomatedSyncWindow Window = new AutomatedSyncWindow(StreamName, ProjectPath, WorkspaceName, DefaultConnection, Log); if(Window.ShowDialog() == DialogResult.OK) { WorkspaceInfo = Window.SelectedWorkspaceInfo; @@ -207,22 +214,22 @@ namespace UnrealGameSync OkBtn.Enabled = (WorkspaceNameTextBox.Text.Length > 0); } - private void OkBtn_Click(object sender, EventArgs e) + public static bool ValidateWorkspace(IWin32Window Owner, PerforceConnection Perforce, string WorkspaceName, string StreamName, TextWriter Log, out WorkspaceInfo SelectedWorkspaceInfo) { - ValidateWorkspaceTask ValidateWorkspace = new ValidateWorkspaceTask(WorkspaceNameTextBox.Text, StreamName); + ValidateWorkspaceTask ValidateWorkspace = new ValidateWorkspaceTask(WorkspaceName, StreamName); string ErrorMessage; ModalTaskResult Result = PerforceModalTask.Execute(Owner, Perforce, ValidateWorkspace, "Checking workspace", "Checking workspace, please wait...", Log, out ErrorMessage); - if(Result == ModalTaskResult.Failed) + if (Result == ModalTaskResult.Failed) { MessageBox.Show(ErrorMessage); } - else if(Result == ModalTaskResult.Succeeded) + else if (Result == ModalTaskResult.Succeeded) { - if(ValidateWorkspace.bRequiresStreamSwitch) + if (ValidateWorkspace.bRequiresStreamSwitch) { string Message; - if(ValidateWorkspace.bHasOpenFiles) + if (ValidateWorkspace.bHasOpenFiles) { Message = String.Format("You have files open for edit in this workspace. If you switch this workspace to {0}, you will not be able to submit them until you switch back.\n\nContinue switching streams?", StreamName); } @@ -230,14 +237,25 @@ namespace UnrealGameSync { Message = String.Format("Switch this workspace to {0}?", StreamName); } - if(MessageBox.Show(Message, "Switch Streams", MessageBoxButtons.YesNo) != DialogResult.Yes) + if (MessageBox.Show(Message, "Switch Streams", MessageBoxButtons.YesNo) != DialogResult.Yes) { - return; + SelectedWorkspaceInfo = null; + return false; } } - SelectedWorkspaceInfo = new WorkspaceInfo(){ ServerAndPort = ValidateWorkspace.ServerAndPort, UserName = ValidateWorkspace.UserName, WorkspaceName = ValidateWorkspace.WorkspaceName, bRequiresStreamSwitch = ValidateWorkspace.bRequiresStreamSwitch }; + SelectedWorkspaceInfo = new WorkspaceInfo() { ServerAndPort = ValidateWorkspace.ServerAndPort, UserName = ValidateWorkspace.UserName, WorkspaceName = ValidateWorkspace.WorkspaceName, bRequiresStreamSwitch = ValidateWorkspace.bRequiresStreamSwitch }; + return true; + } + SelectedWorkspaceInfo = null; + return false; + } + + private void OkBtn_Click(object sender, EventArgs e) + { + if(ValidateWorkspace(this, Perforce, WorkspaceNameTextBox.Text, StreamName, Log, out SelectedWorkspaceInfo)) + { DialogResult = DialogResult.OK; Close(); } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueBrowserWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueBrowserWindow.cs index c3500ff142de..aa1ff58c160c 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueBrowserWindow.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueBrowserWindow.cs @@ -103,7 +103,7 @@ namespace UnrealGameSync // Update the list of project names Issues = NewIssues; - ProjectNames = NewIssues.Select(x => x.Project).Distinct().OrderBy(x => x).ToList(); + ProjectNames = NewIssues.SelectMany(x => x.Projects).Distinct().OrderBy(x => x).ToList(); UpdateIssueList(); BackgroundThread = null; @@ -146,7 +146,7 @@ namespace UnrealGameSync IssueListView.BeginUpdate(); foreach(IssueData Issue in Issues) { - if(FilterProjectName == null || Issue.Project == FilterProjectName) + if(FilterProjectName == null || Issue.Projects.Contains(FilterProjectName)) { for(;;) { @@ -202,7 +202,7 @@ namespace UnrealGameSync void IssueList_UpdateItem(ListViewItem Item, IssueData Issue, DateTime Midnight) { Item.SubItems[IdHeader.Index].Text = Issue.Id.ToString(); - Item.SubItems[ProjectHeader.Index].Text = Issue.Project; + Item.SubItems[ProjectHeader.Index].Text = String.Join(", ", Issue.Projects); Item.SubItems[CreatedHeader.Index].Text = FormatIssueDateTime(Issue.CreatedAt.ToLocalTime(), Midnight); Item.SubItems[ResolvedHeader.Index].Text = Issue.ResolvedAt.HasValue ? FormatIssueDateTime(Issue.ResolvedAt.Value.ToLocalTime(), Midnight) : "Unresolved"; Item.SubItems[OwnerHeader.Index].Text = (Issue.Owner == null) ? "-" : Utility.FormatUserName(Issue.Owner); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueDetailsWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueDetailsWindow.cs index 7336cda9eeb2..d75b51742159 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueDetailsWindow.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueDetailsWindow.cs @@ -321,7 +321,11 @@ namespace UnrealGameSync components.Dispose(); } - IssueMonitor.Release(); + if (IssueMonitor != null) + { + IssueMonitor.Release(); + IssueMonitor = null; + } base.Dispose(disposing); } @@ -494,7 +498,8 @@ namespace UnrealGameSync { UpdateSummaryTextIfChanged(SummaryTextBox, Issue.Summary.ToString()); - BuildLinkLabel.Text = (Issue.Builds.Count > 0) ? Issue.Builds[0].JobName : "Unknown"; + IssueBuildData FirstFailingBuild = Issue.Builds.FirstOrDefault(x => x.ErrorUrl != null); + BuildLinkLabel.Text = (FirstFailingBuild != null)? FirstFailingBuild.JobName : "Unknown"; StringBuilder Status = new StringBuilder(); if(IssueMonitor.HasPendingUpdate()) @@ -1045,8 +1050,15 @@ namespace UnrealGameSync Window = new IssueDetailsWindow(IssueMonitor, Issue, Diagnostics, ServerAndPort, UserName, ServerTimeOffset, Log, CurrentStream); Window.Owner = Owner; - Window.StartPosition = FormStartPosition.Manual; - Window.Location = new Point(Owner.Location.X + (Owner.Width - Window.Width) / 2, Owner.Location.Y + (Owner.Height - Window.Height) / 2); + if (Owner.Visible && Owner.WindowState != FormWindowState.Minimized) + { + Window.StartPosition = FormStartPosition.Manual; + Window.Location = new Point(Owner.Location.X + (Owner.Width - Window.Width) / 2, Owner.Location.Y + (Owner.Height - Window.Height) / 2); + } + else + { + Window.StartPosition = FormStartPosition.CenterScreen; + } Window.Show(); ExistingWindows.Add(Window); @@ -1054,6 +1066,7 @@ namespace UnrealGameSync } else { + Window.Location = new Point(Owner.Location.X + (Owner.Width - Window.Width) / 2, Owner.Location.Y + (Owner.Height - Window.Height) / 2); Window.Activate(); } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueSettingsWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueSettingsWindow.Designer.cs index d16ece292479..0ffd274d891c 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueSettingsWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueSettingsWindow.Designer.cs @@ -30,246 +30,291 @@ namespace UnrealGameSync /// private void InitializeComponent() { - this.OkBtn = new System.Windows.Forms.Button(); - this.CancelBtn = new System.Windows.Forms.Button(); - this.groupBox2 = new System.Windows.Forms.GroupBox(); - this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); - this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); - this.NotifyUnassignedCheckBox = new System.Windows.Forms.CheckBox(); - this.NotifyUnassignedTextBox = new System.Windows.Forms.TextBox(); - this.label2 = new System.Windows.Forms.Label(); - this.flowLayoutPanel2 = new System.Windows.Forms.FlowLayoutPanel(); - this.NotifyUnresolvedCheckBox = new System.Windows.Forms.CheckBox(); - this.NotifyUnresolvedTextBox = new System.Windows.Forms.TextBox(); - this.label3 = new System.Windows.Forms.Label(); - this.flowLayoutPanel3 = new System.Windows.Forms.FlowLayoutPanel(); - this.NotifyUnacknowledgedCheckBox = new System.Windows.Forms.CheckBox(); - this.NotifyUnacknowledgedTextBox = new System.Windows.Forms.TextBox(); - this.label1 = new System.Windows.Forms.Label(); - this.groupBox2.SuspendLayout(); - this.tableLayoutPanel1.SuspendLayout(); - this.flowLayoutPanel1.SuspendLayout(); - this.flowLayoutPanel2.SuspendLayout(); - this.flowLayoutPanel3.SuspendLayout(); - this.SuspendLayout(); - // - // OkBtn - // - this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(356, 159); - this.OkBtn.Name = "OkBtn"; - this.OkBtn.Size = new System.Drawing.Size(87, 27); - this.OkBtn.TabIndex = 0; - this.OkBtn.Text = "Ok"; - this.OkBtn.UseVisualStyleBackColor = true; - this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click); - // - // CancelBtn - // - this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(263, 159); - this.CancelBtn.Name = "CancelBtn"; - this.CancelBtn.Size = new System.Drawing.Size(87, 27); - this.CancelBtn.TabIndex = 1; - this.CancelBtn.Text = "Cancel"; - this.CancelBtn.UseVisualStyleBackColor = true; - this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click); - // - // groupBox2 - // - this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + this.OkBtn = new System.Windows.Forms.Button(); + this.CancelBtn = new System.Windows.Forms.Button(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.NotifyProjectsCheckBox = new System.Windows.Forms.CheckBox(); + this.NotifyProjectsTextBox = new UnrealGameSync.TextBoxWithCueBanner(); + this.flowLayoutPanel3 = new System.Windows.Forms.FlowLayoutPanel(); + this.NotifyUnacknowledgedCheckBox = new System.Windows.Forms.CheckBox(); + this.NotifyUnacknowledgedTextBox = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.NotifyUnassignedCheckBox = new System.Windows.Forms.CheckBox(); + this.NotifyUnassignedTextBox = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.flowLayoutPanel2 = new System.Windows.Forms.FlowLayoutPanel(); + this.NotifyUnresolvedCheckBox = new System.Windows.Forms.CheckBox(); + this.NotifyUnresolvedTextBox = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.groupBox2.SuspendLayout(); + this.tableLayoutPanel1.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + this.flowLayoutPanel3.SuspendLayout(); + this.flowLayoutPanel1.SuspendLayout(); + this.flowLayoutPanel2.SuspendLayout(); + this.SuspendLayout(); + // + // OkBtn + // + this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.OkBtn.Location = new System.Drawing.Point(356, 213); + this.OkBtn.Name = "OkBtn"; + this.OkBtn.Size = new System.Drawing.Size(87, 27); + this.OkBtn.TabIndex = 0; + this.OkBtn.Text = "Ok"; + this.OkBtn.UseVisualStyleBackColor = true; + this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click); + // + // CancelBtn + // + this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.CancelBtn.Location = new System.Drawing.Point(263, 213); + this.CancelBtn.Name = "CancelBtn"; + this.CancelBtn.Size = new System.Drawing.Size(87, 27); + this.CancelBtn.TabIndex = 1; + this.CancelBtn.Text = "Cancel"; + this.CancelBtn.UseVisualStyleBackColor = true; + this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click); + // + // groupBox2 + // + this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.groupBox2.Controls.Add(this.tableLayoutPanel1); - this.groupBox2.Location = new System.Drawing.Point(12, 12); - this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(431, 141); - this.groupBox2.TabIndex = 6; - this.groupBox2.TabStop = false; - this.groupBox2.Text = "Optional Notifications"; - // - // tableLayoutPanel1 - // - this.tableLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + this.groupBox2.Controls.Add(this.tableLayoutPanel1); + this.groupBox2.Location = new System.Drawing.Point(12, 12); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(431, 195); + this.groupBox2.TabIndex = 6; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Optional Notifications"; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.tableLayoutPanel1.AutoSize = true; - this.tableLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.tableLayoutPanel1.ColumnCount = 1; - this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel3, 0, 1); - this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel1, 0, 0); - this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel2, 0, 2); - this.tableLayoutPanel1.Location = new System.Drawing.Point(17, 31); - this.tableLayoutPanel1.Name = "tableLayoutPanel1"; - this.tableLayoutPanel1.RowCount = 3; - this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); - this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); - this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); - this.tableLayoutPanel1.Size = new System.Drawing.Size(388, 93); - this.tableLayoutPanel1.TabIndex = 9; - // - // flowLayoutPanel1 - // - this.flowLayoutPanel1.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.flowLayoutPanel1.AutoSize = true; - this.flowLayoutPanel1.Controls.Add(this.NotifyUnassignedCheckBox); - this.flowLayoutPanel1.Controls.Add(this.NotifyUnassignedTextBox); - this.flowLayoutPanel1.Controls.Add(this.label2); - this.flowLayoutPanel1.Location = new System.Drawing.Point(0, 0); - this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0); - this.flowLayoutPanel1.Name = "flowLayoutPanel1"; - this.flowLayoutPanel1.Size = new System.Drawing.Size(344, 29); - this.flowLayoutPanel1.TabIndex = 7; - this.flowLayoutPanel1.WrapContents = false; - // - // NotifyUnassignedCheckBox - // - this.NotifyUnassignedCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.NotifyUnassignedCheckBox.AutoSize = true; - this.NotifyUnassignedCheckBox.Location = new System.Drawing.Point(3, 5); - this.NotifyUnassignedCheckBox.Name = "NotifyUnassignedCheckBox"; - this.NotifyUnassignedCheckBox.Size = new System.Drawing.Size(192, 19); - this.NotifyUnassignedCheckBox.TabIndex = 4; - this.NotifyUnassignedCheckBox.Text = "Alert on unassigned issues after"; - this.NotifyUnassignedCheckBox.UseVisualStyleBackColor = true; - this.NotifyUnassignedCheckBox.CheckedChanged += new System.EventHandler(this.NotifyUnassignedCheckBox_CheckedChanged); - // - // NotifyUnassignedTextBox - // - this.NotifyUnassignedTextBox.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.NotifyUnassignedTextBox.Location = new System.Drawing.Point(201, 3); - this.NotifyUnassignedTextBox.Name = "NotifyUnassignedTextBox"; - this.NotifyUnassignedTextBox.Size = new System.Drawing.Size(84, 23); - this.NotifyUnassignedTextBox.TabIndex = 5; - // - // label2 - // - this.label2.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(291, 7); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(50, 15); - this.label2.TabIndex = 6; - this.label2.Text = "minutes"; - // - // flowLayoutPanel2 - // - this.flowLayoutPanel2.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.flowLayoutPanel2.AutoSize = true; - this.flowLayoutPanel2.Controls.Add(this.NotifyUnresolvedCheckBox); - this.flowLayoutPanel2.Controls.Add(this.NotifyUnresolvedTextBox); - this.flowLayoutPanel2.Controls.Add(this.label3); - this.flowLayoutPanel2.Location = new System.Drawing.Point(0, 62); - this.flowLayoutPanel2.Margin = new System.Windows.Forms.Padding(0); - this.flowLayoutPanel2.Name = "flowLayoutPanel2"; - this.flowLayoutPanel2.Size = new System.Drawing.Size(342, 29); - this.flowLayoutPanel2.TabIndex = 8; - this.flowLayoutPanel2.WrapContents = false; - // - // NotifyUnresolvedCheckBox - // - this.NotifyUnresolvedCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.NotifyUnresolvedCheckBox.AutoSize = true; - this.NotifyUnresolvedCheckBox.Location = new System.Drawing.Point(3, 5); - this.NotifyUnresolvedCheckBox.Name = "NotifyUnresolvedCheckBox"; - this.NotifyUnresolvedCheckBox.Size = new System.Drawing.Size(190, 19); - this.NotifyUnresolvedCheckBox.TabIndex = 4; - this.NotifyUnresolvedCheckBox.Text = "Alert on unresolved issues after"; - this.NotifyUnresolvedCheckBox.UseVisualStyleBackColor = true; - this.NotifyUnresolvedCheckBox.CheckedChanged += new System.EventHandler(this.NotifyUnresolvedCheckBox_CheckedChanged); - // - // NotifyUnresolvedTextBox - // - this.NotifyUnresolvedTextBox.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.NotifyUnresolvedTextBox.Location = new System.Drawing.Point(199, 3); - this.NotifyUnresolvedTextBox.Name = "NotifyUnresolvedTextBox"; - this.NotifyUnresolvedTextBox.Size = new System.Drawing.Size(84, 23); - this.NotifyUnresolvedTextBox.TabIndex = 5; - // - // label3 - // - this.label3.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(289, 7); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(50, 15); - this.label3.TabIndex = 6; - this.label3.Text = "minutes"; - // - // flowLayoutPanel3 - // - this.flowLayoutPanel3.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.flowLayoutPanel3.AutoSize = true; - this.flowLayoutPanel3.Controls.Add(this.NotifyUnacknowledgedCheckBox); - this.flowLayoutPanel3.Controls.Add(this.NotifyUnacknowledgedTextBox); - this.flowLayoutPanel3.Controls.Add(this.label1); - this.flowLayoutPanel3.Location = new System.Drawing.Point(0, 30); - this.flowLayoutPanel3.Margin = new System.Windows.Forms.Padding(0); - this.flowLayoutPanel3.Name = "flowLayoutPanel3"; - this.flowLayoutPanel3.Size = new System.Drawing.Size(375, 29); - this.flowLayoutPanel3.TabIndex = 9; - this.flowLayoutPanel3.WrapContents = false; - // - // NotifyUnacknowledgedCheckBox - // - this.NotifyUnacknowledgedCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.NotifyUnacknowledgedCheckBox.AutoSize = true; - this.NotifyUnacknowledgedCheckBox.Location = new System.Drawing.Point(3, 5); - this.NotifyUnacknowledgedCheckBox.Name = "NotifyUnacknowledgedCheckBox"; - this.NotifyUnacknowledgedCheckBox.Size = new System.Drawing.Size(223, 19); - this.NotifyUnacknowledgedCheckBox.TabIndex = 4; - this.NotifyUnacknowledgedCheckBox.Text = "Alert on unacknowledged issues after"; - this.NotifyUnacknowledgedCheckBox.UseVisualStyleBackColor = true; + this.tableLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.tableLayoutPanel1.ColumnCount = 1; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel2, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel3, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel1, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel2, 0, 3); + this.tableLayoutPanel1.Location = new System.Drawing.Point(17, 31); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 4; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25.00062F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25.00062F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25.00062F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 24.99813F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(388, 143); + this.tableLayoutPanel1.TabIndex = 9; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel2.ColumnCount = 2; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.Controls.Add(this.NotifyProjectsCheckBox, 0, 0); + this.tableLayoutPanel2.Controls.Add(this.NotifyProjectsTextBox, 1, 0); + this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 2); + this.tableLayoutPanel2.Margin = new System.Windows.Forms.Padding(0); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 1; + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.Size = new System.Drawing.Size(388, 30); + this.tableLayoutPanel2.TabIndex = 7; + // + // NotifyProjectsCheckBox + // + this.NotifyProjectsCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.NotifyProjectsCheckBox.AutoSize = true; + this.NotifyProjectsCheckBox.Location = new System.Drawing.Point(3, 5); + this.NotifyProjectsCheckBox.Name = "NotifyProjectsCheckBox"; + this.NotifyProjectsCheckBox.Size = new System.Drawing.Size(97, 19); + this.NotifyProjectsCheckBox.TabIndex = 4; + this.NotifyProjectsCheckBox.Text = "Filter projects"; + this.NotifyProjectsCheckBox.UseVisualStyleBackColor = true; + this.NotifyProjectsCheckBox.CheckedChanged += new System.EventHandler(this.NotifyProjectsCheckBox_CheckedChanged); + // + // NotifyProjectsTextBox + // + this.NotifyProjectsTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.NotifyProjectsTextBox.CueBanner = "List of project names separated by spaces"; + this.NotifyProjectsTextBox.Location = new System.Drawing.Point(106, 3); + this.NotifyProjectsTextBox.Name = "NotifyProjectsTextBox"; + this.NotifyProjectsTextBox.Size = new System.Drawing.Size(279, 23); + this.NotifyProjectsTextBox.TabIndex = 5; + // + // flowLayoutPanel3 + // + this.flowLayoutPanel3.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.flowLayoutPanel3.AutoSize = true; + this.flowLayoutPanel3.Controls.Add(this.NotifyUnacknowledgedCheckBox); + this.flowLayoutPanel3.Controls.Add(this.NotifyUnacknowledgedTextBox); + this.flowLayoutPanel3.Controls.Add(this.label1); + this.flowLayoutPanel3.Location = new System.Drawing.Point(0, 38); + this.flowLayoutPanel3.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutPanel3.Name = "flowLayoutPanel3"; + this.flowLayoutPanel3.Size = new System.Drawing.Size(375, 29); + this.flowLayoutPanel3.TabIndex = 9; + this.flowLayoutPanel3.WrapContents = false; + // + // NotifyUnacknowledgedCheckBox + // + this.NotifyUnacknowledgedCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.NotifyUnacknowledgedCheckBox.AutoSize = true; + this.NotifyUnacknowledgedCheckBox.Location = new System.Drawing.Point(3, 5); + this.NotifyUnacknowledgedCheckBox.Name = "NotifyUnacknowledgedCheckBox"; + this.NotifyUnacknowledgedCheckBox.Size = new System.Drawing.Size(223, 19); + this.NotifyUnacknowledgedCheckBox.TabIndex = 4; + this.NotifyUnacknowledgedCheckBox.Text = "Alert on unacknowledged issues after"; + this.NotifyUnacknowledgedCheckBox.UseVisualStyleBackColor = true; this.NotifyUnacknowledgedCheckBox.CheckedChanged += new System.EventHandler(this.NotifyUnacknowledgedCheckBox_CheckedChanged); // // NotifyUnacknowledgedTextBox // this.NotifyUnacknowledgedTextBox.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.NotifyUnacknowledgedTextBox.Location = new System.Drawing.Point(232, 3); - this.NotifyUnacknowledgedTextBox.Name = "NotifyUnacknowledgedTextBox"; - this.NotifyUnacknowledgedTextBox.Size = new System.Drawing.Size(84, 23); - this.NotifyUnacknowledgedTextBox.TabIndex = 5; - // - // label1 - // - this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(322, 7); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(50, 15); - this.label1.TabIndex = 6; - this.label1.Text = "minutes"; - // - // IssueSettingsWindow - // - this.AcceptButton = this.OkBtn; - this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; - this.CancelButton = this.CancelBtn; - this.ClientSize = new System.Drawing.Size(455, 198); - this.ControlBox = false; - this.Controls.Add(this.groupBox2); - this.Controls.Add(this.CancelBtn); - this.Controls.Add(this.OkBtn); - this.Font = new System.Drawing.Font("Segoe UI", 9F); - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "IssueSettingsWindow"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "Notification Settings"; - this.groupBox2.ResumeLayout(false); - this.groupBox2.PerformLayout(); - this.tableLayoutPanel1.ResumeLayout(false); - this.tableLayoutPanel1.PerformLayout(); - this.flowLayoutPanel1.ResumeLayout(false); - this.flowLayoutPanel1.PerformLayout(); - this.flowLayoutPanel2.ResumeLayout(false); - this.flowLayoutPanel2.PerformLayout(); - this.flowLayoutPanel3.ResumeLayout(false); - this.flowLayoutPanel3.PerformLayout(); - this.ResumeLayout(false); + this.NotifyUnacknowledgedTextBox.Location = new System.Drawing.Point(232, 3); + this.NotifyUnacknowledgedTextBox.Name = "NotifyUnacknowledgedTextBox"; + this.NotifyUnacknowledgedTextBox.Size = new System.Drawing.Size(84, 23); + this.NotifyUnacknowledgedTextBox.TabIndex = 5; + // + // label1 + // + this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(322, 7); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(50, 15); + this.label1.TabIndex = 6; + this.label1.Text = "minutes"; + // + // flowLayoutPanel1 + // + this.flowLayoutPanel1.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.flowLayoutPanel1.AutoSize = true; + this.flowLayoutPanel1.Controls.Add(this.NotifyUnassignedCheckBox); + this.flowLayoutPanel1.Controls.Add(this.NotifyUnassignedTextBox); + this.flowLayoutPanel1.Controls.Add(this.label2); + this.flowLayoutPanel1.Location = new System.Drawing.Point(0, 73); + this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutPanel1.Name = "flowLayoutPanel1"; + this.flowLayoutPanel1.Size = new System.Drawing.Size(344, 29); + this.flowLayoutPanel1.TabIndex = 7; + this.flowLayoutPanel1.WrapContents = false; + // + // NotifyUnassignedCheckBox + // + this.NotifyUnassignedCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.NotifyUnassignedCheckBox.AutoSize = true; + this.NotifyUnassignedCheckBox.Location = new System.Drawing.Point(3, 5); + this.NotifyUnassignedCheckBox.Name = "NotifyUnassignedCheckBox"; + this.NotifyUnassignedCheckBox.Size = new System.Drawing.Size(192, 19); + this.NotifyUnassignedCheckBox.TabIndex = 4; + this.NotifyUnassignedCheckBox.Text = "Alert on unassigned issues after"; + this.NotifyUnassignedCheckBox.UseVisualStyleBackColor = true; + this.NotifyUnassignedCheckBox.CheckedChanged += new System.EventHandler(this.NotifyUnassignedCheckBox_CheckedChanged); + // + // NotifyUnassignedTextBox + // + this.NotifyUnassignedTextBox.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.NotifyUnassignedTextBox.Location = new System.Drawing.Point(201, 3); + this.NotifyUnassignedTextBox.Name = "NotifyUnassignedTextBox"; + this.NotifyUnassignedTextBox.Size = new System.Drawing.Size(84, 23); + this.NotifyUnassignedTextBox.TabIndex = 5; + // + // label2 + // + this.label2.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(291, 7); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(50, 15); + this.label2.TabIndex = 6; + this.label2.Text = "minutes"; + // + // flowLayoutPanel2 + // + this.flowLayoutPanel2.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.flowLayoutPanel2.AutoSize = true; + this.flowLayoutPanel2.Controls.Add(this.NotifyUnresolvedCheckBox); + this.flowLayoutPanel2.Controls.Add(this.NotifyUnresolvedTextBox); + this.flowLayoutPanel2.Controls.Add(this.label3); + this.flowLayoutPanel2.Location = new System.Drawing.Point(0, 109); + this.flowLayoutPanel2.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutPanel2.Name = "flowLayoutPanel2"; + this.flowLayoutPanel2.Size = new System.Drawing.Size(342, 29); + this.flowLayoutPanel2.TabIndex = 8; + this.flowLayoutPanel2.WrapContents = false; + // + // NotifyUnresolvedCheckBox + // + this.NotifyUnresolvedCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.NotifyUnresolvedCheckBox.AutoSize = true; + this.NotifyUnresolvedCheckBox.Location = new System.Drawing.Point(3, 5); + this.NotifyUnresolvedCheckBox.Name = "NotifyUnresolvedCheckBox"; + this.NotifyUnresolvedCheckBox.Size = new System.Drawing.Size(190, 19); + this.NotifyUnresolvedCheckBox.TabIndex = 4; + this.NotifyUnresolvedCheckBox.Text = "Alert on unresolved issues after"; + this.NotifyUnresolvedCheckBox.UseVisualStyleBackColor = true; + this.NotifyUnresolvedCheckBox.CheckedChanged += new System.EventHandler(this.NotifyUnresolvedCheckBox_CheckedChanged); + // + // NotifyUnresolvedTextBox + // + this.NotifyUnresolvedTextBox.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.NotifyUnresolvedTextBox.Location = new System.Drawing.Point(199, 3); + this.NotifyUnresolvedTextBox.Name = "NotifyUnresolvedTextBox"; + this.NotifyUnresolvedTextBox.Size = new System.Drawing.Size(84, 23); + this.NotifyUnresolvedTextBox.TabIndex = 5; + // + // label3 + // + this.label3.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(289, 7); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(50, 15); + this.label3.TabIndex = 6; + this.label3.Text = "minutes"; + // + // IssueSettingsWindow + // + this.AcceptButton = this.OkBtn; + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.CancelBtn; + this.ClientSize = new System.Drawing.Size(455, 252); + this.ControlBox = false; + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.CancelBtn); + this.Controls.Add(this.OkBtn); + this.Font = new System.Drawing.Font("Segoe UI", 9F); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "IssueSettingsWindow"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Notification Settings"; + this.groupBox2.ResumeLayout(false); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + this.flowLayoutPanel3.ResumeLayout(false); + this.flowLayoutPanel3.PerformLayout(); + this.flowLayoutPanel1.ResumeLayout(false); + this.flowLayoutPanel1.PerformLayout(); + this.flowLayoutPanel2.ResumeLayout(false); + this.flowLayoutPanel2.PerformLayout(); + this.ResumeLayout(false); } @@ -291,5 +336,8 @@ namespace UnrealGameSync private System.Windows.Forms.CheckBox NotifyUnacknowledgedCheckBox; private System.Windows.Forms.TextBox NotifyUnacknowledgedTextBox; private System.Windows.Forms.Label label1; + private System.Windows.Forms.CheckBox NotifyProjectsCheckBox; + private TextBoxWithCueBanner NotifyProjectsTextBox; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; } } \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueSettingsWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueSettingsWindow.cs index 1cb3a8ea1402..2c4b0d5372ea 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueSettingsWindow.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/IssueSettingsWindow.cs @@ -16,12 +16,23 @@ namespace UnrealGameSync { UserSettings Settings; - public IssueSettingsWindow(UserSettings Settings) + public IssueSettingsWindow(UserSettings Settings, string CurrentProject) { this.Settings = Settings; InitializeComponent(); + if (Settings.NotifyProjects.Count == 0) + { + NotifyProjectsCheckBox.Checked = false; + NotifyProjectsTextBox.Text = CurrentProject; + } + else + { + NotifyProjectsCheckBox.Checked = true; + NotifyProjectsTextBox.Text = String.Join(" ", Settings.NotifyProjects); + } + if(Settings.NotifyUnassignedMinutes < 0) { NotifyUnassignedCheckBox.Checked = false; @@ -60,11 +71,17 @@ namespace UnrealGameSync private void UpdateEnabledTextBoxes() { + NotifyProjectsTextBox.Enabled = NotifyProjectsCheckBox.Checked; NotifyUnassignedTextBox.Enabled = NotifyUnassignedCheckBox.Checked; NotifyUnacknowledgedTextBox.Enabled = NotifyUnacknowledgedCheckBox.Checked; NotifyUnresolvedTextBox.Enabled = NotifyUnresolvedCheckBox.Checked; } + private void NotifyProjectsCheckBox_CheckedChanged(object sender, EventArgs e) + { + UpdateEnabledTextBoxes(); + } + private void NotifyUnassignedCheckBox_CheckedChanged(object sender, EventArgs e) { UpdateEnabledTextBoxes(); @@ -82,6 +99,12 @@ namespace UnrealGameSync private void OkBtn_Click(object sender, EventArgs e) { + List NewNotifyProjects = new List(); + if (NotifyProjectsCheckBox.Checked) + { + NewNotifyProjects.AddRange(NotifyProjectsTextBox.Text.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); + } + int NewNotifyUnresolvedMinutes = -1; if(NotifyUnresolvedCheckBox.Checked) { @@ -118,12 +141,13 @@ namespace UnrealGameSync NewNotifyUnassignedMinutes = NewNotifyUnassignedMinutesValue; } + Settings.NotifyProjects = NewNotifyProjects; Settings.NotifyUnresolvedMinutes = NewNotifyUnresolvedMinutes; Settings.NotifyUnacknowledgedMinutes = NewNotifyUnacknowledgedMinutes; Settings.NotifyUnassignedMinutes = NewNotifyUnassignedMinutes; Settings.Save(); - DialogResult = DialogResult.Cancel; + DialogResult = DialogResult.OK; Close(); } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/MainWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/MainWindow.cs index 7894b855ca94..1aa4ab710044 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/MainWindow.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/MainWindow.cs @@ -12,6 +12,8 @@ using System.Threading; using System.Reflection; using System.Text; using System.Diagnostics; +using UnrealGameSync.Forms; +using System.Text.RegularExpressions; namespace UnrealGameSync { @@ -46,6 +48,12 @@ namespace UnrealGameSync partial class MainWindow : Form, IWorkspaceControlOwner { + class WorkspaceIssueMonitor + { + public IssueMonitor IssueMonitor; + public int RefCount; + } + [Flags] enum OpenProjectOptions { @@ -63,7 +71,8 @@ namespace UnrealGameSync UpdateMonitor UpdateMonitor; SynchronizationContext MainThreadSynchronizationContext; - List IssueMonitors = new List(); + List DefaultIssueMonitors = new List(); + List WorkspaceIssueMonitors = new List(); string ApiUrl; string DataFolder; @@ -94,7 +103,7 @@ namespace UnrealGameSync Rectangle PrimaryWorkArea; List AlertWindows = new List(); - public MainWindow(UpdateMonitor InUpdateMonitor, string InApiUrl, string InDataFolder, string InCacheFolder, bool bInRestoreStateOnLoad, string InOriginalExecutableFileName, bool bInUnstable, DetectProjectSettingsResult[] StartupProjects, PerforceConnection InDefaultConnection, LineBasedTextWriter InLog, UserSettings InSettings) + public MainWindow(UpdateMonitor InUpdateMonitor, string InApiUrl, string InDataFolder, string InCacheFolder, bool bInRestoreStateOnLoad, string InOriginalExecutableFileName, bool bInUnstable, DetectProjectSettingsResult[] StartupProjects, PerforceConnection InDefaultConnection, LineBasedTextWriter InLog, UserSettings InSettings, string InUri) { InitializeComponent(); @@ -160,17 +169,22 @@ namespace UnrealGameSync } AutomationLog = new TimestampLogWriter(new BoundedLogWriter(Path.Combine(DataFolder, "Automation.log"))); - AutomationServer = new AutomationServer(Request => { MainThreadSynchronizationContext.Post(Obj => PostAutomationRequest(Request), null); }, AutomationLog); + AutomationServer = new AutomationServer(Request => { MainThreadSynchronizationContext.Post(Obj => PostAutomationRequest(Request), null); }, AutomationLog, InUri); // Allow creating controls from now on TabPanel.ResumeLayout(false); ResumeLayout(false); + foreach (string DefaultIssueApiUrl in DeploymentSettings.DefaultIssueApiUrls) + { + DefaultIssueMonitors.Add(CreateIssueMonitor(DefaultIssueApiUrl, InDefaultConnection.UserName)); + } + bAllowCreatingHandle = true; - foreach(IssueMonitor IssueMonitor in IssueMonitors) + foreach(WorkspaceIssueMonitor WorkspaceIssueMonitor in WorkspaceIssueMonitors) { - IssueMonitor.Start(); + WorkspaceIssueMonitor.IssueMonitor.Start(); } } @@ -178,31 +192,41 @@ namespace UnrealGameSync { try { - if(!CanFocus) + if (!CanFocus) { Request.SetOutput(new AutomationRequestOutput(AutomationRequestResult.Busy)); } - else if(Request.Input.Type == AutomationRequestType.SyncProject) + else if (Request.Input.Type == AutomationRequestType.SyncProject) { AutomationRequestOutput Output = StartAutomatedSync(Request, true); - if(Output != null) + if (Output != null) { Request.SetOutput(Output); } } - else if(Request.Input.Type == AutomationRequestType.FindProject) + else if (Request.Input.Type == AutomationRequestType.FindProject) { AutomationRequestOutput Output = FindProject(Request); Request.SetOutput(Output); } - else if(Request.Input.Type == AutomationRequestType.OpenProject) + else if (Request.Input.Type == AutomationRequestType.OpenProject) { AutomationRequestOutput Output = StartAutomatedSync(Request, false); - if(Output != null) + if (Output != null) { Request.SetOutput(Output); } } + else if (Request.Input.Type == AutomationRequestType.ExecCommand) + { + AutomationRequestOutput Output = StartExecCommand(Request); + Request.SetOutput(Output); + } + else if (Request.Input.Type == AutomationRequestType.OpenIssue) + { + AutomationRequestOutput Output = OpenIssue(Request); + Request.SetOutput(new AutomationRequestOutput(AutomationRequestResult.Ok)); + } else { Request.SetOutput(new AutomationRequestOutput(AutomationRequestResult.Invalid)); @@ -215,6 +239,54 @@ namespace UnrealGameSync } } + AutomationRequestOutput StartExecCommand(AutomationRequest Request) + { + BinaryReader Reader = new BinaryReader(new MemoryStream(Request.Input.Data)); + string StreamName = Reader.ReadString(); + int Changelist = Reader.ReadInt32(); + string Command = Reader.ReadString(); + string ProjectPath = Reader.ReadString(); + + AutomatedBuildWindow.BuildInfo BuildInfo; + if (!AutomatedBuildWindow.ShowModal(this, DefaultConnection, StreamName, ProjectPath, Changelist, Command.ToString(), Settings, Log, out BuildInfo)) + { + return new AutomationRequestOutput(AutomationRequestResult.Canceled); + } + + WorkspaceControl Workspace; + if (!OpenWorkspaceForAutomation(BuildInfo.SelectedWorkspaceInfo, StreamName, BuildInfo.ProjectPath, out Workspace)) + { + return new AutomationRequestOutput(AutomationRequestResult.Error); + } + + Workspace.AddStartupCallback((Control, bCancel) => StartExecCommandAfterStartup(Control, bCancel, BuildInfo.bSync? Changelist : -1, BuildInfo.ExecCommand)); + return new AutomationRequestOutput(AutomationRequestResult.Ok); + } + + private void StartExecCommandAfterStartup(WorkspaceControl Workspace, bool bCancel, int Changelist, string Command) + { + if (!bCancel) + { + if(Changelist == -1) + { + StartExecCommandAfterSync(Workspace, WorkspaceUpdateResult.Success, Command); + } + else + { + Workspace.SyncChange(Changelist, true, Result => StartExecCommandAfterSync(Workspace, Result, Command)); + } + } + } + + private void StartExecCommandAfterSync(WorkspaceControl Workspace, WorkspaceUpdateResult Result, string Command) + { + if (Result == WorkspaceUpdateResult.Success && Command != null) + { + string CmdExe = Environment.GetEnvironmentVariable("COMSPEC"); + Workspace.ExecCommand("Run build command", "Running build command", CmdExe, String.Format("/c {0}", Command), Workspace.BranchDirectoryName, true); + } + } + AutomationRequestOutput StartAutomatedSync(AutomationRequest Request, bool bForceSync) { ShowAndActivate(); @@ -229,41 +301,9 @@ namespace UnrealGameSync return new AutomationRequestOutput(AutomationRequestResult.Canceled); } - if(WorkspaceInfo.bRequiresStreamSwitch) + WorkspaceControl Workspace; + if(!OpenWorkspaceForAutomation(WorkspaceInfo, StreamName, ProjectPath, out Workspace)) { - // Close any tab containing this window - for(int ExistingTabIdx = 0; ExistingTabIdx < TabControl.GetTabCount(); ExistingTabIdx++) - { - WorkspaceControl ExistingWorkspace = TabControl.GetTabData(ExistingTabIdx) as WorkspaceControl; - if(ExistingWorkspace != null && ExistingWorkspace.ClientName.Equals(WorkspaceInfo.WorkspaceName)) - { - TabControl.RemoveTab(ExistingTabIdx); - break; - } - } - - // Switch the stream - PerforceConnection Perforce = new PerforceConnection(WorkspaceInfo.UserName, WorkspaceInfo.WorkspaceName, WorkspaceInfo.ServerAndPort); - if(!Perforce.SwitchStream(StreamName, Log)) - { - Log.WriteLine("Unable to switch stream"); - return new AutomationRequestOutput(AutomationRequestResult.Error); - } - } - - UserSelectedProjectSettings SelectedProject = new UserSelectedProjectSettings(WorkspaceInfo.ServerAndPort, WorkspaceInfo.UserName, UserSelectedProjectType.Client, String.Format("//{0}{1}", WorkspaceInfo.WorkspaceName, ProjectPath), null); - - int TabIdx = TryOpenProject(SelectedProject, -1, OpenProjectOptions.None); - if(TabIdx == -1) - { - Log.WriteLine("Unable to open project"); - return new AutomationRequestOutput(AutomationRequestResult.Error); - } - - WorkspaceControl Workspace = TabControl.GetTabData(TabIdx) as WorkspaceControl; - if(Workspace == null) - { - Log.WriteLine("Workspace was unable to open"); return new AutomationRequestOutput(AutomationRequestResult.Error); } @@ -276,6 +316,54 @@ namespace UnrealGameSync return null; } + private bool OpenWorkspaceForAutomation(AutomatedSyncWindow.WorkspaceInfo WorkspaceInfo, string StreamName, string ProjectPath, out WorkspaceControl OutWorkspace) + { + if (WorkspaceInfo.bRequiresStreamSwitch) + { + // Close any tab containing this window + for (int ExistingTabIdx = 0; ExistingTabIdx < TabControl.GetTabCount(); ExistingTabIdx++) + { + WorkspaceControl ExistingWorkspace = TabControl.GetTabData(ExistingTabIdx) as WorkspaceControl; + if (ExistingWorkspace != null && ExistingWorkspace.ClientName.Equals(WorkspaceInfo.WorkspaceName)) + { + TabControl.RemoveTab(ExistingTabIdx); + break; + } + } + + // Switch the stream + PerforceConnection Perforce = new PerforceConnection(WorkspaceInfo.UserName, WorkspaceInfo.WorkspaceName, WorkspaceInfo.ServerAndPort); + if (!Perforce.SwitchStream(StreamName, Log)) + { + Log.WriteLine("Unable to switch stream"); + OutWorkspace = null; + return false; + } + } + + UserSelectedProjectSettings SelectedProject = new UserSelectedProjectSettings(WorkspaceInfo.ServerAndPort, WorkspaceInfo.UserName, UserSelectedProjectType.Client, String.Format("//{0}{1}", WorkspaceInfo.WorkspaceName, ProjectPath), null); + + int TabIdx = TryOpenProject(SelectedProject, -1, OpenProjectOptions.None); + if (TabIdx == -1) + { + Log.WriteLine("Unable to open project"); + OutWorkspace = null; + return false; + } + + WorkspaceControl Workspace = TabControl.GetTabData(TabIdx) as WorkspaceControl; + if (Workspace == null) + { + Log.WriteLine("Workspace was unable to open"); + OutWorkspace = null; + return false; + } + + TabControl.SelectTab(TabIdx); + OutWorkspace = Workspace; + return true; + } + private void StartAutomatedSyncAfterStartup(WorkspaceControl Workspace, bool bCancel, AutomationRequest Request) { if(bCancel) @@ -334,6 +422,44 @@ namespace UnrealGameSync return new AutomationRequestOutput(AutomationRequestResult.NotFound); } + AutomationRequestOutput OpenIssue(AutomationRequest Request) + { + BinaryReader Reader = new BinaryReader(new MemoryStream(Request.Input.Data)); + int IssueId = Reader.ReadInt32(); + + for (int ExistingTabIdx = 0; ExistingTabIdx < TabControl.GetTabCount(); ExistingTabIdx++) + { + WorkspaceControl ExistingWorkspace = TabControl.GetTabData(ExistingTabIdx) as WorkspaceControl; + if (ExistingWorkspace != null) + { + IssueMonitor IssueMonitor = ExistingWorkspace.GetIssueMonitor(); + if (IssueMonitor != null) + { + IssueMonitor.AddRef(); + try + { + IssueData Issue = RESTApi.GET(IssueMonitor.ApiUrl, String.Format("issues/{0}", IssueId)); + Issue.Builds = RESTApi.GET>(IssueMonitor.ApiUrl, String.Format("issues/{0}/builds", IssueId)); + ExistingWorkspace.ShowIssueDetails(Issue); + return new AutomationRequestOutput(AutomationRequestResult.Ok); + } + catch (Exception Ex) + { + MessageBox.Show(String.Format("Unable to query issue {0} from {1}\n\n{2}", IssueId, IssueMonitor.ApiUrl, Ex)); + Log.WriteException(Ex, "Unable to query issue {0} from {1}", IssueId, IssueMonitor.ApiUrl); + return new AutomationRequestOutput(AutomationRequestResult.Error); + } + finally + { + IssueMonitor.Release(); + } + } + } + } + + return new AutomationRequestOutput(AutomationRequestResult.NotFound); + } + protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); @@ -405,32 +531,6 @@ namespace UnrealGameSync } TabPanel.Dispose(); - HashSet NewIssueMonitors = new HashSet(); - for(int Idx = 0; Idx < TabControl.GetTabCount(); Idx++) - { - WorkspaceControl Workspace = TabControl.GetTabData(Idx) as WorkspaceControl; - if(Workspace != null) - { - NewIssueMonitors.Add(Workspace.GetIssueMonitor()); - } - } - foreach(IssueMonitor IssueMonitor in IssueMonitors) - { - if(!NewIssueMonitors.Contains(IssueMonitor)) - { - for(int Idx = AlertWindows.Count - 1; Idx >= 0; Idx--) - { - IssueAlertWindow AlertWindow = AlertWindows[Idx]; - if(AlertWindow.IssueMonitor == IssueMonitor) - { - CloseAlertWindow(AlertWindow); - } - } - IssueMonitor.Release(); - } - } - IssueMonitors = NewIssueMonitors.ToList(); - SaveTabSettings(); } @@ -458,11 +558,12 @@ namespace UnrealGameSync StopScheduleTimer(); - foreach(IssueMonitor IssueMonitor in IssueMonitors) + foreach (IssueMonitor DefaultIssueMonitor in DefaultIssueMonitors) { - IssueMonitor.Release(); + ReleaseIssueMonitor(DefaultIssueMonitor); } - IssueMonitors.Clear(); + DefaultIssueMonitors.Clear(); + Debug.Assert(WorkspaceIssueMonitors.Count == 0); if(AutomationServer != null) { @@ -801,7 +902,7 @@ namespace UnrealGameSync if(Workspace != null) { Log.WriteLine("Schedule: Considering {0}", Workspace.SelectedFileName); - if(Settings.ScheduleAnyOpenProject || Settings.ScheduleProjects.Contains(Workspace.SelectedProject)) + if(Settings.ScheduleAnyOpenProject || Settings.ScheduleProjects.Any(x => x.LocalPath.Equals(Workspace.SelectedProject.LocalPath, StringComparison.OrdinalIgnoreCase))) { Log.WriteLine("Schedule: Starting Sync"); Workspace.ScheduleTimerElapsed(); @@ -1031,18 +1132,8 @@ namespace UnrealGameSync } } - // Find or add an issue monitor for this - IssueMonitor IssueMonitor = IssueMonitors.FirstOrDefault(x => String.Compare(x.UserName, ProjectSettings.PerforceClient.UserName, StringComparison.OrdinalIgnoreCase) == 0); - if(IssueMonitor == null) - { - string LogFileName = Path.Combine(DataFolder, String.Format("IssueMonitor-{0}.log", ProjectSettings.PerforceClient.UserName)); - IssueMonitor = new IssueMonitor(ApiUrl, ProjectSettings.PerforceClient.UserName, TimeSpan.FromSeconds(60.0), LogFileName); - IssueMonitor.OnIssuesChanged += IssueMonitor_OnUpdateAsync; - IssueMonitors.Add(IssueMonitor); - } - // Now that we have the project settings, we can construct the tab - WorkspaceControl NewWorkspace = new WorkspaceControl(this, ApiUrl, OriginalExecutableFileName, bUnstable, ProjectSettings, IssueMonitor, Log, Settings); + WorkspaceControl NewWorkspace = new WorkspaceControl(this, ApiUrl, OriginalExecutableFileName, bUnstable, ProjectSettings, Log, Settings); NewWorkspace.Parent = TabPanel; NewWorkspace.Dock = DockStyle.Fill; NewWorkspace.Hide(); @@ -1278,10 +1369,20 @@ namespace UnrealGameSync WindowState = Settings.WindowState; } + bool ShowNotificationForIssue(IssueData Issue) + { + return Issue.Projects.Any(x => ShowNotificationsForProject(x)); + } + + bool ShowNotificationsForProject(string Project) + { + return String.IsNullOrEmpty(Project) || Settings.NotifyProjects.Count == 0 || Settings.NotifyProjects.Any(x => x.Equals(Project, StringComparison.OrdinalIgnoreCase)); + } + public void UpdateAlertWindows() { HashSet AllIssues = new HashSet(); - foreach(IssueMonitor IssueMonitor in IssueMonitors) + foreach(IssueMonitor IssueMonitor in WorkspaceIssueMonitors.Select(x => x.IssueMonitor)) { List Issues = IssueMonitor.GetIssues(); foreach(IssueData Issue in Issues) @@ -1295,7 +1396,7 @@ namespace UnrealGameSync { Reason |= IssueAlertReason.Normal; } - if(Settings.NotifyUnassignedMinutes >= 0 && Issue.RetrievedAt - Issue.CreatedAt >= TimeSpan.FromMinutes(Settings.NotifyUnassignedMinutes)) + if(ShowNotificationForIssue(Issue) && Settings.NotifyUnassignedMinutes >= 0 && Issue.RetrievedAt - Issue.CreatedAt >= TimeSpan.FromMinutes(Settings.NotifyUnassignedMinutes)) { Reason |= IssueAlertReason.UnassignedTimer; } @@ -1306,12 +1407,12 @@ namespace UnrealGameSync { Reason |= IssueAlertReason.Owner; } - else if(Settings.NotifyUnacknowledgedMinutes >= 0 && Issue.RetrievedAt - Issue.CreatedAt >= TimeSpan.FromMinutes(Settings.NotifyUnacknowledgedMinutes)) + else if(ShowNotificationForIssue(Issue) && Settings.NotifyUnacknowledgedMinutes >= 0 && Issue.RetrievedAt - Issue.CreatedAt >= TimeSpan.FromMinutes(Settings.NotifyUnacknowledgedMinutes)) { Reason |= IssueAlertReason.UnacknowledgedTimer; } } - if(Settings.NotifyUnresolvedMinutes >= 0 && Issue.RetrievedAt - Issue.CreatedAt >= TimeSpan.FromMinutes(Settings.NotifyUnresolvedMinutes)) + if(ShowNotificationForIssue(Issue) && Settings.NotifyUnresolvedMinutes >= 0 && Issue.RetrievedAt - Issue.CreatedAt >= TimeSpan.FromMinutes(Settings.NotifyUnresolvedMinutes)) { Reason |= IssueAlertReason.UnresolvedTimer; } @@ -1469,5 +1570,58 @@ namespace UnrealGameSync { SetAlertWindowPositions(); } + + public IssueMonitor CreateIssueMonitor(string ApiUrl, string UserName) + { + WorkspaceIssueMonitor WorkspaceIssueMonitor = WorkspaceIssueMonitors.FirstOrDefault(x => String.Compare(x.IssueMonitor.ApiUrl, ApiUrl, StringComparison.OrdinalIgnoreCase) == 0 && String.Compare(x.IssueMonitor.UserName, UserName, StringComparison.OrdinalIgnoreCase) == 0); + if (WorkspaceIssueMonitor == null) + { + string ServerId = Regex.Replace(ApiUrl, @"^.*://", ""); + ServerId = Regex.Replace(ServerId, "[^a-zA-Z.]", "+"); + + string LogFileName = Path.Combine(DataFolder, String.Format("IssueMonitor-{0}-{1}.log", ServerId, UserName)); + + WorkspaceIssueMonitor = new WorkspaceIssueMonitor(); + WorkspaceIssueMonitor.IssueMonitor = new IssueMonitor(ApiUrl, UserName, TimeSpan.FromSeconds(60.0), LogFileName); + WorkspaceIssueMonitor.IssueMonitor.OnIssuesChanged += IssueMonitor_OnUpdateAsync; + + WorkspaceIssueMonitors.Add(WorkspaceIssueMonitor); + } + + WorkspaceIssueMonitor.RefCount++; + + if (WorkspaceIssueMonitor.RefCount == 1 && bAllowCreatingHandle) + { + WorkspaceIssueMonitor.IssueMonitor.Start(); + } + + return WorkspaceIssueMonitor.IssueMonitor; + } + + public void ReleaseIssueMonitor(IssueMonitor IssueMonitor) + { + int Index = WorkspaceIssueMonitors.FindIndex(x => x.IssueMonitor == IssueMonitor); + if(Index != -1) + { + WorkspaceIssueMonitor WorkspaceIssueMonitor = WorkspaceIssueMonitors[Index]; + WorkspaceIssueMonitor.RefCount--; + + if (WorkspaceIssueMonitor.RefCount == 0) + { + for (int Idx = AlertWindows.Count - 1; Idx >= 0; Idx--) + { + IssueAlertWindow AlertWindow = AlertWindows[Idx]; + if (AlertWindow.IssueMonitor == IssueMonitor) + { + CloseAlertWindow(AlertWindow); + } + } + IssueMonitor.OnIssuesChanged -= IssueMonitor_OnUpdateAsync; + IssueMonitor.Release(); + + WorkspaceIssueMonitors.RemoveAt(Index); + } + } + } } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/IssueMonitor.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/IssueMonitor.cs index 933d50d16771..c7433ce90570 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/IssueMonitor.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/IssueMonitor.cs @@ -1,14 +1,20 @@ // Copyright Epic Games, Inc. All Rights Reserved. +using EnvDTE; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Linq; +using System.Security.Policy; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Thread = System.Threading.Thread; + namespace UnrealGameSync { public enum IssueBuildOutcome @@ -54,6 +60,51 @@ namespace UnrealGameSync public DateTime? ResolvedAt { get; set; } public bool bNotify { get; set; } public List Builds { get; set; } + public List Streams { get;set; } + + HashSet CachedProjects; + + public HashSet Projects + { + get + { + // HACK to infer project names from streams + if(CachedProjects == null) + { + HashSet NewProjects = new HashSet(StringComparer.OrdinalIgnoreCase); + if (!String.IsNullOrEmpty(Project)) + { + NewProjects.Add(Project); + } + if (Streams != null) + { + foreach (string Stream in Streams) + { + Match Match = Regex.Match(Stream, "^//([^/]+)/"); + if (Match.Success) + { + string Project = Match.Groups[1].Value; + if (Project.StartsWith("UE", StringComparison.OrdinalIgnoreCase)) + { + Project = "UE" + Project.Substring(2); + } + else if (Char.IsLower(Project[0])) + { + Project = Char.ToUpper(Project[0], CultureInfo.InvariantCulture) + Project.Substring(1); + } + NewProjects.Add(Project); + } + } + } + if (NewProjects.Count == 0) + { + NewProjects.Add("Default"); + } + CachedProjects = NewProjects; + } + return CachedProjects; + } + } } public class IssueUpdateData @@ -276,9 +327,9 @@ namespace UnrealGameSync { // Check if there's any pending update IssueUpdateData PendingUpdate; - lock(LockObject) + lock (LockObject) { - if(PendingUpdates.Count > 0) + if (PendingUpdates.Count > 0) { PendingUpdate = PendingUpdates[0]; } @@ -289,9 +340,9 @@ namespace UnrealGameSync } // If we have an update, try to post it to the backend and check for another - if(PendingUpdate != null) + if (PendingUpdate != null) { - if(SendUpdate(PendingUpdate)) + if (SendUpdate(PendingUpdate)) { lock (LockObject) { PendingUpdates.RemoveAt(0); } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Perforce.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Perforce.cs index 80363a412367..d31d08b85adc 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Perforce.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Perforce.cs @@ -43,6 +43,7 @@ namespace UnrealGameSync public string Host; public string Stream; public string Root; + public long Access; public PerforceClientRecord(Dictionary Tags) { @@ -51,6 +52,12 @@ namespace UnrealGameSync Tags.TryGetValue("Host", out Host); Tags.TryGetValue("Stream", out Stream); Tags.TryGetValue("Root", out Root); + + string AccessString; + if (Tags.TryGetValue("Access", out AccessString)) + { + long.TryParse(AccessString, out Access); + } } } @@ -163,6 +170,7 @@ namespace UnrealGameSync public string HostName; public string ClientAddress; public TimeSpan ServerTimeZone; + public bool Unicode; public PerforceInfoRecord(List> TagRecords) { @@ -183,6 +191,9 @@ namespace UnrealGameSync } } } + + string UnicodeValue; + Unicode = TryGetValue(TagRecords, "unicode", out UnicodeValue) && UnicodeValue == "enabled"; } static bool TryGetValue(List> TagRecords, string Key, out string Value) @@ -515,11 +526,29 @@ namespace UnrealGameSync public readonly string UserName; public readonly string ClientName; + public bool UnicodeServer; + static Dictionary UnicodeServerCache = new Dictionary(); + + public PerforceConnection(string InUserName, string InClientName, string InServerAndPort) { ServerAndPort = InServerAndPort; UserName = InUserName; ClientName = InClientName; + + lock (UnicodeServerCache) + { + string ServerAndPortKey = ServerAndPort ?? String.Empty; + if (!UnicodeServerCache.TryGetValue(ServerAndPortKey, out UnicodeServer)) + { + PerforceInfoRecord InfoRecord; + if (Info(out InfoRecord, new StringWriter())) + { + UnicodeServer = InfoRecord.Unicode; + UnicodeServerCache[ServerAndPortKey] = UnicodeServer; + } + } + } } public PerforceConnection OpenClient(string NewClientName) @@ -1503,6 +1532,13 @@ namespace UnrealGameSync FullCommandLine.Append("-s "); } FullCommandLine.AppendFormat("-zprog=UGS -zversion={0} ", Assembly.GetExecutingAssembly().GetName().Version.ToString()); + + if(UnicodeServer) + { + FullCommandLine.Append("-C utf8 "); + } + FullCommandLine.Append("-Q utf8 "); + FullCommandLine.Append(CommandLine); return FullCommandLine.ToString(); @@ -1618,12 +1654,15 @@ namespace UnrealGameSync /// Whether to include client information on the command line private bool RunCommandWithBinaryOutput(string CommandLine, HandleRecordDelegate HandleOutput, CommandOptions Options, TextWriter Log) { + string FullCommandLine = GetFullCommandLine("-G " + CommandLine, Options | CommandOptions.NoChannels); + Log.WriteLine("p4> p4.exe {0}", CommandLine); + // Execute Perforce, consuming the binary output into a memory stream MemoryStream MemoryStream = new MemoryStream(); using (Process Process = new Process()) { Process.StartInfo.FileName = "p4.exe"; - Process.StartInfo.Arguments = GetFullCommandLine("-G " + CommandLine, Options | CommandOptions.NoChannels); + Process.StartInfo.Arguments = FullCommandLine; Process.StartInfo.RedirectStandardError = true; Process.StartInfo.RedirectStandardOutput = true; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/PerforceMonitor.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/PerforceMonitor.cs index eaf8c946e316..08ba2dc1a66c 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/PerforceMonitor.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/PerforceMonitor.cs @@ -34,6 +34,11 @@ namespace UnrealGameSync { bContainsContent = true; } + + if (bContainsCode && bContainsContent) + { + break; + } } } } @@ -222,6 +227,25 @@ namespace UnrealGameSync } void PollForUpdates() + { + while (!bDisposing) + { + try + { + PollForUpdatesInner(); + } + catch (ThreadAbortException) + { + break; + } + catch (Exception Ex) + { + LogWriter.WriteException(Ex, "Unhandled exception in PollForUpdatesInner()"); + } + } + } + + void PollForUpdatesInner() { string StreamName; if(!Perforce.GetActiveStream(out StreamName, LogWriter)) diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Program.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Program.cs index cdef39706b9a..04f095154bf8 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Program.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Program.cs @@ -6,6 +6,7 @@ using System.Data.SqlClient; using System.Diagnostics; using System.IO; using System.Linq; +using System.Net; using System.Reflection; using System.Runtime.InteropServices; using System.Text; @@ -22,15 +23,24 @@ namespace UnrealGameSync [STAThread] static void Main(string[] Args) { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - bool bFirstInstance; - using(Mutex InstanceMutex = new Mutex(true, "UnrealGameSyncRunning", out bFirstInstance)) + using (Mutex InstanceMutex = new Mutex(true, "UnrealGameSyncRunning", out bFirstInstance)) { - using(EventWaitHandle ActivateEvent = new EventWaitHandle(false, EventResetMode.AutoReset, "ActivateUnrealGameSync")) + if (bFirstInstance) { - if(bFirstInstance) + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + } + + using (EventWaitHandle ActivateEvent = new EventWaitHandle(false, EventResetMode.AutoReset, "ActivateUnrealGameSync")) + { + // handle any url passed in, possibly exiting + if (UriHandler.ProcessCommandLine(Args, bFirstInstance, ActivateEvent)) + { + return; + } + + if (bFirstInstance) { InnerMain(InstanceMutex, ActivateEvent, Args); } @@ -66,6 +76,9 @@ namespace UnrealGameSync string ProjectFileName; ParseArgument(RemainingArgs, "-project=", out ProjectFileName); + string Uri; + ParseArgument(RemainingArgs, "-uri=", out Uri); + string UpdateConfigFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate.ini"); MergeUpdateSettings(UpdateConfigFile, ref UpdatePath, ref UpdateSpawn); @@ -85,6 +98,9 @@ namespace UnrealGameSync string DataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "UnrealGameSync"); Directory.CreateDirectory(DataFolder); + // Enable TLS 1.1 and 1.2. TLS 1.0 is now deprecated and not allowed by default in NET Core servers. + ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; + // Create the log file using (TimestampLogWriter Log = new TimestampLogWriter(new BoundedLogWriter(Path.Combine(DataFolder, "UnrealGameSync.log")))) { @@ -117,7 +133,7 @@ namespace UnrealGameSync PerforceConnection DefaultConnection = new PerforceConnection(UserName, null, ServerAndPort); using (UpdateMonitor UpdateMonitor = new UpdateMonitor(DefaultConnection, UpdatePath)) { - ProgramApplicationContext Context = new ProgramApplicationContext(DefaultConnection, UpdateMonitor, DeploymentSettings.ApiUrl, DataFolder, ActivateEvent, bRestoreState, UpdateSpawn, ProjectFileName, bUnstable, Log); + ProgramApplicationContext Context = new ProgramApplicationContext(DefaultConnection, UpdateMonitor, DeploymentSettings.ApiUrl, DataFolder, ActivateEvent, bRestoreState, UpdateSpawn, ProjectFileName, bUnstable, Log, Uri); Application.Run(Context); if (UpdateMonitor.IsUpdateAvailable && UpdateSpawn != null) diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ProgramApplicationContext.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ProgramApplicationContext.cs index c4610f3d9b33..166f8e1b03a5 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ProgramApplicationContext.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ProgramApplicationContext.cs @@ -27,6 +27,7 @@ namespace UnrealGameSync string UpdateSpawn; bool bUnstable; bool bIsClosing; + string Uri; TimestampLogWriter Log; UserSettings Settings; @@ -47,7 +48,7 @@ namespace UnrealGameSync ModalTaskWindow DetectStartupProjectSettingsWindow; MainWindow MainWindowInstance; - public ProgramApplicationContext(PerforceConnection DefaultConnection, UpdateMonitor UpdateMonitor, string ApiUrl, string DataFolder, EventWaitHandle ActivateEvent, bool bRestoreState, string UpdateSpawn, string ProjectFileName, bool bUnstable, TimestampLogWriter Log) + public ProgramApplicationContext(PerforceConnection DefaultConnection, UpdateMonitor UpdateMonitor, string ApiUrl, string DataFolder, EventWaitHandle ActivateEvent, bool bRestoreState, string UpdateSpawn, string ProjectFileName, bool bUnstable, TimestampLogWriter Log, string Uri) { this.DefaultConnection = DefaultConnection; this.UpdateMonitor = UpdateMonitor; @@ -58,6 +59,7 @@ namespace UnrealGameSync this.UpdateSpawn = UpdateSpawn; this.bUnstable = bUnstable; this.Log = Log; + this.Uri = Uri; // Create the directories Directory.CreateDirectory(DataFolder); @@ -74,7 +76,7 @@ namespace UnrealGameSync MainThreadSynchronizationContext = WindowsFormsSynchronizationContext.Current; // Read the user's settings - Settings = new UserSettings(Path.Combine(DataFolder, "UnrealGameSync.ini")); + Settings = new UserSettings(Path.Combine(DataFolder, "UnrealGameSync.ini"), Log); if(!String.IsNullOrEmpty(ProjectFileName)) { string FullProjectFileName = Path.GetFullPath(ProjectFileName); @@ -90,7 +92,8 @@ namespace UnrealGameSync // Clear out the server settings for anything using the default server if(Settings.Version < UserSettingsVersion.DefaultServerSettings) { - for(int Idx = 0; Idx < Settings.OpenProjects.Count; Idx++) + Log.WriteLine("Clearing project settings for default server"); + for (int Idx = 0; Idx < Settings.OpenProjects.Count; Idx++) { Settings.OpenProjects[Idx] = UpgradeSelectedProjectSettings(Settings.OpenProjects[Idx]); } @@ -173,6 +176,8 @@ namespace UnrealGameSync List Tasks = new List(); foreach(UserSelectedProjectSettings OpenProject in Settings.OpenProjects) { + Log.WriteLine("Opening existing project {0}", OpenProject); + BufferedTextWriter StartupLog = new BufferedTextWriter(); StartupLog.WriteLine("Detecting settings for {0}", OpenProject); StartupLogs.Add(StartupLog); @@ -234,7 +239,7 @@ namespace UnrealGameSync DetectProjectSettingsResult[] StartupProjects = DetectStartupProjectSettingsTask.Results.Where(x => x != null).ToArray(); // Create the main window - MainWindowInstance = new MainWindow(UpdateMonitor, ApiUrl, DataFolder, CacheFolder, bRestoreState, UpdateSpawn ?? Assembly.GetExecutingAssembly().Location, bUnstable, StartupProjects, DefaultConnection, Log, Settings); + MainWindowInstance = new MainWindow(UpdateMonitor, ApiUrl, DataFolder, CacheFolder, bRestoreState, UpdateSpawn ?? Assembly.GetExecutingAssembly().Location, bUnstable, StartupProjects, DefaultConnection, Log, Settings, Uri); if(bVisible) { MainWindowInstance.Show(); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Properties/AssemblyInfo.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Properties/AssemblyInfo.cs index 550320ab6995..0ef61a1ad94f 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Properties/AssemblyInfo.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Properties/AssemblyInfo.cs @@ -34,5 +34,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.201.0.0")] -[assembly: AssemblyFileVersion("1.201.0.0")] +[assembly: AssemblyVersion("1.211.0.0")] +[assembly: AssemblyFileVersion("1.211.0.0")] diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/RESTApi.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/RESTApi.cs index 2c26d9e44e12..cf0db1c20f22 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/RESTApi.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/RESTApi.cs @@ -10,6 +10,19 @@ using System.Text.Json; namespace UnrealGameSync { + class RestException : Exception + { + public RestException(string Method, string Uri, Exception InnerException) + : base(String.Format("Error executing {0} {1}", Method, Uri), InnerException) + { + } + + public override string ToString() + { + return String.Format("{0}\n\n{1}", Message, InnerException.ToString()); + } + } + public static class RESTApi { private static string SendRequestInternal(string URI, string Resource, string Method, string RequestBody = null, params string[] QueryParams) @@ -28,17 +41,17 @@ namespace UnrealGameSync } } } + HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(TargetURI.ToString()); Request.ContentType = "application/json"; Request.Method = Method; - // Add json to request body if (!string.IsNullOrEmpty(RequestBody)) { if (Method == "POST" || Method == "PUT") { - byte[] bytes = Encoding.ASCII.GetBytes(RequestBody); + byte[] bytes = Encoding.UTF8.GetBytes(RequestBody); using (Stream RequestStream = Request.GetRequestStream()) { RequestStream.Write(bytes, 0, bytes.Length); @@ -47,28 +60,19 @@ namespace UnrealGameSync } try { - WebResponse Repsonse = Request.GetResponse(); - string ResponseContent = null; - using (StreamReader ResponseReader = new System.IO.StreamReader(Repsonse.GetResponseStream(), Encoding.Default)) + using (WebResponse Response = Request.GetResponse()) { - ResponseContent = ResponseReader.ReadToEnd(); - } - return ResponseContent; - } - catch (WebException ex) - { - if (ex.Response != null) - { - throw new Exception(string.Format("Request returned status: {0}, message: {1}", ((HttpWebResponse)ex.Response).StatusCode, ex.Message)); - } - else - { - throw new Exception(string.Format("Request returned message: {0}", ex.InnerException.Message)); + string ResponseContent; + using (StreamReader ResponseReader = new StreamReader(Response.GetResponseStream(), Encoding.UTF8)) + { + ResponseContent = ResponseReader.ReadToEnd(); + } + return ResponseContent; } } - catch (Exception ex) + catch (Exception Ex) { - throw new Exception(string.Format("Couldn't complete the request, error: {0}", ex.Message)); + throw new RestException(Method, Request.RequestUri.ToString(), Ex); } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UnrealGameSync.csproj b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UnrealGameSync.csproj index e392965710bb..dc9b597fe8b6 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UnrealGameSync.csproj +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UnrealGameSync.csproj @@ -1,4 +1,4 @@ - + WinExe netcoreapp3.1 @@ -16,23 +16,17 @@ TRACE;WITH_TELEMETRY + + + ThirdParty\Ionic.Zip.Reduced.dll - + + ..\..\..\..\Binaries\ThirdParty\VisualStudio\Microsoft.VisualStudio.Setup.Configuration.Interop.dll + true + @@ -56,4 +50,5 @@ + \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/P4Handler.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/P4Handler.cs new file mode 100644 index 000000000000..45c1d349f413 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/P4Handler.cs @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +namespace UnrealGameSync +{ + + /// + /// P4 URI handler + /// + static class P4Handler + { + [UriHandler(true)] + public static UriResult Timelapse(string DepotPath, int Line = -1) + { + string CommandLine = string.Format("timelapse {0}{1}", Line == -1 ? "" : string.Format(" -l {0} ", Line), DepotPath); + + if (!Utility.SpawnHiddenProcess("p4vc.exe", CommandLine)) + { + return new UriResult() { Error = string.Format("Error spawning p4vc.exe with command line: {0}", CommandLine) }; + } + + return new UriResult() { Success = true }; + } + } +} \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/UGSHandler.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/UGSHandler.cs new file mode 100644 index 000000000000..7b10d222ade8 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/UGSHandler.cs @@ -0,0 +1,75 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Windows.Forms; + +namespace UnrealGameSync +{ + + /// + /// UGS handler + /// + static class UGSHandler + { + + [UriHandler] + public static UriResult OpenProject(string Stream, string Project, bool Sync = false) + { + // Create the request + using (MemoryStream InputDataStream = new MemoryStream()) + { + using (BinaryWriter Writer = new BinaryWriter(InputDataStream)) + { + Writer.Write(Stream); + Writer.Write(Project); + } + + AutomationRequestInput Input = new AutomationRequestInput(Sync ? AutomationRequestType.SyncProject : AutomationRequestType.OpenProject, InputDataStream.GetBuffer()); + return new UriResult() { Success = true, Request = new AutomationRequest(Input) }; + } + } + + [UriHandler] + public static UriResult BuildStep(string Project, string Stream, string Step, string Changelist, string Arguments) + { + MessageBox.Show(string.Format("Project: {0}\nStream: {1}\nStep: {2}\nChange: {3}\nArguments: {4}", Project, Stream, Step, Changelist, Arguments), "UGS Build Step Handler"); + + return new UriResult() { Success = true }; + } + + [UriHandler] + public static UriResult Execute(string Stream, int Changelist, string Command, string Project = "") + { + using (MemoryStream InputDataStream = new MemoryStream()) + { + using (BinaryWriter Writer = new BinaryWriter(InputDataStream)) + { + Writer.Write(Stream); + Writer.Write(Changelist); + Writer.Write(Command); + Writer.Write(Project); + } + + AutomationRequestInput Input = new AutomationRequestInput(AutomationRequestType.ExecCommand, InputDataStream.GetBuffer()); + return new UriResult() { Success = true, Request = new AutomationRequest(Input) }; + } + } + + [UriHandler] + public static UriResult OpenIssue(int Id) + { + using (MemoryStream InputDataStream = new MemoryStream()) + { + using (BinaryWriter Writer = new BinaryWriter(InputDataStream)) + { + Writer.Write(Id); + } + + AutomationRequestInput Input = new AutomationRequestInput(AutomationRequestType.OpenIssue, InputDataStream.GetBuffer()); + return new UriResult() { Success = true, Request = new AutomationRequest(Input) }; + } + } + } +} \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/UriHandler.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/UriHandler.cs new file mode 100644 index 000000000000..3823d0743add --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/UriHandler.cs @@ -0,0 +1,407 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using Microsoft.Win32; +using System.Reflection; +using System.Threading; +using System.Collections.Specialized; +using System.Web; +using System.Linq; +using System.Diagnostics; +using System.Windows.Forms; +using System.Configuration; + +namespace UnrealGameSync +{ + + /// + /// Uri handler result + /// + class UriResult + { + public bool Success = false; + public string Error; + public AutomationRequest Request = null; + } + + /// + /// Main handler for Uri requests + /// + static class UriHandler + { + public static UriResult HandleUri(string UriIn) + { + try + { + Uri Uri = new Uri(UriIn); + + // Check if this is a registered uri request + if (!Handlers.TryGetValue(Uri.Host, out MethodInfo Info)) + { + return new UriResult() { Error = string.Format("Unknown Uri {0}", Uri.Host) }; + } + + NameValueCollection Query = HttpUtility.ParseQueryString(Uri.Query); + + List Parameters = new List(); + + foreach (ParameterInfo Param in Info.GetParameters()) + { + string Value = Query.Get(Param.Name); + + if (Value == null) + { + if (!Param.HasDefaultValue) + { + return new UriResult() { Error = string.Format("Uri {0} is missing required parameter {1}", Uri.Host, Param.Name) }; + } + + Parameters.Add(Param.DefaultValue); + continue; + } + + if (Param.ParameterType == typeof(string)) + { + Parameters.Add(Value); + } + else if (Param.ParameterType == typeof(bool)) + { + if (Value.Equals("true", StringComparison.OrdinalIgnoreCase)) + { + Parameters.Add(true); + } + else if (Value.Equals("false", StringComparison.OrdinalIgnoreCase)) + { + Parameters.Add(false); + } + else + { + return new UriResult() { Error = string.Format("Uri {0} bool parameter {1} must be true or false", Uri.Host, Param.Name) }; + } + } + else if (Param.ParameterType == typeof(int) || Param.ParameterType == typeof(float)) + { + float NumberValue; + if (!float.TryParse(Value, out NumberValue)) + { + return new UriResult() { Error = string.Format("Uri {0} invalid number parameter {1} : {2}", Uri.Host, Param.Name, Value) }; + } + + if (Param.ParameterType == typeof(int)) + { + Parameters.Add(Convert.ToInt32(NumberValue)); + } + else + { + Parameters.Add(NumberValue); + } + + } + + } + + return Info.Invoke(null, Parameters.ToArray()) as UriResult; + } + catch (Exception Ex) + { + return new UriResult() { Error = Ex.Message }; + } + } + + public const string InstallHandlerArg = "-InstallHandler"; + public const string UninstallHandlerArg = "-UninstallHandler"; + public const string ElevatedArg = "-Elevated"; + + /// + /// Handle URI passed in via command lines + /// + public static bool ProcessCommandLine(string[] Args, bool FirstInstance, EventWaitHandle ActivateEvent = null) + { + if (Args.Any(x => x.Equals(InstallHandlerArg, StringComparison.OrdinalIgnoreCase))) + { + if (Args.Any(x => x.Equals(ElevatedArg, StringComparison.OrdinalIgnoreCase))) + { + ProtocolHandlerUtils.InstallElevated(); + } + else + { + ProtocolHandlerUtils.Install(); + } + return true; + } + else if (Args.Any(x => x.Equals(UninstallHandlerArg, StringComparison.OrdinalIgnoreCase))) + { + if (Args.Any(x => x.Equals(ElevatedArg, StringComparison.OrdinalIgnoreCase))) + { + ProtocolHandlerUtils.UninstallElevated(); + } + else + { + ProtocolHandlerUtils.Uninstall(); + } + return true; + } + else + { + string UriIn = string.Empty; + for (int Idx = 0; Idx < Args.Length; Idx++) + { + const string Prefix = "-uri="; + if (Args[Idx].StartsWith(Prefix, StringComparison.OrdinalIgnoreCase)) + { + UriIn = Args[Idx].Substring(Prefix.Length); + } + } + + if (UriIn == string.Empty) + { + return false; + } + + Uri Uri; + try + { + Uri = new Uri(UriIn); + } + catch + { + MessageBox.Show(String.Format("Invalid URI: {0}", UriIn)); + return true; + } + + MethodInfo Handler; + if (!Handlers.TryGetValue(Uri.Host, out Handler)) + { + MessageBox.Show(String.Format("Unknown action from URI request ('{0}')", Uri.Host)); + return true; + } + + UriHandlerAttribute Attribute = Handler.GetCustomAttribute(); + + // handle case where we terminate after invoking handler + if (Attribute.Terminate) + { + UriResult Result = HandleUri(UriIn); + if (!Result.Success) + { + MessageBox.Show(Result.Error); + } + return true; + } + + if (!FirstInstance) + { + if (ActivateEvent != null) + { + ActivateEvent.Set(); + } + + // send to main UGS process using IPC + AutomationServer.SendUri(UriIn); + return true; + } + + // we're in the main UGS process, which was also launched, defer handling to after main window is created + return false; + } + } + + + static UriHandler() + { + foreach (Type Type in Assembly.GetExecutingAssembly().GetTypes()) + { + List HandlerMethods = new List (Type.GetMethods().Where(MethodInfo => MethodInfo.GetCustomAttribute() != null)); + foreach (MethodInfo MethodInfo in HandlerMethods) + { + if (!MethodInfo.IsStatic) + { + throw new Exception(string.Format("UriHandler method {0} must be static", MethodInfo.Name)); + } + + if (MethodInfo.ReturnType != typeof(UriResult)) + { + throw new Exception(string.Format("UriHandler method {0} must return UriResult type", MethodInfo.Name)); + } + + if (Handlers.ContainsKey(MethodInfo.Name)) + { + throw new Exception(string.Format("UriHandler method {0} clashes with another handler", MethodInfo.Name)); + } + + foreach (ParameterInfo ParameterInfo in MethodInfo.GetParameters()) + { + Type ParameterType = ParameterInfo.ParameterType; + if (ParameterType != typeof(bool) && ParameterType != typeof(string) && ParameterType != typeof(float) && ParameterType != typeof(int)) + { + throw new Exception(string.Format("UriHandler method parameter {0} must be bool, string, int, or float", ParameterInfo.Name)); + } + } + + Handlers[MethodInfo.Name] = MethodInfo; + } + } + } + + /// + /// All available handler infos + /// + static readonly Dictionary Handlers = new Dictionary(StringComparer.OrdinalIgnoreCase); + + } + + /// + /// Method attribute for Uri handlers + /// + [AttributeUsage(AttributeTargets.Method)] + class UriHandlerAttribute : Attribute + { + public bool Terminate; + + public UriHandlerAttribute(bool Terminate = false) + { + this.Terminate = Terminate; + } + } + + enum ProtocolHandlerState + { + Unknown, + Installed, + NotInstalled, + } + + /// + /// Development utilities to register protocol binary, this or a variation should go in installer/updater + /// Needs administrator to edit registry + /// + static class ProtocolHandlerUtils + { + class RegistrySetting + { + public RegistryKey RootKey; + public string KeyName; + public string ValueName; + public string Value; + + public RegistrySetting(RegistryKey RootKey, string KeyName, string ValueName, string Value) + { + this.RootKey = RootKey; + this.KeyName = KeyName; + this.ValueName = ValueName; + this.Value = Value; + } + } + + static List GetRegistrySettings() + { + string ApplicationPath = Assembly.GetExecutingAssembly().Location; + + List Keys = new List(); + Keys.Add(new RegistrySetting(Registry.ClassesRoot, "UGS", null, "URL:UGS Protocol")); + Keys.Add(new RegistrySetting(Registry.ClassesRoot, "UGS", "URL Protocol", "")); + Keys.Add(new RegistrySetting(Registry.ClassesRoot, "UGS\\DefaultIcon", null, String.Format("\"{0}\",0", ApplicationPath))); + Keys.Add(new RegistrySetting(Registry.ClassesRoot, "UGS\\shell\\open\\command", null, String.Format("\"{0}\" -uri=\"%1\"", ApplicationPath))); + return Keys; + } + + public static ProtocolHandlerState GetState() + { + try + { + bool bHasAny = false; + bool bHasAll = true; + + List Keys = GetRegistrySettings(); + foreach (IGrouping RootKeyGroup in Keys.GroupBy(x => x.RootKey)) + { + foreach (IGrouping KeyNameGroup in RootKeyGroup.GroupBy(x => x.KeyName)) + { + using (RegistryKey RegistryKey = RootKeyGroup.Key.OpenSubKey(KeyNameGroup.Key)) + { + if (RegistryKey == null) + { + bHasAll = false; + } + else + { + bHasAll &= KeyNameGroup.All(x => (RegistryKey.GetValue(x.ValueName) as string) == x.Value); + bHasAny = true; + } + } + } + } + + return bHasAll? ProtocolHandlerState.Installed : bHasAny? ProtocolHandlerState.Unknown : ProtocolHandlerState.NotInstalled; + } + catch + { + return ProtocolHandlerState.Unknown; + } + } + + public static void Install() + { + RunElevated(String.Format("{0} {1}", UriHandler.InstallHandlerArg, UriHandler.ElevatedArg)); + } + + public static void InstallElevated() + { + try + { + List Keys = GetRegistrySettings(); + foreach (IGrouping RootKeyGroup in Keys.GroupBy(x => x.RootKey)) + { + foreach (IGrouping KeyNameGroup in RootKeyGroup.GroupBy(x => x.KeyName)) + { + using (RegistryKey RegistryKey = RootKeyGroup.Key.CreateSubKey(KeyNameGroup.Key)) + { + foreach (RegistrySetting Setting in KeyNameGroup) + { + RegistryKey.SetValue(Setting.ValueName, Setting.Value); + } + } + } + } + } + catch (Exception Ex) + { + MessageBox.Show(String.Format("Unable to register protocol handler: {0}", Ex)); + } + } + + public static void Uninstall() + { + RunElevated(String.Format("{0} {1}", UriHandler.UninstallHandlerArg, UriHandler.ElevatedArg)); + } + + public static void UninstallElevated() + { + try + { + Registry.ClassesRoot.DeleteSubKeyTree("UGS", false); + } + catch (Exception Ex) + { + MessageBox.Show(String.Format("Unable to register protocol handler: {0}", Ex)); + } + } + + private static void RunElevated(string Arguments) + { + using (Process Process = new Process()) + { + Process.StartInfo.FileName = Assembly.GetExecutingAssembly().Location; + Process.StartInfo.Arguments = Arguments; + Process.StartInfo.Verb = "runas"; + Process.StartInfo.UseShellExecute = true; + Process.Start(); + Process.WaitForExit(); + } + } + } +} + + + diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/VisualStudioHandler.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/VisualStudioHandler.cs new file mode 100644 index 000000000000..b7893b002a36 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UriHandlers/VisualStudioHandler.cs @@ -0,0 +1,35 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using System.IO; +using EnvDTE; + +namespace UnrealGameSync +{ + + /// + /// VisualStudio Uri Handler + /// + static class VisualStudioHandler + { + [UriHandler(true)] + public static UriResult VSOpen(string DepotPath, int Line = -1) + { + string ErrorMessage; + string TempFileName; + + if (!P4Automation.PrintToTempFile(null, DepotPath, out TempFileName, out ErrorMessage)) + { + return new UriResult() { Error = ErrorMessage ?? "Unknown P4 Error" }; + } + + if (!VisualStudioAutomation.OpenFile(TempFileName, out ErrorMessage, Line)) + { + return new UriResult() { Error = ErrorMessage ?? "Unknown Visual Studio Error" }; + } + + return new UriResult() { Success = true }; + } + + } + +} \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UserSettings.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UserSettings.cs index b56736e00c81..ad2d999ec3c1 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UserSettings.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UserSettings.cs @@ -290,6 +290,7 @@ namespace UnrealGameSync public bool bEditorArgumentsPrompt; // Notification settings + public List NotifyProjects; public int NotifyUnassignedMinutes; public int NotifyUnacknowledgedMinutes; public int NotifyUnresolvedMinutes; @@ -335,13 +336,10 @@ namespace UnrealGameSync return Projects; } - public UserSettings(string InFileName) + public UserSettings(string InFileName, TextWriter Log) { FileName = InFileName; - if(File.Exists(FileName)) - { - ConfigFile.Load(FileName); - } + ConfigFile.TryLoad(FileName, Log); // General settings Version = (UserSettingsVersion)ConfigFile.GetValue("General.Version", (int)UserSettingsVersion.Initial); @@ -458,6 +456,7 @@ namespace UnrealGameSync ScheduleProjects = ReadProjectList("Schedule.Projects", "Schedule.ProjectFileNames"); // Notification settings + NotifyProjects = ConfigFile.GetValues("Notifications.NotifyProjects", new string[0]).ToList(); NotifyUnassignedMinutes = ConfigFile.GetValue("Notifications.NotifyUnassignedMinutes", -1); NotifyUnacknowledgedMinutes = ConfigFile.GetValue("Notifications.NotifyUnacknowledgedMinutes", -1); NotifyUnresolvedMinutes = ConfigFile.GetValue("Notifications.NotifyUnresolvedMinutes", -1); @@ -754,6 +753,10 @@ namespace UnrealGameSync // Notification settings ConfigSection NotificationSection = ConfigFile.FindOrAddSection("Notifications"); NotificationSection.Clear(); + if (NotifyProjects.Count > 0) + { + NotificationSection.SetValues("NotifyProjects", NotifyProjects.ToArray()); + } if (NotifyUnassignedMinutes != -1) { NotificationSection.SetValue("NotifyUnassignedMinutes", NotifyUnassignedMinutes); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Workspace.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Workspace.cs index 2980faa0a7e4..253808b40905 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Workspace.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Workspace.cs @@ -616,6 +616,7 @@ namespace UnrealGameSync SyncRecords.AddRange(OpenRecords.Where(x => x.Action != "add" && x.Action != "branch" && x.Action != "move/add")); + // Enumerate all the files to be synced. NOTE: depotPath is escaped, whereas clientPath is not. foreach (PerforceFileRecord SyncRecord in SyncRecords) { // If it doesn't exist locally, just add a sync command for it @@ -651,12 +652,12 @@ namespace UnrealGameSync string RelativePath = FullName.Substring(LocalRootPrefix.Length).Replace('\\', '/'); if (Filter.Matches(RelativePath)) { - SyncTree.IncludeFile(RelativePath, SyncRecord.FileSize); + SyncTree.IncludeFile(PerforceUtils.EscapePath(RelativePath), SyncRecord.FileSize); SyncDepotPaths.Add(SyncRecord.DepotPath); } else { - SyncTree.ExcludeFile(RelativePath); + SyncTree.ExcludeFile(PerforceUtils.EscapePath(RelativePath)); } } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSyncLauncher/UnrealGameSyncLauncher.csproj b/Engine/Source/Programs/UnrealGameSync/UnrealGameSyncLauncher/UnrealGameSyncLauncher.csproj index c4a51de31f0a..67b5ab2c2c68 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSyncLauncher/UnrealGameSyncLauncher.csproj +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSyncLauncher/UnrealGameSyncLauncher.csproj @@ -1,8 +1,9 @@ - + WinExe + UGS_LAUNCHER netcoreapp3.1 true false diff --git a/Engine/Source/Programs/UnrealHeaderTool/Private/ClassDeclarationMetaData.cpp b/Engine/Source/Programs/UnrealHeaderTool/Private/ClassDeclarationMetaData.cpp index 6ec17d796fb5..e17c38d7e35b 100644 --- a/Engine/Source/Programs/UnrealHeaderTool/Private/ClassDeclarationMetaData.cpp +++ b/Engine/Source/Programs/UnrealHeaderTool/Private/ClassDeclarationMetaData.cpp @@ -184,6 +184,12 @@ void FClassDeclarationMetaData::ParseClassProperties(TArray& ClassFlags |= CLASS_GlobalUserConfig; break; + case EClassMetadataSpecifier::ProjectUserConfig: + + // Save object config only to project user overrides, never to INIs that are checked in + ClassFlags |= CLASS_ProjectUserConfig; + break; + case EClassMetadataSpecifier::ShowCategories: FHeaderParser::RequireSpecifierValue(PropSpecifier); diff --git a/Engine/Source/Programs/UnrealHeaderTool/Private/ClassMaps.h b/Engine/Source/Programs/UnrealHeaderTool/Private/ClassMaps.h index 593f9ff4c55c..4fb5e285eceb 100644 --- a/Engine/Source/Programs/UnrealHeaderTool/Private/ClassMaps.h +++ b/Engine/Source/Programs/UnrealHeaderTool/Private/ClassMaps.h @@ -8,6 +8,7 @@ #include "UnderlyingEnumType.h" #include "UnrealSourceFile.h" +#include "UObject/ErrorException.h" class UField; class UClass; diff --git a/Engine/Source/Programs/UnrealHeaderTool/Private/CodeGenerator.cpp b/Engine/Source/Programs/UnrealHeaderTool/Private/CodeGenerator.cpp index 718a2f35020c..f1d0b9455b06 100644 --- a/Engine/Source/Programs/UnrealHeaderTool/Private/CodeGenerator.cpp +++ b/Engine/Source/Programs/UnrealHeaderTool/Private/CodeGenerator.cpp @@ -3534,6 +3534,10 @@ FString FNativeClassHeaderGenerator::GetClassFlagExportText( UClass* Class ) { StaticClassFlagText += TEXT(" | CLASS_GlobalUserConfig"); } + if (Class->HasAnyClassFlags(CLASS_ProjectUserConfig)) + { + StaticClassFlagText += TEXT(" | CLASS_ProjectUserConfig"); + } if( Class->HasAnyClassFlags(CLASS_Config) ) { StaticClassFlagText += TEXT(" | CLASS_Config"); diff --git a/Engine/Source/Programs/UnrealHeaderTool/Private/Specifiers/ClassMetadataSpecifiers.def b/Engine/Source/Programs/UnrealHeaderTool/Private/Specifiers/ClassMetadataSpecifiers.def index 671fe75f93d9..5254125c915f 100644 --- a/Engine/Source/Programs/UnrealHeaderTool/Private/Specifiers/ClassMetadataSpecifiers.def +++ b/Engine/Source/Programs/UnrealHeaderTool/Private/Specifiers/ClassMetadataSpecifiers.def @@ -35,6 +35,7 @@ CLASS_METADATA_SPECIFIER(NotEditInlineNew) CLASS_METADATA_SPECIFIER(NotPlaceable) CLASS_METADATA_SPECIFIER(PerObjectConfig) CLASS_METADATA_SPECIFIER(Placeable) +CLASS_METADATA_SPECIFIER(ProjectUserConfig) CLASS_METADATA_SPECIFIER(ShowCategories) CLASS_METADATA_SPECIFIER(ShowFunctions) CLASS_METADATA_SPECIFIER(SparseClassDataTypes) diff --git a/Engine/Source/Programs/Windows/CompileTimeAnalyzer/CompileTimeAnalyzer/MainWindow.xaml b/Engine/Source/Programs/Windows/CompileTimeAnalyzer/CompileTimeAnalyzer/MainWindow.xaml index 5de0519975ab..f6be85d8ac81 100644 --- a/Engine/Source/Programs/Windows/CompileTimeAnalyzer/CompileTimeAnalyzer/MainWindow.xaml +++ b/Engine/Source/Programs/Windows/CompileTimeAnalyzer/CompileTimeAnalyzer/MainWindow.xaml @@ -7,7 +7,9 @@ xmlns:tdi="clr-namespace:Timing_Data_Investigator" mc:Ignorable="d" Title="CompileTimeAnalyzer" Width="1024" Height="768" WindowStartupLocation="CenterScreen"> - + + +